Merge "Annotate some SQLite APIs for nullability" into androidx-master-dev
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
index 1ecc48eb..d522e82 100644
--- a/.idea/codeStyles/Project.xml
+++ b/.idea/codeStyles/Project.xml
@@ -1,5 +1,6 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
+ <option name="WRAP_WHEN_TYPING_REACHES_RIGHT_MARGIN" value="true" />
<JavaCodeStyleSettings>
<option name="FIELD_NAME_PREFIX" value="m" />
<option name="STATIC_FIELD_NAME_PREFIX" value="s" />
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index 550b016..aafa7bf 100644
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -44,6 +44,7 @@
<option name="IGNORE_POINT_TO_ITSELF" value="false" />
<option name="myAdditionalJavadocTags" value="hide" />
</inspection_tool>
+ <inspection_tool class="KDocUnresolvedReference" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="MissingDeprecatedAnnotation" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="NullableProblems" enabled="true" level="ERROR" enabled_by_default="true">
<option name="REPORT_NULLABLE_METHOD_OVERRIDES_NOTNULL" value="true" />
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 174d43b..f4c5c4f 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -51,4 +51,4 @@
<component name="ProjectType">
<option name="id" value="Android" />
</component>
-</project>
\ No newline at end of file
+</project>
diff --git a/README.md b/README.md
index 96b9476..552466f 100644
--- a/README.md
+++ b/README.md
@@ -84,9 +84,9 @@
./gradlew createArchive
-And put in your **project** `build.gradle` file:
+And put the following at the top of your 'repositories' property in your **project** `build.gradle` file:
- handler.maven { url '/path/to/checkout/out/androidx/build/support_repo/' }
+ maven { url '/path/to/checkout/out/androidx/build/support_repo/' }
### Continuous integration
[Our continuous integration system](https://ci.android.com/builds/branches/aosp-androidx-master-dev/grid?) builds all in progress (and potentially unstable) libraries as new changes are merged. You can manually download these AARs and JARs for your experimentation.
diff --git a/activity/activity-ktx/src/main/java/androidx/activity/ActivityViewModelLazy.kt b/activity/activity-ktx/src/main/java/androidx/activity/ActivityViewModelLazy.kt
index 2b7b97f..0cdc8cc 100644
--- a/activity/activity-ktx/src/main/java/androidx/activity/ActivityViewModelLazy.kt
+++ b/activity/activity-ktx/src/main/java/androidx/activity/ActivityViewModelLazy.kt
@@ -20,7 +20,6 @@
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelLazy
import androidx.lifecycle.ViewModelProvider
-import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory
import androidx.lifecycle.ViewModelProvider.Factory
/**
@@ -42,10 +41,7 @@
noinline factoryProducer: (() -> Factory)? = null
): Lazy<VM> {
val factoryPromise = factoryProducer ?: {
- val application = application ?: throw IllegalArgumentException(
- "ViewModel can be accessed only when Activity is attached"
- )
- AndroidViewModelFactory.getInstance(application)
+ defaultViewModelProviderFactory
}
return ViewModelLazy(VM::class, { viewModelStore }, factoryPromise)
diff --git a/activity/activity/api/1.1.0-alpha02.txt b/activity/activity/api/1.1.0-alpha02.txt
index cd98da6..f087fd3 100644
--- a/activity/activity/api/1.1.0-alpha02.txt
+++ b/activity/activity/api/1.1.0-alpha02.txt
@@ -1,9 +1,10 @@
// Signature format: 3.0
package androidx.activity {
- public class ComponentActivity extends androidx.core.app.ComponentActivity implements androidx.lifecycle.LifecycleOwner androidx.activity.OnBackPressedDispatcherOwner androidx.savedstate.SavedStateRegistryOwner androidx.lifecycle.ViewModelStoreOwner {
+ public class ComponentActivity extends androidx.core.app.ComponentActivity implements androidx.lifecycle.HasDefaultViewModelProviderFactory androidx.lifecycle.LifecycleOwner androidx.activity.OnBackPressedDispatcherOwner androidx.savedstate.SavedStateRegistryOwner androidx.lifecycle.ViewModelStoreOwner {
ctor public ComponentActivity();
ctor @ContentView public ComponentActivity(@LayoutRes int);
+ method public androidx.lifecycle.ViewModelProvider.Factory getDefaultViewModelProviderFactory();
method @Deprecated public Object? getLastCustomNonConfigurationInstance();
method public androidx.lifecycle.Lifecycle getLifecycle();
method public final androidx.activity.OnBackPressedDispatcher getOnBackPressedDispatcher();
diff --git a/activity/activity/api/current.txt b/activity/activity/api/current.txt
index cd98da6..f087fd3 100644
--- a/activity/activity/api/current.txt
+++ b/activity/activity/api/current.txt
@@ -1,9 +1,10 @@
// Signature format: 3.0
package androidx.activity {
- public class ComponentActivity extends androidx.core.app.ComponentActivity implements androidx.lifecycle.LifecycleOwner androidx.activity.OnBackPressedDispatcherOwner androidx.savedstate.SavedStateRegistryOwner androidx.lifecycle.ViewModelStoreOwner {
+ public class ComponentActivity extends androidx.core.app.ComponentActivity implements androidx.lifecycle.HasDefaultViewModelProviderFactory androidx.lifecycle.LifecycleOwner androidx.activity.OnBackPressedDispatcherOwner androidx.savedstate.SavedStateRegistryOwner androidx.lifecycle.ViewModelStoreOwner {
ctor public ComponentActivity();
ctor @ContentView public ComponentActivity(@LayoutRes int);
+ method public androidx.lifecycle.ViewModelProvider.Factory getDefaultViewModelProviderFactory();
method @Deprecated public Object? getLastCustomNonConfigurationInstance();
method public androidx.lifecycle.Lifecycle getLifecycle();
method public final androidx.activity.OnBackPressedDispatcher getOnBackPressedDispatcher();
diff --git a/activity/activity/api/restricted_1.1.0-alpha02.txt b/activity/activity/api/restricted_1.1.0-alpha02.txt
index cd98da6..f087fd3 100644
--- a/activity/activity/api/restricted_1.1.0-alpha02.txt
+++ b/activity/activity/api/restricted_1.1.0-alpha02.txt
@@ -1,9 +1,10 @@
// Signature format: 3.0
package androidx.activity {
- public class ComponentActivity extends androidx.core.app.ComponentActivity implements androidx.lifecycle.LifecycleOwner androidx.activity.OnBackPressedDispatcherOwner androidx.savedstate.SavedStateRegistryOwner androidx.lifecycle.ViewModelStoreOwner {
+ public class ComponentActivity extends androidx.core.app.ComponentActivity implements androidx.lifecycle.HasDefaultViewModelProviderFactory androidx.lifecycle.LifecycleOwner androidx.activity.OnBackPressedDispatcherOwner androidx.savedstate.SavedStateRegistryOwner androidx.lifecycle.ViewModelStoreOwner {
ctor public ComponentActivity();
ctor @ContentView public ComponentActivity(@LayoutRes int);
+ method public androidx.lifecycle.ViewModelProvider.Factory getDefaultViewModelProviderFactory();
method @Deprecated public Object? getLastCustomNonConfigurationInstance();
method public androidx.lifecycle.Lifecycle getLifecycle();
method public final androidx.activity.OnBackPressedDispatcher getOnBackPressedDispatcher();
diff --git a/activity/activity/api/restricted_current.txt b/activity/activity/api/restricted_current.txt
index cd98da6..f087fd3 100644
--- a/activity/activity/api/restricted_current.txt
+++ b/activity/activity/api/restricted_current.txt
@@ -1,9 +1,10 @@
// Signature format: 3.0
package androidx.activity {
- public class ComponentActivity extends androidx.core.app.ComponentActivity implements androidx.lifecycle.LifecycleOwner androidx.activity.OnBackPressedDispatcherOwner androidx.savedstate.SavedStateRegistryOwner androidx.lifecycle.ViewModelStoreOwner {
+ public class ComponentActivity extends androidx.core.app.ComponentActivity implements androidx.lifecycle.HasDefaultViewModelProviderFactory androidx.lifecycle.LifecycleOwner androidx.activity.OnBackPressedDispatcherOwner androidx.savedstate.SavedStateRegistryOwner androidx.lifecycle.ViewModelStoreOwner {
ctor public ComponentActivity();
ctor @ContentView public ComponentActivity(@LayoutRes int);
+ method public androidx.lifecycle.ViewModelProvider.Factory getDefaultViewModelProviderFactory();
method @Deprecated public Object? getLastCustomNonConfigurationInstance();
method public androidx.lifecycle.Lifecycle getLifecycle();
method public final androidx.activity.OnBackPressedDispatcher getOnBackPressedDispatcher();
diff --git a/activity/activity/build.gradle b/activity/activity/build.gradle
index 81bb273..b6edf7e 100644
--- a/activity/activity/build.gradle
+++ b/activity/activity/build.gradle
@@ -22,6 +22,7 @@
api(project(":lifecycle:lifecycle-runtime"))
api(project(":lifecycle:lifecycle-viewmodel"))
api("androidx.savedstate:savedstate:1.0.0-rc01")
+ api(project(":lifecycle:lifecycle-viewmodel-savedstate"))
androidTestImplementation(KOTLIN_STDLIB)
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
diff --git a/activity/activity/lint-baseline.xml b/activity/activity/lint-baseline.xml
deleted file mode 100644
index 591c093..0000000
--- a/activity/activity/lint-baseline.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
- 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.
- -->
-
-<issues format="5" by="lint 3.5.0-beta04" client="gradle" variant="debug" version="3.5.0-beta04">
-
- <issue
- id="LambdaLast"
- message="Functional interface parameters (such as parameter 1, "owner", in androidx.activity.OnBackPressedDispatcher.addCallback) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions"
- errorLine1=" @NonNull OnBackPressedCallback onBackPressedCallback) {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/activity/OnBackPressedDispatcher.java"
- line="144"
- column="13"/>
- </issue>
-
-</issues>
diff --git a/activity/activity/src/androidTest/java/androidx/activity/ComponentActivityViewModelTest.kt b/activity/activity/src/androidTest/java/androidx/activity/ComponentActivityViewModelTest.kt
index b7a8b3f..e52b140 100644
--- a/activity/activity/src/androidTest/java/androidx/activity/ComponentActivityViewModelTest.kt
+++ b/activity/activity/src/androidTest/java/androidx/activity/ComponentActivityViewModelTest.kt
@@ -16,7 +16,10 @@
package androidx.activity
+import android.app.Application
import android.os.Bundle
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelStore
@@ -72,12 +75,18 @@
fun testActivityOnCleared() {
lateinit var activityModel: TestViewModel
lateinit var defaultActivityModel: TestViewModel
+ lateinit var androidModel: TestAndroidViewModel
+ lateinit var savedStateModel: TestSavedStateViewModel
ActivityScenario.launch(ViewModelActivity::class.java).use { scenario ->
activityModel = scenario.withActivity { this.activityModel }
defaultActivityModel = scenario.withActivity { this.defaultActivityModel }
+ androidModel = scenario.withActivity { this.androidModel }
+ savedStateModel = scenario.withActivity { this.savedStateModel }
}
assertThat(activityModel.cleared).isTrue()
assertThat(defaultActivityModel.cleared).isTrue()
+ assertThat(androidModel.cleared).isTrue()
+ assertThat(savedStateModel.cleared).isTrue()
}
}
@@ -91,18 +100,19 @@
lateinit var postOnCreateViewModelStore: ViewModelStore
lateinit var activityModel: TestViewModel
lateinit var defaultActivityModel: TestViewModel
+ lateinit var androidModel: TestAndroidViewModel
+ lateinit var savedStateModel: TestSavedStateViewModel
override fun onCreate(savedInstanceState: Bundle?) {
preOnCreateViewModelStore = viewModelStore
super.onCreate(savedInstanceState)
postOnCreateViewModelStore = viewModelStore
- val viewModelProvider = ViewModelProvider(
- this,
- ViewModelProvider.NewInstanceFactory()
- )
+ val viewModelProvider = ViewModelProvider(this)
activityModel = viewModelProvider.get(KEY_ACTIVITY_MODEL, TestViewModel::class.java)
defaultActivityModel = viewModelProvider.get(TestViewModel::class.java)
+ androidModel = viewModelProvider.get(TestAndroidViewModel::class.java)
+ savedStateModel = viewModelProvider.get(TestSavedStateViewModel::class.java)
}
}
@@ -112,4 +122,21 @@
override fun onCleared() {
cleared = true
}
-}
\ No newline at end of file
+}
+
+class TestAndroidViewModel(application: Application) : AndroidViewModel(application) {
+ var cleared = false
+
+ override fun onCleared() {
+ cleared = true
+ }
+}
+
+@Suppress("unused")
+class TestSavedStateViewModel(val savedStateHandle: SavedStateHandle) : ViewModel() {
+ var cleared = false
+
+ override fun onCleared() {
+ cleared = true
+ }
+}
diff --git a/activity/activity/src/main/java/androidx/activity/ComponentActivity.java b/activity/activity/src/main/java/androidx/activity/ComponentActivity.java
index a596e73..cee2d27 100644
--- a/activity/activity/src/main/java/androidx/activity/ComponentActivity.java
+++ b/activity/activity/src/main/java/androidx/activity/ComponentActivity.java
@@ -29,11 +29,14 @@
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.lifecycle.HasDefaultViewModelProviderFactory;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleEventObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LifecycleRegistry;
import androidx.lifecycle.ReportFragment;
+import androidx.lifecycle.SavedStateViewModelFactory;
+import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelStore;
import androidx.lifecycle.ViewModelStoreOwner;
import androidx.savedstate.SavedStateRegistry;
@@ -50,6 +53,7 @@
public class ComponentActivity extends androidx.core.app.ComponentActivity implements
LifecycleOwner,
ViewModelStoreOwner,
+ HasDefaultViewModelProviderFactory,
SavedStateRegistryOwner,
OnBackPressedDispatcherOwner {
@@ -64,6 +68,7 @@
// Lazily recreated from NonConfigurationInstances by getViewModelStore()
private ViewModelStore mViewModelStore;
+ private ViewModelProvider.Factory mDefaultFactory;
private final OnBackPressedDispatcher mOnBackPressedDispatcher =
new OnBackPressedDispatcher(new Runnable() {
@@ -273,6 +278,29 @@
}
/**
+ * {@inheritDoc}
+ *
+ * <p>The extras of {@link #getIntent()} 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() {
+ if (getApplication() == null) {
+ throw new IllegalStateException("Your activity is not yet attached to the "
+ + "Application instance. You can't request ViewModel before onCreate call.");
+ }
+ if (mDefaultFactory == null) {
+ mDefaultFactory = new SavedStateViewModelFactory(
+ getApplication(),
+ this,
+ getIntent() != null ? getIntent().getExtras() : null);
+ }
+ return mDefaultFactory;
+ }
+
+ /**
* Called when the activity has detected the user's press of the back
* key. The {@link #getOnBackPressedDispatcher() OnBackPressedDispatcher} will be given a
* chance to handle the back button before the default behavior of
diff --git a/activity/activity/src/main/java/androidx/activity/OnBackPressedDispatcher.java b/activity/activity/src/main/java/androidx/activity/OnBackPressedDispatcher.java
index 71e5dce..e842248 100644
--- a/activity/activity/src/main/java/androidx/activity/OnBackPressedDispatcher.java
+++ b/activity/activity/src/main/java/androidx/activity/OnBackPressedDispatcher.java
@@ -16,6 +16,8 @@
package androidx.activity;
+import android.annotation.SuppressLint;
+
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -139,6 +141,7 @@
*
* @see #onBackPressed()
*/
+ @SuppressLint("LambdaLast")
@MainThread
public void addCallback(@NonNull LifecycleOwner owner,
@NonNull OnBackPressedCallback onBackPressedCallback) {
diff --git a/ads/ads-identifier-common/api/1.0.0-alpha01.txt b/ads/ads-identifier-common/api/1.0.0-alpha01.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/ads/ads-identifier-common/api/1.0.0-alpha01.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/ads/ads-identifier-common/api/current.txt b/ads/ads-identifier-common/api/current.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/ads/ads-identifier-common/api/current.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/benchmark/api/res-1.0.0-alpha01.txt b/ads/ads-identifier-common/api/res-1.0.0-alpha01.txt
similarity index 100%
copy from benchmark/api/res-1.0.0-alpha01.txt
copy to ads/ads-identifier-common/api/res-1.0.0-alpha01.txt
diff --git a/ads/ads-identifier-common/api/restricted_1.0.0-alpha01.txt b/ads/ads-identifier-common/api/restricted_1.0.0-alpha01.txt
new file mode 100644
index 0000000..ae9f628
--- /dev/null
+++ b/ads/ads-identifier-common/api/restricted_1.0.0-alpha01.txt
@@ -0,0 +1,11 @@
+// Signature format: 3.0
+package androidx.ads.identifier {
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public class AdvertisingIdUtils {
+ method public static java.util.List<android.content.pm.ResolveInfo!> getAdvertisingIdProviderServices(android.content.pm.PackageManager);
+ method public static android.content.pm.ServiceInfo? selectServiceByPriority(java.util.List<android.content.pm.ResolveInfo!>?, android.content.pm.PackageManager);
+ field public static final String GET_AD_ID_ACTION = "androidx.ads.identifier.provider.GET_AD_ID";
+ }
+
+}
+
diff --git a/ads/ads-identifier-common/api/restricted_current.txt b/ads/ads-identifier-common/api/restricted_current.txt
new file mode 100644
index 0000000..ae9f628
--- /dev/null
+++ b/ads/ads-identifier-common/api/restricted_current.txt
@@ -0,0 +1,11 @@
+// Signature format: 3.0
+package androidx.ads.identifier {
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public class AdvertisingIdUtils {
+ method public static java.util.List<android.content.pm.ResolveInfo!> getAdvertisingIdProviderServices(android.content.pm.PackageManager);
+ method public static android.content.pm.ServiceInfo? selectServiceByPriority(java.util.List<android.content.pm.ResolveInfo!>?, android.content.pm.PackageManager);
+ field public static final String GET_AD_ID_ACTION = "androidx.ads.identifier.provider.GET_AD_ID";
+ }
+
+}
+
diff --git a/benchmark/build.gradle b/ads/ads-identifier-common/build.gradle
similarity index 61%
copy from benchmark/build.gradle
copy to ads/ads-identifier-common/build.gradle
index 386f71d..3433fb7 100644
--- a/benchmark/build.gradle
+++ b/ads/ads-identifier-common/build.gradle
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * 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.
@@ -26,20 +26,26 @@
}
dependencies {
- implementation(ANDROIDX_TEST_RULES)
- implementation(ANDROIDX_TEST_RUNNER)
- implementation(KOTLIN_STDLIB)
- implementation(SUPPORT_ANNOTATIONS)
+ implementation("androidx.annotation:annotation:1.1.0")
- androidTestImplementation(ANDROIDX_TEST_CORE)
- androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
+ testImplementation(ANDROIDX_TEST_CORE)
+ testImplementation(ANDROIDX_TEST_RUNNER)
+ testImplementation(JUNIT)
+ testImplementation(TRUTH)
+ testImplementation(MOCKITO_CORE)
+}
+
+android {
+ defaultConfig {
+ minSdkVersion 14
+ }
}
androidx {
- name = "Android Benchmark"
+ name = "AndroidX Ads Identifier Common"
publish = Publish.SNAPSHOT_AND_RELEASE
- mavenVersion = LibraryVersions.BENCHMARK
- mavenGroup = LibraryGroups.BENCHMARK
- inceptionYear = "2018"
- description = "Android Benchmark"
+ mavenVersion = LibraryVersions.ADS_IDENTIFIER
+ mavenGroup = LibraryGroups.ADS
+ inceptionYear = "2019"
+ description = "AndroidX Ads Identifier Common"
}
diff --git a/ads/ads-identifier-common/src/main/AndroidManifest.xml b/ads/ads-identifier-common/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..0d3cb05
--- /dev/null
+++ b/ads/ads-identifier-common/src/main/AndroidManifest.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest package="androidx.ads.identifier.common"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+</manifest>
diff --git a/ads/ads-identifier-common/src/main/aidl/androidx/ads/identifier/provider/IAdvertisingIdService.aidl b/ads/ads-identifier-common/src/main/aidl/androidx/ads/identifier/provider/IAdvertisingIdService.aidl
new file mode 100644
index 0000000..c5bdc98
--- /dev/null
+++ b/ads/ads-identifier-common/src/main/aidl/androidx/ads/identifier/provider/IAdvertisingIdService.aidl
@@ -0,0 +1,13 @@
+package androidx.ads.identifier.provider;
+
+/**
+ * The Advertising ID service used to communicate between an Advertising ID Provider and the
+ * developer library.
+ *
+ * <p>The Advertising ID is a resettable identifier used for ads purpose.
+ * @hide
+ */
+interface IAdvertisingIdService {
+ String getId() = 0;
+ boolean isLimitAdTrackingEnabled() = 1;
+}
diff --git a/ads/ads-identifier-common/src/main/java/androidx/ads/identifier/AdvertisingIdUtils.java b/ads/ads-identifier-common/src/main/java/androidx/ads/identifier/AdvertisingIdUtils.java
new file mode 100644
index 0000000..3312079
--- /dev/null
+++ b/ads/ads-identifier-common/src/main/java/androidx/ads/identifier/AdvertisingIdUtils.java
@@ -0,0 +1,173 @@
+/*
+ * 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.ads.identifier;
+
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.Build;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Internal utilities for Advertising ID.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class AdvertisingIdUtils {
+
+ /**
+ * The Intent action used to identify an Advertising ID Provider. The Advertising ID Provider
+ * Service should declare this as an intent-filter, so that clients can find it.
+ */
+ public static final String GET_AD_ID_ACTION = "androidx.ads.identifier.provider.GET_AD_ID";
+
+ /**
+ * The permission used to indicate which Advertising ID Provider should be used in case there
+ * are multiple Advertising ID Providers on the device. Device manufacturer (OEM) should only
+ * grant this permission to the designated Advertising ID Provider.
+ */
+ @VisibleForTesting
+ static final String HIGH_PRIORITY_PERMISSION = "androidx.ads.identifier.provider.HIGH_PRIORITY";
+
+ AdvertisingIdUtils() {
+ }
+
+ /**
+ * Retrieves a list of all Advertising ID Providers' services on this device.
+ *
+ * <p>This is achieved by looking up which services can handle {@link #GET_AD_ID_ACTION}
+ * intent action.
+ * <p>Only system-level providers will be returned.
+ */
+ @NonNull
+ public static List<ResolveInfo> getAdvertisingIdProviderServices(
+ @NonNull PackageManager packageManager) {
+ Intent intent = new Intent(GET_AD_ID_ACTION);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ List<ResolveInfo> resolveInfos =
+ packageManager.queryIntentServices(intent, PackageManager.MATCH_SYSTEM_ONLY);
+ return resolveInfos != null ? resolveInfos : Collections.emptyList();
+ }
+
+ List<ResolveInfo> resolveInfos = packageManager.queryIntentServices(intent, 0);
+ if (resolveInfos == null || resolveInfos.isEmpty()) {
+ return Collections.emptyList();
+ }
+ List<ResolveInfo> systemLevelResolveInfos = new ArrayList<>();
+ for (ResolveInfo resolveInfo : resolveInfos) {
+ ServiceInfo serviceInfo = resolveInfo.serviceInfo;
+ ApplicationInfo applicationInfo;
+ try {
+ applicationInfo = packageManager.getApplicationInfo(serviceInfo.packageName, 0);
+ } catch (PackageManager.NameNotFoundException ignored) {
+ // Ignore this provider if name not found.
+ continue;
+ }
+ if ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ systemLevelResolveInfos.add(resolveInfo);
+ }
+ }
+ return systemLevelResolveInfos;
+ }
+
+ /**
+ * Selects the Service of an Advertising ID Provider which should be used by developer
+ * library when requesting an Advertising ID.
+ *
+ * <p>Note: This method should only be used with the {@link ResolveInfo}s from
+ * {@link #getAdvertisingIdProviderServices} method, this currently means that only
+ * system-level Providers will be selected.
+ * <p>It will return the same Advertising ID Provider for all apps which use the developer
+ * library, using this priority:
+ * <ol>
+ * <li>Providers with {@link #HIGH_PRIORITY_PERMISSION} permission
+ * <li>Other Providers
+ * </ol>
+ * <p>If there are ties in any of the above categories, it will use this priority:
+ * <ol>
+ * <li>First app by earliest install time ({@link PackageInfo#firstInstallTime})
+ * <li>First app by package name alphabetically sorted
+ * </ol>
+ *
+ * @return null if the input {@code resolveInfos} is null or empty, or non of the input
+ * package is found.
+ */
+ @Nullable
+ public static ServiceInfo selectServiceByPriority(
+ @Nullable List<ResolveInfo> resolveInfos, @NonNull PackageManager packageManager) {
+ if (resolveInfos == null || resolveInfos.isEmpty()) {
+ return null;
+ }
+ ServiceInfo selectedServiceInfo = null;
+ PackageInfo selectedPackageInfo = null;
+ for (ResolveInfo resolveInfo : resolveInfos) {
+ ServiceInfo serviceInfo = resolveInfo.serviceInfo;
+ PackageInfo packageInfo;
+ try {
+ packageInfo =
+ packageManager.getPackageInfo(
+ serviceInfo.packageName, PackageManager.GET_PERMISSIONS);
+ } catch (PackageManager.NameNotFoundException ignored) {
+ // Ignore this provider if name not found.
+ continue;
+ }
+ if (selectedPackageInfo == null
+ || hasHigherPriority(packageInfo, selectedPackageInfo)) {
+ selectedServiceInfo = serviceInfo;
+ selectedPackageInfo = packageInfo;
+ }
+ }
+ return selectedServiceInfo;
+ }
+
+ private static boolean hasHigherPriority(PackageInfo candidate, PackageInfo currentHighest) {
+ boolean isCandidateRequestHighPriority = isRequestHighPriority(candidate);
+ boolean isCurrentHighestRequestHighPriority = isRequestHighPriority(currentHighest);
+ if (isCandidateRequestHighPriority != isCurrentHighestRequestHighPriority) {
+ return isCandidateRequestHighPriority;
+ }
+ if (candidate.firstInstallTime != currentHighest.firstInstallTime) {
+ return candidate.firstInstallTime < currentHighest.firstInstallTime;
+ }
+ return candidate.packageName.compareTo(currentHighest.packageName) < 0;
+ }
+
+ private static boolean isRequestHighPriority(PackageInfo packageInfo) {
+ if (packageInfo.requestedPermissions == null) {
+ return false;
+ }
+ for (String permission : packageInfo.requestedPermissions) {
+ if (HIGH_PRIORITY_PERMISSION.equals(permission)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/ads/ads-identifier-common/src/test/java/androidx/ads/identifier/AdvertisingIdUtilsTest.java b/ads/ads-identifier-common/src/test/java/androidx/ads/identifier/AdvertisingIdUtilsTest.java
new file mode 100644
index 0000000..221ee4e
--- /dev/null
+++ b/ads/ads-identifier-common/src/test/java/androidx/ads/identifier/AdvertisingIdUtilsTest.java
@@ -0,0 +1,164 @@
+/*
+ * 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.ads.identifier;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+
+import com.google.common.collect.Lists;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+@RunWith(JUnit4.class)
+public class AdvertisingIdUtilsTest {
+ @Test
+ public void selectServiceByPriority() throws Exception {
+ PackageManager packageManager = mock(PackageManager.class);
+
+ List<ResolveInfo> resolveInfos = Lists.newArrayList(
+ createPackageInfo("c.normal.1", false, 1, packageManager),
+ createPackageInfo("y.normal.0", false, 0, packageManager),
+ createPackageInfo("x.normal.0", false, 0, packageManager),
+ createPackageInfo("z.high.2", true, 2, packageManager));
+
+ List<String> priorityList = getPriorityList(resolveInfos, packageManager);
+
+ assertThat(priorityList).containsExactly(
+ "z.high.2",
+ "x.normal.0",
+ "y.normal.0",
+ "c.normal.1"
+ ).inOrder();
+ }
+
+ @Test
+ public void selectServiceByPriority_firstInstallTime() throws Exception {
+ PackageManager packageManager = mock(PackageManager.class);
+
+ List<ResolveInfo> resolveInfos = Lists.newArrayList(
+ createPackageInfo("com.a", false, 2, packageManager),
+ createPackageInfo("com.b", false, 9, packageManager),
+ createPackageInfo("com.c", false, 7, packageManager),
+ createPackageInfo("com.d", false, 10, packageManager),
+ createPackageInfo("com.e", false, 0, packageManager));
+
+ List<String> priorityList = getPriorityList(resolveInfos, packageManager);
+
+ assertThat(priorityList).containsExactly(
+ "com.e",
+ "com.a",
+ "com.c",
+ "com.b",
+ "com.d"
+ ).inOrder();
+ }
+
+ @Test
+ public void selectServiceByPriority_packageName() throws Exception {
+ PackageManager packageManager = mock(PackageManager.class);
+
+ List<ResolveInfo> resolveInfos = Lists.newArrayList(
+ createPackageInfo("com.abc.id", false, 0, packageManager),
+ createPackageInfo("com.abc", false, 0, packageManager),
+ createPackageInfo("org.example", false, 0, packageManager),
+ createPackageInfo("com.abcde", false, 0, packageManager),
+ createPackageInfo("com.abcde_id", false, 0, packageManager));
+
+ List<String> priorityList = getPriorityList(resolveInfos, packageManager);
+
+ assertThat(priorityList).containsExactly(
+ "com.abc",
+ "com.abc.id",
+ "com.abcde",
+ "com.abcde_id",
+ "org.example"
+ ).inOrder();
+ }
+
+ private List<String> getPriorityList(List<ResolveInfo> resolveInfos,
+ PackageManager packageManager) {
+ List<String> result = new ArrayList<>();
+ while (resolveInfos.size() > 0) {
+ final ServiceInfo serviceInfo =
+ AdvertisingIdUtils.selectServiceByPriority(resolveInfos, packageManager);
+
+ result.add(serviceInfo.packageName);
+
+ resolveInfos.removeIf(resolveInfo -> resolveInfo.serviceInfo == serviceInfo);
+ }
+ return result;
+ }
+
+ @Test
+ public void selectServiceByPriority_inputNull() throws Exception {
+ PackageManager packageManager = mock(PackageManager.class);
+
+ ServiceInfo serviceInfo =
+ AdvertisingIdUtils.selectServiceByPriority(null, packageManager);
+
+ assertThat(serviceInfo).isNull();
+ }
+
+ @Test
+ public void selectServiceByPriority_inputEmpty() throws Exception {
+ PackageManager packageManager = mock(PackageManager.class);
+
+ ServiceInfo serviceInfo =
+ AdvertisingIdUtils.selectServiceByPriority(Collections.emptyList(), packageManager);
+
+ assertThat(serviceInfo).isNull();
+ }
+
+ private ResolveInfo createPackageInfo(String packageName,
+ boolean requestHighPriority, long firstInstallTime, PackageManager packageManager)
+ throws Exception {
+ PackageInfo packageInfo = mock(PackageInfo.class);
+ packageInfo.packageName = packageName;
+ if (requestHighPriority) {
+ packageInfo.requestedPermissions =
+ new String[]{AdvertisingIdUtils.HIGH_PRIORITY_PERMISSION};
+ }
+ packageInfo.firstInstallTime = firstInstallTime;
+
+ mockGetPackageInfo(packageInfo, packageManager);
+
+ ResolveInfo resolveInfo = mock(ResolveInfo.class);
+ resolveInfo.serviceInfo = mock(ServiceInfo.class);
+ resolveInfo.serviceInfo.packageName = packageName;
+ return resolveInfo;
+ }
+
+ private void mockGetPackageInfo(PackageInfo packageInfo, PackageManager packageManager)
+ throws Exception {
+ when(packageManager.getPackageInfo(eq(packageInfo.packageName),
+ eq(PackageManager.GET_PERMISSIONS))).thenReturn(packageInfo);
+ }
+}
diff --git a/ads/ads-identifier-provider/api/1.0.0-alpha01.txt b/ads/ads-identifier-provider/api/1.0.0-alpha01.txt
new file mode 100644
index 0000000..463129f
--- /dev/null
+++ b/ads/ads-identifier-provider/api/1.0.0-alpha01.txt
@@ -0,0 +1,21 @@
+// Signature format: 3.0
+package androidx.ads.identifier.provider {
+
+ public interface AdvertisingIdProvider {
+ method public String getId();
+ method public boolean isLimitAdTrackingEnabled();
+ }
+
+ public abstract class AdvertisingIdProviderInfo {
+ method public abstract String getPackageName();
+ method public abstract android.content.Intent? getSettingsIntent();
+ method public abstract boolean isHighestPriority();
+ }
+
+ public class AdvertisingIdProviderManager {
+ method public static java.util.List<androidx.ads.identifier.provider.AdvertisingIdProviderInfo!> getAdvertisingIdProviders(android.content.Context);
+ method public static void registerProviderCallable(java.util.concurrent.Callable<androidx.ads.identifier.provider.AdvertisingIdProvider!>);
+ }
+
+}
+
diff --git a/ads/ads-identifier-provider/api/current.txt b/ads/ads-identifier-provider/api/current.txt
new file mode 100644
index 0000000..463129f
--- /dev/null
+++ b/ads/ads-identifier-provider/api/current.txt
@@ -0,0 +1,21 @@
+// Signature format: 3.0
+package androidx.ads.identifier.provider {
+
+ public interface AdvertisingIdProvider {
+ method public String getId();
+ method public boolean isLimitAdTrackingEnabled();
+ }
+
+ public abstract class AdvertisingIdProviderInfo {
+ method public abstract String getPackageName();
+ method public abstract android.content.Intent? getSettingsIntent();
+ method public abstract boolean isHighestPriority();
+ }
+
+ public class AdvertisingIdProviderManager {
+ method public static java.util.List<androidx.ads.identifier.provider.AdvertisingIdProviderInfo!> getAdvertisingIdProviders(android.content.Context);
+ method public static void registerProviderCallable(java.util.concurrent.Callable<androidx.ads.identifier.provider.AdvertisingIdProvider!>);
+ }
+
+}
+
diff --git a/benchmark/api/res-1.0.0-alpha01.txt b/ads/ads-identifier-provider/api/res-1.0.0-alpha01.txt
similarity index 100%
copy from benchmark/api/res-1.0.0-alpha01.txt
copy to ads/ads-identifier-provider/api/res-1.0.0-alpha01.txt
diff --git a/ads/ads-identifier-provider/api/restricted_1.0.0-alpha01.txt b/ads/ads-identifier-provider/api/restricted_1.0.0-alpha01.txt
new file mode 100644
index 0000000..463129f
--- /dev/null
+++ b/ads/ads-identifier-provider/api/restricted_1.0.0-alpha01.txt
@@ -0,0 +1,21 @@
+// Signature format: 3.0
+package androidx.ads.identifier.provider {
+
+ public interface AdvertisingIdProvider {
+ method public String getId();
+ method public boolean isLimitAdTrackingEnabled();
+ }
+
+ public abstract class AdvertisingIdProviderInfo {
+ method public abstract String getPackageName();
+ method public abstract android.content.Intent? getSettingsIntent();
+ method public abstract boolean isHighestPriority();
+ }
+
+ public class AdvertisingIdProviderManager {
+ method public static java.util.List<androidx.ads.identifier.provider.AdvertisingIdProviderInfo!> getAdvertisingIdProviders(android.content.Context);
+ method public static void registerProviderCallable(java.util.concurrent.Callable<androidx.ads.identifier.provider.AdvertisingIdProvider!>);
+ }
+
+}
+
diff --git a/ads/ads-identifier-provider/api/restricted_current.txt b/ads/ads-identifier-provider/api/restricted_current.txt
new file mode 100644
index 0000000..463129f
--- /dev/null
+++ b/ads/ads-identifier-provider/api/restricted_current.txt
@@ -0,0 +1,21 @@
+// Signature format: 3.0
+package androidx.ads.identifier.provider {
+
+ public interface AdvertisingIdProvider {
+ method public String getId();
+ method public boolean isLimitAdTrackingEnabled();
+ }
+
+ public abstract class AdvertisingIdProviderInfo {
+ method public abstract String getPackageName();
+ method public abstract android.content.Intent? getSettingsIntent();
+ method public abstract boolean isHighestPriority();
+ }
+
+ public class AdvertisingIdProviderManager {
+ method public static java.util.List<androidx.ads.identifier.provider.AdvertisingIdProviderInfo!> getAdvertisingIdProviders(android.content.Context);
+ method public static void registerProviderCallable(java.util.concurrent.Callable<androidx.ads.identifier.provider.AdvertisingIdProvider!>);
+ }
+
+}
+
diff --git a/ads/ads-identifier-provider/build.gradle b/ads/ads-identifier-provider/build.gradle
new file mode 100644
index 0000000..78b4ade
--- /dev/null
+++ b/ads/ads-identifier-provider/build.gradle
@@ -0,0 +1,59 @@
+/*
+ * 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.
+ */
+
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
+import androidx.build.Publish
+
+plugins {
+ id("AndroidXPlugin")
+ id("com.android.library")
+ id("kotlin-android")
+}
+
+dependencies {
+ implementation("androidx.annotation:annotation:1.1.0")
+ implementation("androidx.core:core:1.1.0-rc01")
+ implementation(AUTO_VALUE_ANNOTATIONS)
+ annotationProcessor(AUTO_VALUE)
+
+ implementation(project(":ads-identifier-common"))
+
+ androidTestImplementation(JUNIT)
+ androidTestImplementation(TRUTH)
+ androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
+ androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
+ androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
+ androidTestImplementation(ANDROIDX_TEST_CORE)
+ androidTestImplementation(ANDROIDX_TEST_RUNNER)
+ androidTestImplementation(ANDROIDX_TEST_RULES)
+}
+
+android {
+ defaultConfig {
+ minSdkVersion 14
+ }
+}
+
+androidx {
+ name = "AndroidX Ads Identifier Provider"
+ publish = Publish.SNAPSHOT_AND_RELEASE
+ mavenVersion = LibraryVersions.ADS_IDENTIFIER
+ mavenGroup = LibraryGroups.ADS
+ inceptionYear = "2019"
+ description = "AndroidX Ads Identifier Provider"
+}
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/Trace.kt b/ads/ads-identifier-provider/integration-tests/testapp/build.gradle
similarity index 61%
copy from compose/compose-runtime/src/main/java/androidx/compose/Trace.kt
copy to ads/ads-identifier-provider/integration-tests/testapp/build.gradle
index e451296..bd185ba 100644
--- a/compose/compose-runtime/src/main/java/androidx/compose/Trace.kt
+++ b/ads/ads-identifier-provider/integration-tests/testapp/build.gradle
@@ -14,19 +14,21 @@
* limitations under the License.
*/
-package androidx.compose
+import static androidx.build.dependencies.DependenciesKt.*
-import android.os.Trace
+plugins {
+ id("AndroidXPlugin")
+ id("com.android.application")
+}
-/**
- * Wrap the specified [block] in calls to [Trace.beginSection] (with the supplied [sectionName])
- * and [Trace.endSection].
- */
-inline fun <T> trace(sectionName: String, block: () -> T): T {
- Trace.beginSection(sectionName)
- try {
- return block()
- } finally {
- Trace.endSection()
+android {
+ defaultConfig {
+ applicationId "androidx.ads.identifier.provider.testapp"
+ minSdkVersion 14
}
-}
\ No newline at end of file
+}
+
+dependencies {
+ implementation(project(":ads-identifier-provider"))
+ implementation("androidx.annotation:annotation:1.1.0")
+}
diff --git a/ads/ads-identifier-provider/integration-tests/testapp/src/main/AndroidManifest.xml b/ads/ads-identifier-provider/integration-tests/testapp/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..259681e
--- /dev/null
+++ b/ads/ads-identifier-provider/integration-tests/testapp/src/main/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="androidx.ads.identifier.provider.testapp">
+
+ <application
+ android:name=".AdsIdentifierProviderApplication"
+ android:allowBackup="false"
+ android:label="@string/app_name"
+ tools:ignore="GoogleAppIndexingWarning,MissingApplicationIcon">
+
+ <activity
+ android:name=".AdsIdentifierProviderActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
diff --git a/ads/ads-identifier-provider/integration-tests/testapp/src/main/java/androidx/ads/identifier/provider/testapp/AdsIdentifierProviderActivity.java b/ads/ads-identifier-provider/integration-tests/testapp/src/main/java/androidx/ads/identifier/provider/testapp/AdsIdentifierProviderActivity.java
new file mode 100644
index 0000000..9de822e
--- /dev/null
+++ b/ads/ads-identifier-provider/integration-tests/testapp/src/main/java/androidx/ads/identifier/provider/testapp/AdsIdentifierProviderActivity.java
@@ -0,0 +1,55 @@
+/*
+ * 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.ads.identifier.provider.testapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.ads.identifier.provider.AdvertisingIdProviderInfo;
+import androidx.ads.identifier.provider.AdvertisingIdProviderManager;
+
+import java.util.List;
+
+/**
+ * Simple activity as an Advertising ID Provider.
+ */
+public class AdsIdentifierProviderActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_ads_identifier_provider);
+ }
+
+ /** Lists all the Advertising ID Providers. */
+ public void listProviders(View view) {
+ TextView textView = findViewById(R.id.text);
+ textView.setText("All providers:\n");
+
+ List<AdvertisingIdProviderInfo> allAdIdProviders =
+ AdvertisingIdProviderManager.getAdvertisingIdProviders(this);
+
+ for (AdvertisingIdProviderInfo providerInfo : allAdIdProviders) {
+ textView.append("Package name: " + providerInfo.getPackageName() + "\n");
+ textView.append("Settings UI intent: " + providerInfo.getSettingsIntent() + "\n");
+ textView.append("Is highest priority: " + providerInfo.isHighestPriority() + "\n");
+ textView.append("\n");
+ }
+ }
+}
diff --git a/ads/ads-identifier-provider/integration-tests/testapp/src/main/java/androidx/ads/identifier/provider/testapp/AdsIdentifierProviderApplication.java b/ads/ads-identifier-provider/integration-tests/testapp/src/main/java/androidx/ads/identifier/provider/testapp/AdsIdentifierProviderApplication.java
new file mode 100644
index 0000000..77e2fea
--- /dev/null
+++ b/ads/ads-identifier-provider/integration-tests/testapp/src/main/java/androidx/ads/identifier/provider/testapp/AdsIdentifierProviderApplication.java
@@ -0,0 +1,35 @@
+/*
+ * 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.ads.identifier.provider.testapp;
+
+import android.app.Application;
+
+import androidx.ads.identifier.provider.AdvertisingIdProviderManager;
+
+/**
+ * This will show you how to make your own Advertising ID Provider for providing the developer
+ * library an Advertising ID when requested by apps.
+ */
+public class AdsIdentifierProviderApplication extends Application {
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ AdvertisingIdProviderManager.registerProviderCallable(SampleAdvertisingIdProvider::new);
+ }
+}
diff --git a/ads/ads-identifier-provider/integration-tests/testapp/src/main/java/androidx/ads/identifier/provider/testapp/SampleAdvertisingIdProvider.java b/ads/ads-identifier-provider/integration-tests/testapp/src/main/java/androidx/ads/identifier/provider/testapp/SampleAdvertisingIdProvider.java
new file mode 100644
index 0000000..fc5c2af
--- /dev/null
+++ b/ads/ads-identifier-provider/integration-tests/testapp/src/main/java/androidx/ads/identifier/provider/testapp/SampleAdvertisingIdProvider.java
@@ -0,0 +1,35 @@
+/*
+ * 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.ads.identifier.provider.testapp;
+
+import androidx.ads.identifier.provider.AdvertisingIdProvider;
+import androidx.annotation.NonNull;
+
+/** An example Advertising ID Provider which always returns same ID. */
+public class SampleAdvertisingIdProvider implements AdvertisingIdProvider {
+
+ @NonNull
+ @Override
+ public String getId() {
+ return "308f629d-c857-4026-8b62-7bdd71caaaaa";
+ }
+
+ @Override
+ public boolean isLimitAdTrackingEnabled() {
+ return false;
+ }
+}
diff --git a/ads/ads-identifier-provider/integration-tests/testapp/src/main/res/layout/activity_ads_identifier_provider.xml b/ads/ads-identifier-provider/integration-tests/testapp/src/main/res/layout/activity_ads_identifier_provider.xml
new file mode 100644
index 0000000..8b76e30
--- /dev/null
+++ b/ads/ads-identifier-provider/integration-tests/testapp/src/main/res/layout/activity_ads_identifier_provider.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+
+<LinearLayout
+ 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"
+ android:orientation="vertical"
+ tools:context="androidx.ads.identifier.provider.testapp.AdsIdentifierProviderActivity">
+
+ <LinearLayout
+ style="?android:attr/buttonBarStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <Button
+ style="?android:attr/buttonBarButtonStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:onClick="listProviders"
+ android:text="@string/list_providers" />
+ </LinearLayout>
+
+ <TextView
+ android:id="@+id/text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="" />
+</LinearLayout>
diff --git a/benchmark/src/androidTest/AndroidManifest.xml b/ads/ads-identifier-provider/integration-tests/testapp/src/main/res/values/strings.xml
similarity index 65%
copy from benchmark/src/androidTest/AndroidManifest.xml
copy to ads/ads-identifier-provider/integration-tests/testapp/src/main/res/values/strings.xml
index f5ec776..57ce496 100644
--- a/benchmark/src/androidTest/AndroidManifest.xml
+++ b/ads/ads-identifier-provider/integration-tests/testapp/src/main/res/values/strings.xml
@@ -14,13 +14,8 @@
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.benchmark.test">
- <application
- android:name="androidx.benchmark.ArgumentInjectingApplication">
- <activity android:name="android.app.Activity"/>
- </application>
-</manifest>
\ No newline at end of file
+<resources>
+ <string name="app_name">Ad ID Provider</string>
+ <string name="list_providers">List Providers</string>
+</resources>
\ No newline at end of file
diff --git a/ads/ads-identifier-provider/src/androidTest/AndroidManifest.xml b/ads/ads-identifier-provider/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000..237644a
--- /dev/null
+++ b/ads/ads-identifier-provider/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<!--
+ 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
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="androidx.ads.identifier.provider.test">
+</manifest>
diff --git a/ads/ads-identifier-provider/src/androidTest/java/androidx/ads/identifier/provider/AdvertisingIdProviderManagerTest.java b/ads/ads-identifier-provider/src/androidTest/java/androidx/ads/identifier/provider/AdvertisingIdProviderManagerTest.java
new file mode 100644
index 0000000..a1e7f9f6
--- /dev/null
+++ b/ads/ads-identifier-provider/src/androidTest/java/androidx/ads/identifier/provider/AdvertisingIdProviderManagerTest.java
@@ -0,0 +1,284 @@
+/*
+ * 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.ads.identifier.provider;
+
+import static androidx.ads.identifier.AdvertisingIdUtils.GET_AD_ID_ACTION;
+import static androidx.ads.identifier.provider.AdvertisingIdProviderManager.OPEN_SETTINGS_ACTION;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.Build;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.google.common.collect.Lists;
+import com.google.common.truth.Correspondence;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.List;
+import java.util.concurrent.Callable;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class AdvertisingIdProviderManagerTest {
+
+ private static final Correspondence<AdvertisingIdProviderInfo, AdvertisingIdProviderInfo>
+ PROVIDER_INFO_EQUALITY = Correspondence.from(
+ AdvertisingIdProviderManagerTest::isProviderInfoEqual, "is equivalent to");
+
+ @Rule
+ public MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+ @Mock
+ private PackageManager mMockPackageManager;
+
+ private Context mContext;
+
+ @Before
+ public void setUp() {
+ Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+ mContext = new ContextWrapper(context) {
+ @Override
+ public PackageManager getPackageManager() {
+ return mMockPackageManager;
+ }
+ };
+ }
+
+ private ResolveInfo createServiceResolveInfo(String packageName) {
+ ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.serviceInfo = new ServiceInfo();
+ resolveInfo.serviceInfo.packageName = packageName;
+ return resolveInfo;
+ }
+
+ private ResolveInfo createActivityResolveInfo(String packageName, String name) {
+ ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.activityInfo = new ActivityInfo();
+ resolveInfo.activityInfo.packageName = packageName;
+ resolveInfo.activityInfo.name = name;
+ return resolveInfo;
+ }
+
+ private void mockQueryIntentServices(List<ResolveInfo> resolveInfos) throws Exception {
+ boolean supportMatchSystemOnly = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
+ int flags = supportMatchSystemOnly ? PackageManager.MATCH_SYSTEM_ONLY : 0;
+ when(mMockPackageManager.queryIntentServices(
+ argThat(intent -> intent != null && GET_AD_ID_ACTION.equals(intent.getAction())),
+ eq(flags))).thenReturn(resolveInfos);
+ for (ResolveInfo resolveInfo : resolveInfos) {
+ if (!supportMatchSystemOnly) {
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
+ when(mMockPackageManager.getApplicationInfo(resolveInfo.serviceInfo.packageName, 0))
+ .thenReturn(applicationInfo);
+ }
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.packageName = resolveInfo.serviceInfo.packageName;
+ when(mMockPackageManager.getPackageInfo(packageInfo.packageName,
+ PackageManager.GET_PERMISSIONS)).thenReturn(packageInfo);
+ }
+ }
+
+ private void mockQueryIntentActivities(List<ResolveInfo> resolveInfos) {
+ when(mMockPackageManager.queryIntentActivities(
+ argThat(intent -> intent != null
+ && OPEN_SETTINGS_ACTION.equals(intent.getAction())),
+ eq(0))).thenReturn(resolveInfos);
+ }
+
+ @Test
+ public void getAllAdIdProviders_onlySelf() throws Exception {
+ mockQueryIntentServices(
+ Lists.newArrayList(createServiceResolveInfo(mContext.getPackageName())));
+
+ assertThat(AdvertisingIdProviderManager.getAdvertisingIdProviders(mContext))
+ .comparingElementsUsing(PROVIDER_INFO_EQUALITY)
+ .containsExactly(
+ AdvertisingIdProviderInfo.builder()
+ .setPackageName(mContext.getPackageName())
+ .setHighestPriority(true)
+ .build());
+ }
+
+ @Test
+ public void getAllAdIdProviders_noProvider() {
+ assertThat(AdvertisingIdProviderManager.getAdvertisingIdProviders(mContext)).isEmpty();
+ }
+
+ @Test
+ public void getAllAdIdProviders() throws Exception {
+ mockQueryIntentServices(
+ Lists.newArrayList(
+ createServiceResolveInfo(mContext.getPackageName()),
+ createServiceResolveInfo("com.a")));
+
+ assertThat(AdvertisingIdProviderManager.getAdvertisingIdProviders(mContext))
+ .comparingElementsUsing(PROVIDER_INFO_EQUALITY)
+ .containsExactly(
+ AdvertisingIdProviderInfo.builder()
+ .setPackageName(mContext.getPackageName())
+ .setHighestPriority(true)
+ .build(),
+ AdvertisingIdProviderInfo.builder().setPackageName("com.a").build());
+ }
+
+ @Test
+ public void getAllAdIdProviders_withOpenIntent() throws Exception {
+ mockQueryIntentServices(
+ Lists.newArrayList(
+ createServiceResolveInfo(mContext.getPackageName()),
+ createServiceResolveInfo("com.a")));
+
+ mockQueryIntentActivities(
+ Lists.newArrayList(
+ createActivityResolveInfo(mContext.getPackageName(), "Activity"),
+ createActivityResolveInfo("com.a", "A")));
+
+ assertThat(AdvertisingIdProviderManager.getAdvertisingIdProviders(mContext))
+ .comparingElementsUsing(PROVIDER_INFO_EQUALITY)
+ .containsExactly(
+ AdvertisingIdProviderInfo.builder()
+ .setPackageName(mContext.getPackageName())
+ .setSettingsIntent(new Intent(OPEN_SETTINGS_ACTION)
+ .setClassName(mContext.getPackageName(), "Activity"))
+ .setHighestPriority(true)
+ .build(),
+ AdvertisingIdProviderInfo.builder()
+ .setPackageName("com.a")
+ .setSettingsIntent(new Intent(OPEN_SETTINGS_ACTION)
+ .setClassName("com.a", "A"))
+ .build());
+ }
+
+ @Test
+ public void getAllAdIdProviders_twoOtherProviders() throws Exception {
+ mockQueryIntentServices(
+ Lists.newArrayList(
+ createServiceResolveInfo(mContext.getPackageName()),
+ createServiceResolveInfo("com.a"),
+ createServiceResolveInfo("com.b")));
+
+ mockQueryIntentActivities(
+ Lists.newArrayList(
+ createActivityResolveInfo(mContext.getPackageName(), "Activity"),
+ createActivityResolveInfo("com.a", "A")));
+
+ assertThat(AdvertisingIdProviderManager.getAdvertisingIdProviders(mContext))
+ .comparingElementsUsing(PROVIDER_INFO_EQUALITY)
+ .containsExactly(
+ AdvertisingIdProviderInfo.builder()
+ .setPackageName(mContext.getPackageName())
+ .setSettingsIntent(new Intent(OPEN_SETTINGS_ACTION)
+ .setClassName(mContext.getPackageName(), "Activity"))
+ .setHighestPriority(true)
+ .build(),
+ AdvertisingIdProviderInfo.builder()
+ .setPackageName("com.a")
+ .setSettingsIntent(new Intent(OPEN_SETTINGS_ACTION)
+ .setClassName("com.a", "A"))
+ .build(),
+ AdvertisingIdProviderInfo.builder()
+ .setPackageName("com.b")
+ .build());
+ }
+
+ @Test
+ public void getAllAdIdProviders_extraOpenIntent() throws Exception {
+ mockQueryIntentServices(
+ Lists.newArrayList(
+ createServiceResolveInfo(mContext.getPackageName()),
+ createServiceResolveInfo("com.a")));
+
+ mockQueryIntentActivities(
+ Lists.newArrayList(
+ createActivityResolveInfo(mContext.getPackageName(), "Activity"),
+ createActivityResolveInfo("com.a", "A"),
+ createActivityResolveInfo("com.b", "B")));
+
+ assertThat(AdvertisingIdProviderManager.getAdvertisingIdProviders(mContext))
+ .comparingElementsUsing(PROVIDER_INFO_EQUALITY)
+ .containsExactly(
+ AdvertisingIdProviderInfo.builder()
+ .setPackageName(mContext.getPackageName())
+ .setSettingsIntent(new Intent(OPEN_SETTINGS_ACTION)
+ .setClassName(mContext.getPackageName(), "Activity"))
+ .setHighestPriority(true)
+ .build(),
+ AdvertisingIdProviderInfo.builder()
+ .setPackageName("com.a")
+ .setSettingsIntent(new Intent(OPEN_SETTINGS_ACTION)
+ .setClassName("com.a", "A"))
+ .build());
+ }
+
+ @Test
+ public void registerProviderCallable() {
+ Callable<AdvertisingIdProvider> providerCallable = () -> null;
+
+ AdvertisingIdProviderManager.registerProviderCallable(providerCallable);
+
+ assertThat(AdvertisingIdProviderManager.getProviderCallable())
+ .isSameInstanceAs(providerCallable);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void registerProviderCallable_null() {
+ AdvertisingIdProviderManager.registerProviderCallable(null);
+ }
+
+ @Test
+ public void clearProviderCallable() {
+ Callable<AdvertisingIdProvider> providerCallable = () -> null;
+
+ AdvertisingIdProviderManager.registerProviderCallable(providerCallable);
+ AdvertisingIdProviderManager.clearProviderCallable();
+
+ assertThat(AdvertisingIdProviderManager.getProviderCallable()).isNull();
+ }
+
+ private static boolean isProviderInfoEqual(
+ AdvertisingIdProviderInfo actual, AdvertisingIdProviderInfo expected) {
+ return actual.getPackageName().equals(expected.getPackageName())
+ && (actual.getSettingsIntent() == null ? expected.getSettingsIntent() == null
+ : actual.getSettingsIntent().filterEquals(expected.getSettingsIntent()))
+ && actual.isHighestPriority() == expected.isHighestPriority();
+ }
+}
diff --git a/ads/ads-identifier-provider/src/androidTest/java/androidx/ads/identifier/provider/internal/AdvertisingIdServiceTest.java b/ads/ads-identifier-provider/src/androidTest/java/androidx/ads/identifier/provider/internal/AdvertisingIdServiceTest.java
new file mode 100644
index 0000000..edacc3e
--- /dev/null
+++ b/ads/ads-identifier-provider/src/androidTest/java/androidx/ads/identifier/provider/internal/AdvertisingIdServiceTest.java
@@ -0,0 +1,163 @@
+/*
+ * 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.ads.identifier.provider.internal;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+
+import androidx.ads.identifier.AdvertisingIdUtils;
+import androidx.ads.identifier.provider.AdvertisingIdProvider;
+import androidx.ads.identifier.provider.AdvertisingIdProviderManager;
+import androidx.ads.identifier.provider.IAdvertisingIdService;
+import androidx.annotation.NonNull;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.concurrent.BlockingDeque;
+import java.util.concurrent.LinkedBlockingDeque;
+import java.util.concurrent.TimeUnit;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class AdvertisingIdServiceTest {
+ private static final String TESTING_AD_ID = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
+
+ @Rule
+ public MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+ private Context mContext;
+ private Intent mIntent;
+ private ServiceConnection mServiceConnection;
+
+ @Before
+ public void setUp() {
+ AdvertisingIdProviderManager.clearProviderCallable();
+
+ mContext = ApplicationProvider.getApplicationContext();
+
+ mIntent = new Intent(AdvertisingIdUtils.GET_AD_ID_ACTION);
+ mIntent.setClassName(mContext.getPackageName(), AdvertisingIdService.class.getName());
+ }
+
+ @After
+ public void tearDown() {
+ if (mServiceConnection != null) {
+ mContext.unbindService(mServiceConnection);
+ }
+ mContext.stopService(mIntent);
+ }
+
+ private IAdvertisingIdService getService() throws InterruptedException {
+ BlockingDeque<IAdvertisingIdService> blockingDeque = new LinkedBlockingDeque<>();
+ mServiceConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
+ blockingDeque.add(IAdvertisingIdService.Stub.asInterface(iBinder));
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName componentName) {
+ }
+ };
+ mContext.bindService(mIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
+ return blockingDeque.poll(1, TimeUnit.SECONDS);
+ }
+
+ @Test
+ public void getId() throws Exception {
+ AdvertisingIdProviderManager.registerProviderCallable(
+ () -> new MockAdvertisingIdProvider(TESTING_AD_ID, true));
+
+ IAdvertisingIdService service = getService();
+
+ assertThat(service.getId()).isEqualTo(TESTING_AD_ID);
+ assertThat(service.isLimitAdTrackingEnabled()).isEqualTo(true);
+ }
+
+ @Test(expected = RuntimeException.class)
+ public void getId_providerThrowsException() throws Exception {
+ AdvertisingIdProviderManager.registerProviderCallable(() -> {
+ MockAdvertisingIdProvider mockAdvertisingIdProvider =
+ new MockAdvertisingIdProvider(TESTING_AD_ID, true);
+ mockAdvertisingIdProvider.mGetIdThrowsException = true;
+ return mockAdvertisingIdProvider;
+ });
+
+ IAdvertisingIdService service = getService();
+ service.getId();
+ }
+
+ private static class MockAdvertisingIdProvider implements AdvertisingIdProvider {
+ private final String mId;
+ private final boolean mLimitAdTrackingEnabled;
+ boolean mGetIdThrowsException = false;
+
+ MockAdvertisingIdProvider(String id, boolean limitAdTrackingEnabled) {
+ mId = id;
+ mLimitAdTrackingEnabled = limitAdTrackingEnabled;
+ }
+
+ @NonNull
+ @Override
+ public String getId() {
+ if (mGetIdThrowsException) {
+ throw new RuntimeException();
+ }
+ return mId;
+ }
+
+ @Override
+ public boolean isLimitAdTrackingEnabled() {
+ return mLimitAdTrackingEnabled;
+ }
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void getId_providerNotRegistered() {
+ AdvertisingIdService.getAdvertisingIdProvider();
+ }
+
+ @Test(expected = RuntimeException.class)
+ public void getId_providerCallableThrowsException() {
+ AdvertisingIdProviderManager.registerProviderCallable(() -> {
+ throw new Exception();
+ });
+
+ AdvertisingIdService.getAdvertisingIdProvider();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void getId_providerCallableReturnsNull() {
+ AdvertisingIdProviderManager.registerProviderCallable(() -> null);
+
+ AdvertisingIdService.getAdvertisingIdProvider();
+ }
+}
diff --git a/ads/ads-identifier-provider/src/main/AndroidManifest.xml b/ads/ads-identifier-provider/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..3a7221b
--- /dev/null
+++ b/ads/ads-identifier-provider/src/main/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="androidx.ads.identifier.provider">
+
+ <application>
+ <service
+ android:name=".internal.AdvertisingIdService"
+ android:enabled="true"
+ android:exported="true"
+ android:visibleToInstantApps="true"
+ tools:ignore="ExportedService">
+ <intent-filter>
+ <action android:name="androidx.ads.identifier.provider.GET_AD_ID" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <meta-data
+ android:name="instantapps.clients.allowed"
+ android:value="true" />
+ </service>
+ </application>
+</manifest>
diff --git a/ads/ads-identifier-provider/src/main/java/androidx/ads/identifier/provider/AdvertisingIdProvider.java b/ads/ads-identifier-provider/src/main/java/androidx/ads/identifier/provider/AdvertisingIdProvider.java
new file mode 100644
index 0000000..682e90a
--- /dev/null
+++ b/ads/ads-identifier-provider/src/main/java/androidx/ads/identifier/provider/AdvertisingIdProvider.java
@@ -0,0 +1,39 @@
+/*
+ * 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.ads.identifier.provider;
+
+import androidx.annotation.NonNull;
+
+/**
+ * The class for the AndroidX Advertising ID Provider that should provide the resettable ID and
+ * LAT preference should implement this interface.
+ *
+ * See {@link AdvertisingIdProviderManager} for more details.
+ *
+ * <p>Note: The implementation of this interface must be completely thread-safe.
+ */
+public interface AdvertisingIdProvider {
+ /**
+ * Retrieves the Advertising ID.
+ * <p>This ID will be normalized to UUID format by the developer library if it isn't already.
+ */
+ @NonNull
+ String getId();
+
+ /** Retrieves whether the user has chosen to limit ad tracking (ads personalization). */
+ boolean isLimitAdTrackingEnabled();
+}
diff --git a/ads/ads-identifier-provider/src/main/java/androidx/ads/identifier/provider/AdvertisingIdProviderInfo.java b/ads/ads-identifier-provider/src/main/java/androidx/ads/identifier/provider/AdvertisingIdProviderInfo.java
new file mode 100644
index 0000000..7a18345
--- /dev/null
+++ b/ads/ads-identifier-provider/src/main/java/androidx/ads/identifier/provider/AdvertisingIdProviderInfo.java
@@ -0,0 +1,81 @@
+/*
+ * 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.ads.identifier.provider;
+
+import android.content.Intent;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.google.auto.value.AutoValue;
+
+/**
+ * A {@link AdvertisingIdProviderInfo} represents the information about an Advertising ID Provider
+ * installed on the device.
+ *
+ * <p>Used in cases when there are multiple Advertising ID Providers on the device. See
+ * {@link AdvertisingIdProviderManager#getAdvertisingIdProviders} for more details.
+ */
+@AutoValue
+public abstract class AdvertisingIdProviderInfo {
+
+ // Create a no-args constructor so it doesn't appear in current.txt
+ AdvertisingIdProviderInfo() {
+ }
+
+ /** Retrieves the Advertising ID Provider package name. */
+ @NonNull
+ public abstract String getPackageName();
+
+ /**
+ * Retrieves the {@link Intent} to open the Advertising ID settings page for a given
+ * Advertising ID Provider.
+ *
+ * <p>This page should allow the user to reset Advertising IDs and change Limit Advertising
+ * Tracking preference.
+ */
+ @Nullable
+ public abstract Intent getSettingsIntent();
+
+ /**
+ * Retrieves whether the provider has the highest priority among all the providers for the
+ * developer library, meaning its provided ID will be used.
+ */
+ public abstract boolean isHighestPriority();
+
+ /** Create a {@link Builder}. */
+ static Builder builder() {
+ return new AutoValue_AdvertisingIdProviderInfo.Builder().setHighestPriority(false);
+ }
+
+ /** The builder for {@link AdvertisingIdProviderInfo}. */
+ @AutoValue.Builder
+ abstract static class Builder {
+
+ // Create a no-args constructor so it doesn't appear in current.txt
+ Builder() {
+ }
+
+ abstract Builder setPackageName(String packageName);
+
+ abstract Builder setSettingsIntent(Intent settingsIntent);
+
+ abstract Builder setHighestPriority(boolean highestPriority);
+
+ abstract AdvertisingIdProviderInfo build();
+ }
+}
diff --git a/ads/ads-identifier-provider/src/main/java/androidx/ads/identifier/provider/AdvertisingIdProviderManager.java b/ads/ads-identifier-provider/src/main/java/androidx/ads/identifier/provider/AdvertisingIdProviderManager.java
new file mode 100644
index 0000000..5899f40
--- /dev/null
+++ b/ads/ads-identifier-provider/src/main/java/androidx/ads/identifier/provider/AdvertisingIdProviderManager.java
@@ -0,0 +1,160 @@
+/*
+ * 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.ads.identifier.provider;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+
+import androidx.ads.identifier.AdvertisingIdUtils;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.VisibleForTesting;
+import androidx.core.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+
+/**
+ * The AdvertisingIdProviderManager will be used by an Advertising ID Provider to register the
+ * provider implementation or retrieve all the Advertising ID Providers on the device.
+ *
+ * This package contains an implementation of the Advertising ID Provider Service that supports
+ * IAdvertisingIdService.aidl for communication with the developer library, allowing you to
+ * easily make your own Advertising ID Provider. Simply do the following:
+ * <ol>
+ * <li>Implement the {@link AdvertisingIdProvider} interface in the provider library. Developer apps
+ * will be interacting with the provider through this programmatic interface.
+ * <li>Register the implementation by calling {@link #registerProviderCallable} within the
+ * provider’s {@link android.app.Application#onCreate} callback.
+ * <li>Register the Advertising Id settings UI with the intent filter
+ * "androidx.ads.identifier.provider.OPEN_SETTINGS".
+ * </ol>
+ */
+public class AdvertisingIdProviderManager {
+
+ @VisibleForTesting
+ static final String OPEN_SETTINGS_ACTION = "androidx.ads.identifier.provider.OPEN_SETTINGS";
+
+ private static Callable<AdvertisingIdProvider> sProviderCallable = null;
+
+ private AdvertisingIdProviderManager() {
+ }
+
+ /**
+ * Registers the {@link Callable} to create an instance of {@link AdvertisingIdProvider}.
+ *
+ * <p>This is used to lazy load the {@link AdvertisingIdProvider} when the Service is started.
+ * <p>This {@link Callable} will be called within the library's built-in Advertising ID
+ * Service's {@link android.app.Service#onCreate} method.
+ * <p>Provider could call this method to register the implementation in
+ * {@link android.app.Application#onCreate}, which is before
+ * {@link android.app.Service#onCreate} has been called.
+ */
+ public static void registerProviderCallable(
+ @NonNull Callable<AdvertisingIdProvider> providerCallable) {
+ sProviderCallable = Preconditions.checkNotNull(providerCallable);
+ }
+
+ /**
+ * Gets the {@link Callable} to create the Advertising ID Provider.
+ *
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @Nullable
+ public static Callable<AdvertisingIdProvider> getProviderCallable() {
+ return sProviderCallable;
+ }
+
+ /** @hide */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @VisibleForTesting
+ public static void clearProviderCallable() {
+ sProviderCallable = null;
+ }
+
+ /**
+ * Retrieves a list of all the Advertising ID Providers' information on this device, including
+ * self and other providers which is based on the AndroidX Advertising ID Provider library.
+ *
+ * <p>This method helps one Advertising ID Provider find other providers. One usage of this is
+ * to link to other providers' settings activity from one provider's settings activity, so the
+ * user of the device can manager all the providers' settings together.
+ */
+ @NonNull
+ public static List<AdvertisingIdProviderInfo> getAdvertisingIdProviders(
+ @NonNull Context context) {
+ PackageManager packageManager = context.getPackageManager();
+ List<ResolveInfo> resolveInfos =
+ AdvertisingIdUtils.getAdvertisingIdProviderServices(packageManager);
+ if (resolveInfos.isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ Map<String, String> activityMap = getOpenSettingsActivities(packageManager);
+ ServiceInfo highestPriorityServiceInfo =
+ AdvertisingIdUtils.selectServiceByPriority(resolveInfos, packageManager);
+
+ List<AdvertisingIdProviderInfo> providerInfos = new ArrayList<>();
+ for (ResolveInfo resolveInfo : resolveInfos) {
+ String packageName = resolveInfo.serviceInfo.packageName;
+
+ AdvertisingIdProviderInfo.Builder builder =
+ AdvertisingIdProviderInfo.builder()
+ .setPackageName(packageName)
+ .setHighestPriority(
+ resolveInfo.serviceInfo == highestPriorityServiceInfo);
+ String activityName = activityMap.get(packageName);
+ if (activityName != null) {
+ builder.setSettingsIntent(
+ new Intent(OPEN_SETTINGS_ACTION).setClassName(packageName, activityName));
+ }
+ providerInfos.add(builder.build());
+ }
+ return providerInfos;
+ }
+
+ /**
+ * Retrieves a {@link Map} from package name to settings activity name.
+ *
+ * <p>This is achieved by looking up which activities can handle {@link #OPEN_SETTINGS_ACTION}
+ * intent action.
+ */
+ private static Map<String, String> getOpenSettingsActivities(PackageManager packageManager) {
+ Intent settingsIntent = new Intent(OPEN_SETTINGS_ACTION);
+ List<ResolveInfo> settingsResolveInfos = packageManager.queryIntentActivities(
+ settingsIntent, 0);
+ if (settingsResolveInfos.isEmpty()) {
+ return Collections.emptyMap();
+ }
+ Map<String, String> activityMap = new HashMap<>();
+ for (ResolveInfo settingsResolveInfo : settingsResolveInfos) {
+ ActivityInfo settingsActivityInfo = settingsResolveInfo.activityInfo;
+ activityMap.put(settingsActivityInfo.packageName, settingsActivityInfo.name);
+ }
+ return activityMap;
+ }
+}
diff --git a/ads/ads-identifier-provider/src/main/java/androidx/ads/identifier/provider/internal/AdvertisingIdAidlServiceImpl.java b/ads/ads-identifier-provider/src/main/java/androidx/ads/identifier/provider/internal/AdvertisingIdAidlServiceImpl.java
new file mode 100644
index 0000000..c12a915
--- /dev/null
+++ b/ads/ads-identifier-provider/src/main/java/androidx/ads/identifier/provider/internal/AdvertisingIdAidlServiceImpl.java
@@ -0,0 +1,43 @@
+/*
+ * 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.ads.identifier.provider.internal;
+
+import androidx.ads.identifier.provider.AdvertisingIdProvider;
+import androidx.ads.identifier.provider.IAdvertisingIdService;
+
+/**
+ * The implementation of the IAdvertisingIdService.aidl which retrieves values from
+ * {@link AdvertisingIdProvider} and replies to the client.
+ */
+class AdvertisingIdAidlServiceImpl extends IAdvertisingIdService.Stub {
+
+ private final AdvertisingIdProvider mProvider;
+
+ AdvertisingIdAidlServiceImpl(AdvertisingIdProvider advertisingIdProvider) {
+ mProvider = advertisingIdProvider;
+ }
+
+ @Override
+ public String getId() {
+ return mProvider.getId();
+ }
+
+ @Override
+ public boolean isLimitAdTrackingEnabled() {
+ return mProvider.isLimitAdTrackingEnabled();
+ }
+}
diff --git a/ads/ads-identifier-provider/src/main/java/androidx/ads/identifier/provider/internal/AdvertisingIdService.java b/ads/ads-identifier-provider/src/main/java/androidx/ads/identifier/provider/internal/AdvertisingIdService.java
new file mode 100644
index 0000000..45ad580
--- /dev/null
+++ b/ads/ads-identifier-provider/src/main/java/androidx/ads/identifier/provider/internal/AdvertisingIdService.java
@@ -0,0 +1,68 @@
+/*
+ * 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.ads.identifier.provider.internal;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+import androidx.ads.identifier.provider.AdvertisingIdProvider;
+import androidx.ads.identifier.provider.AdvertisingIdProviderManager;
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
+import java.util.concurrent.Callable;
+
+/**
+ * The internal service of AndroidX Advertising ID Provider library to provide the Advertising ID.
+ */
+public class AdvertisingIdService extends Service {
+
+ private AdvertisingIdAidlServiceImpl mAdvertisingIdAidlServiceImpl;
+
+ @Override
+ public void onCreate() {
+ mAdvertisingIdAidlServiceImpl =
+ new AdvertisingIdAidlServiceImpl(getAdvertisingIdProvider());
+ }
+
+ @Override
+ @NonNull
+ public IBinder onBind(@NonNull Intent intent) {
+ return mAdvertisingIdAidlServiceImpl;
+ }
+
+ @VisibleForTesting
+ @NonNull
+ static AdvertisingIdProvider getAdvertisingIdProvider() {
+ Callable<AdvertisingIdProvider> providerCallable =
+ AdvertisingIdProviderManager.getProviderCallable();
+ if (providerCallable == null) {
+ throw new IllegalStateException("Advertising ID Provider not registered.");
+ }
+ AdvertisingIdProvider advertisingIdProvider;
+ try {
+ advertisingIdProvider = providerCallable.call();
+ } catch (Exception e) {
+ throw new RuntimeException("Could not fetch the Advertising ID Provider.", e);
+ }
+ if (advertisingIdProvider == null) {
+ throw new IllegalArgumentException("Fetched Advertising ID Provider is null.");
+ }
+ return advertisingIdProvider;
+ }
+}
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/JoinedKey.kt b/ads/ads-identifier-provider/src/main/java/androidx/ads/identifier/provider/internal/package-info.java
similarity index 75%
copy from compose/compose-runtime/src/main/java/androidx/compose/JoinedKey.kt
copy to ads/ads-identifier-provider/src/main/java/androidx/ads/identifier/provider/internal/package-info.java
index 601fbf1..9ffd5e1 100644
--- a/compose/compose-runtime/src/main/java/androidx/compose/JoinedKey.kt
+++ b/ads/ads-identifier-provider/src/main/java/androidx/ads/identifier/provider/internal/package-info.java
@@ -14,9 +14,12 @@
* limitations under the License.
*/
-package androidx.compose
+/**
+ * @hide
+ */
+@RestrictTo(LIBRARY)
+package androidx.ads.identifier.provider.internal;
-internal data class JoinedKey(
- @JvmField val left: Any?,
- @JvmField val right: Any?
-)
\ No newline at end of file
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
+
+import androidx.annotation.RestrictTo;
diff --git a/ads/ads-identifier/api/1.0.0-alpha01.txt b/ads/ads-identifier/api/1.0.0-alpha01.txt
index da4f6cc..20ed609 100644
--- a/ads/ads-identifier/api/1.0.0-alpha01.txt
+++ b/ads/ads-identifier/api/1.0.0-alpha01.txt
@@ -1 +1,21 @@
// Signature format: 3.0
+package androidx.ads.identifier {
+
+ public class AdvertisingIdClient {
+ method public static com.google.common.util.concurrent.ListenableFuture<androidx.ads.identifier.AdvertisingIdInfo!> getAdvertisingIdInfo(android.content.Context);
+ method public static boolean isAdvertisingIdProviderAvailable(android.content.Context);
+ }
+
+ public abstract class AdvertisingIdInfo {
+ method public abstract String getId();
+ method public abstract String getProviderPackageName();
+ method public abstract boolean isLimitAdTrackingEnabled();
+ }
+
+ public class AdvertisingIdNotAvailableException extends java.lang.Exception {
+ ctor public AdvertisingIdNotAvailableException(String);
+ ctor public AdvertisingIdNotAvailableException(String, Throwable);
+ }
+
+}
+
diff --git a/ads/ads-identifier/api/current.txt b/ads/ads-identifier/api/current.txt
index da4f6cc..20ed609 100644
--- a/ads/ads-identifier/api/current.txt
+++ b/ads/ads-identifier/api/current.txt
@@ -1 +1,21 @@
// Signature format: 3.0
+package androidx.ads.identifier {
+
+ public class AdvertisingIdClient {
+ method public static com.google.common.util.concurrent.ListenableFuture<androidx.ads.identifier.AdvertisingIdInfo!> getAdvertisingIdInfo(android.content.Context);
+ method public static boolean isAdvertisingIdProviderAvailable(android.content.Context);
+ }
+
+ public abstract class AdvertisingIdInfo {
+ method public abstract String getId();
+ method public abstract String getProviderPackageName();
+ method public abstract boolean isLimitAdTrackingEnabled();
+ }
+
+ public class AdvertisingIdNotAvailableException extends java.lang.Exception {
+ ctor public AdvertisingIdNotAvailableException(String);
+ ctor public AdvertisingIdNotAvailableException(String, Throwable);
+ }
+
+}
+
diff --git a/ads/ads-identifier/api/restricted_1.0.0-alpha01.txt b/ads/ads-identifier/api/restricted_1.0.0-alpha01.txt
index da4f6cc..20ed609 100644
--- a/ads/ads-identifier/api/restricted_1.0.0-alpha01.txt
+++ b/ads/ads-identifier/api/restricted_1.0.0-alpha01.txt
@@ -1 +1,21 @@
// Signature format: 3.0
+package androidx.ads.identifier {
+
+ public class AdvertisingIdClient {
+ method public static com.google.common.util.concurrent.ListenableFuture<androidx.ads.identifier.AdvertisingIdInfo!> getAdvertisingIdInfo(android.content.Context);
+ method public static boolean isAdvertisingIdProviderAvailable(android.content.Context);
+ }
+
+ public abstract class AdvertisingIdInfo {
+ method public abstract String getId();
+ method public abstract String getProviderPackageName();
+ method public abstract boolean isLimitAdTrackingEnabled();
+ }
+
+ public class AdvertisingIdNotAvailableException extends java.lang.Exception {
+ ctor public AdvertisingIdNotAvailableException(String);
+ ctor public AdvertisingIdNotAvailableException(String, Throwable);
+ }
+
+}
+
diff --git a/ads/ads-identifier/api/restricted_current.txt b/ads/ads-identifier/api/restricted_current.txt
index da4f6cc..20ed609 100644
--- a/ads/ads-identifier/api/restricted_current.txt
+++ b/ads/ads-identifier/api/restricted_current.txt
@@ -1 +1,21 @@
// Signature format: 3.0
+package androidx.ads.identifier {
+
+ public class AdvertisingIdClient {
+ method public static com.google.common.util.concurrent.ListenableFuture<androidx.ads.identifier.AdvertisingIdInfo!> getAdvertisingIdInfo(android.content.Context);
+ method public static boolean isAdvertisingIdProviderAvailable(android.content.Context);
+ }
+
+ public abstract class AdvertisingIdInfo {
+ method public abstract String getId();
+ method public abstract String getProviderPackageName();
+ method public abstract boolean isLimitAdTrackingEnabled();
+ }
+
+ public class AdvertisingIdNotAvailableException extends java.lang.Exception {
+ ctor public AdvertisingIdNotAvailableException(String);
+ ctor public AdvertisingIdNotAvailableException(String, Throwable);
+ }
+
+}
+
diff --git a/ads/ads-identifier/build.gradle b/ads/ads-identifier/build.gradle
index e3a1410..3eca9f2 100644
--- a/ads/ads-identifier/build.gradle
+++ b/ads/ads-identifier/build.gradle
@@ -26,7 +26,23 @@
}
dependencies {
+ implementation("androidx.annotation:annotation:1.1.0")
+ implementation("androidx.core:core:1.1.0-rc01")
+ implementation(AUTO_VALUE_ANNOTATIONS)
+ annotationProcessor(AUTO_VALUE)
+ api(GUAVA_LISTENABLE_FUTURE)
+ implementation("androidx.concurrent:concurrent-futures:1.0.0-beta01")
+ implementation(project(":ads-identifier-common"))
+
+ androidTestImplementation(JUNIT)
+ androidTestImplementation(TRUTH)
+ androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
+ androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
+ androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
+ androidTestImplementation(ANDROIDX_TEST_CORE)
+ androidTestImplementation(ANDROIDX_TEST_RUNNER)
+ androidTestImplementation(ANDROIDX_TEST_RULES)
}
android {
@@ -37,7 +53,7 @@
androidx {
name = "AndroidX Ads Identifier"
- publish = Publish.NONE
+ publish = Publish.SNAPSHOT_AND_RELEASE
mavenVersion = LibraryVersions.ADS_IDENTIFIER
mavenGroup = LibraryGroups.ADS
inceptionYear = "2019"
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/Trace.kt b/ads/ads-identifier/integration-tests/testapp/build.gradle
similarity index 60%
copy from compose/compose-runtime/src/main/java/androidx/compose/Trace.kt
copy to ads/ads-identifier/integration-tests/testapp/build.gradle
index e451296..c0fd225 100644
--- a/compose/compose-runtime/src/main/java/androidx/compose/Trace.kt
+++ b/ads/ads-identifier/integration-tests/testapp/build.gradle
@@ -14,19 +14,22 @@
* limitations under the License.
*/
-package androidx.compose
+import static androidx.build.dependencies.DependenciesKt.*
-import android.os.Trace
+plugins {
+ id("AndroidXPlugin")
+ id("com.android.application")
+}
-/**
- * Wrap the specified [block] in calls to [Trace.beginSection] (with the supplied [sectionName])
- * and [Trace.endSection].
- */
-inline fun <T> trace(sectionName: String, block: () -> T): T {
- Trace.beginSection(sectionName)
- try {
- return block()
- } finally {
- Trace.endSection()
+android {
+ defaultConfig {
+ applicationId "androidx.ads.identifier.testapp"
+ minSdkVersion 14
}
-}
\ No newline at end of file
+}
+
+dependencies {
+ implementation(project(":ads-identifier"))
+ implementation(project(":ads-identifier-common"))
+ implementation(GUAVA_ANDROID)
+}
diff --git a/ads/ads-identifier/integration-tests/testapp/src/main/AndroidManifest.xml b/ads/ads-identifier/integration-tests/testapp/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..b9efcc2
--- /dev/null
+++ b/ads/ads-identifier/integration-tests/testapp/src/main/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="androidx.ads.identifier.testapp">
+
+ <application
+ android:allowBackup="false"
+ android:label="@string/app_name"
+ tools:ignore="GoogleAppIndexingWarning,MissingApplicationIcon">
+ <activity
+ android:name=".AdsIdentifierActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
\ No newline at end of file
diff --git a/ads/ads-identifier/integration-tests/testapp/src/main/java/androidx/ads/identifier/testapp/AdsIdentifierActivity.java b/ads/ads-identifier/integration-tests/testapp/src/main/java/androidx/ads/identifier/testapp/AdsIdentifierActivity.java
new file mode 100644
index 0000000..037200b
--- /dev/null
+++ b/ads/ads-identifier/integration-tests/testapp/src/main/java/androidx/ads/identifier/testapp/AdsIdentifierActivity.java
@@ -0,0 +1,146 @@
+/*
+ * 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.ads.identifier.testapp;
+
+import android.app.Activity;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+import android.text.format.DateFormat;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.ads.identifier.AdvertisingIdClient;
+import androidx.ads.identifier.AdvertisingIdInfo;
+import androidx.ads.identifier.AdvertisingIdUtils;
+
+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 java.util.List;
+import java.util.Locale;
+import java.util.concurrent.ExecutionException;
+
+/**
+ * Simple activity as an ads identifier developer.
+ */
+public class AdsIdentifierActivity extends Activity {
+
+ private static final String HIGH_PRIORITY_PERMISSION =
+ "androidx.ads.identifier.provider.HIGH_PRIORITY";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_ads_identifier);
+ }
+
+ /** Gets Advertising ID. */
+ public void getId(View view) {
+ TextView textView = findViewById(R.id.text);
+ ListenableFuture<AdvertisingIdInfo> advertisingIdInfoListenableFuture =
+ AdvertisingIdClient.getAdvertisingIdInfo(getApplicationContext());
+ Futures.addCallback(advertisingIdInfoListenableFuture,
+ new FutureCallback<AdvertisingIdInfo>() {
+ @Override
+ public void onSuccess(AdvertisingIdInfo advertisingIdInfo) {
+ runOnUiThread(() -> textView.setText(advertisingIdInfo.toString()));
+ }
+
+ @Override
+ public void onFailure(Throwable throwable) {
+ runOnUiThread(() -> textView.setText(throwable.toString()));
+ }
+ }, MoreExecutors.directExecutor());
+ }
+
+ /** Gets Advertising ID synchronously. */
+ public void getIdSync(View view) {
+ TextView textView = findViewById(R.id.text);
+ new Thread(() -> {
+ AdvertisingIdInfo advertisingIdInfo;
+ try {
+ advertisingIdInfo =
+ AdvertisingIdClient.getAdvertisingIdInfo(getApplicationContext()).get();
+ } catch (ExecutionException e) {
+ Throwable cause = e.getCause() != null ? e.getCause() : e;
+ runOnUiThread(() -> textView.setText(cause.toString()));
+ return;
+
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ runOnUiThread(() -> textView.setText(e.toString()));
+ return;
+ }
+ runOnUiThread(() -> textView.setText(advertisingIdInfo.toString()));
+ }).start();
+ }
+
+ /** Checks is provider available. */
+ public void isProviderAvailable(View view) {
+ TextView textView = findViewById(R.id.text);
+ boolean isAvailable = AdvertisingIdClient.isAdvertisingIdProviderAvailable(this);
+ textView.setText(String.valueOf(isAvailable));
+ }
+
+ /** Lists all the providers. */
+ public void listProvider(View view) {
+ TextView textView = findViewById(R.id.text);
+ textView.setText("Services:\n");
+
+ List<ResolveInfo> resolveInfos =
+ AdvertisingIdUtils.getAdvertisingIdProviderServices(getPackageManager());
+ for (ResolveInfo resolveInfo : resolveInfos) {
+ String packageName = resolveInfo.serviceInfo.packageName;
+ PackageInfo packageInfo;
+ try {
+ packageInfo = getPackageManager().getPackageInfo(packageName,
+ PackageManager.GET_PERMISSIONS);
+ } catch (PackageManager.NameNotFoundException e) {
+ continue;
+ }
+ show(textView, packageInfo);
+ }
+ }
+
+ private void show(TextView textView, PackageInfo packageInfo) {
+ textView.append(String.format(Locale.US, "%s\nFLAG_SYSTEM:%d\n",
+ packageInfo.packageName,
+ packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM));
+ textView.append(String.format(Locale.US, "isRequestHighPriority:%s\n",
+ isRequestHighPriority(packageInfo.requestedPermissions)));
+ textView.append(String.format(Locale.US, "firstInstallTime:%s\n",
+ DateFormat.format("yyyy-MM-dd HH:mm:ss", packageInfo.firstInstallTime)));
+ textView.append("\n");
+ }
+
+ private static boolean isRequestHighPriority(String[] array) {
+ if (array == null) {
+ return false;
+ }
+ for (String permission : array) {
+ if (HIGH_PRIORITY_PERMISSION.equals(permission)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/ads/ads-identifier/integration-tests/testapp/src/main/res/layout/activity_ads_identifier.xml b/ads/ads-identifier/integration-tests/testapp/src/main/res/layout/activity_ads_identifier.xml
new file mode 100644
index 0000000..0f4f3ea
--- /dev/null
+++ b/ads/ads-identifier/integration-tests/testapp/src/main/res/layout/activity_ads_identifier.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+
+<LinearLayout
+ 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"
+ android:orientation="vertical"
+ tools:context="androidx.ads.identifier.testapp.AdsIdentifierActivity">
+
+ <LinearLayout
+ style="?android:attr/buttonBarStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <Button
+ style="?android:attr/buttonBarButtonStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:onClick="getId"
+ android:text="@string/get_ad_id" />
+
+ <Button
+ style="?android:attr/buttonBarButtonStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:onClick="getIdSync"
+ android:text="@string/get_ad_id_sync" />
+ </LinearLayout>
+
+ <LinearLayout
+ style="?android:attr/buttonBarStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <Button
+ style="?android:attr/buttonBarButtonStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:onClick="isProviderAvailable"
+ android:text="@string/is_provider_available" />
+
+ <Button
+ style="?android:attr/buttonBarButtonStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:onClick="listProvider"
+ android:text="@string/list_provider" />
+ </LinearLayout>
+
+ <TextView
+ android:id="@+id/text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="" />
+</LinearLayout>
diff --git a/benchmark/src/androidTest/AndroidManifest.xml b/ads/ads-identifier/integration-tests/testapp/src/main/res/values/strings.xml
similarity index 65%
copy from benchmark/src/androidTest/AndroidManifest.xml
copy to ads/ads-identifier/integration-tests/testapp/src/main/res/values/strings.xml
index f5ec776..4d7dca0 100644
--- a/benchmark/src/androidTest/AndroidManifest.xml
+++ b/ads/ads-identifier/integration-tests/testapp/src/main/res/values/strings.xml
@@ -14,13 +14,11 @@
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.benchmark.test">
- <application
- android:name="androidx.benchmark.ArgumentInjectingApplication">
- <activity android:name="android.app.Activity"/>
- </application>
-</manifest>
\ No newline at end of file
+<resources>
+ <string name="app_name">Ad ID</string>
+ <string name="get_ad_id">Get Ad ID</string>
+ <string name="get_ad_id_sync">Get Ad ID (Sync)</string>
+ <string name="list_provider">List Providers</string>
+ <string name="is_provider_available">Is Provider Available</string>
+</resources>
\ No newline at end of file
diff --git a/ads/ads-identifier/src/androidTest/AndroidManifest.xml b/ads/ads-identifier/src/androidTest/AndroidManifest.xml
index f5c3bd7..b4a88ac 100644
--- a/ads/ads-identifier/src/androidTest/AndroidManifest.xml
+++ b/ads/ads-identifier/src/androidTest/AndroidManifest.xml
@@ -15,5 +15,32 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="androidx.ads.identifier.tests">
+ xmlns:tools="http://schemas.android.com/tools"
+ package="androidx.ads.identifier.test">
+
+ <application>
+ <service
+ android:name="androidx.ads.identifier.MockAdvertisingIdService"
+ android:enabled="true"
+ android:exported="true"
+ android:process=":test"
+ tools:ignore="ExportedService">
+ <intent-filter>
+ <action android:name="androidx.ads.identifier.provider.GET_AD_ID" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </service>
+
+ <service
+ android:name="androidx.ads.identifier.MockAdvertisingIdThrowsNpeService"
+ android:enabled="true"
+ android:exported="true"
+ android:process=":test"
+ tools:ignore="ExportedService">
+ <intent-filter>
+ <action android:name="androidx.ads.identifier.provider.GET_AD_ID" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </service>
+ </application>
</manifest>
diff --git a/ads/ads-identifier/src/androidTest/java/androidx/ads/identifier/AdvertisingIdClientTest.java b/ads/ads-identifier/src/androidTest/java/androidx/ads/identifier/AdvertisingIdClientTest.java
new file mode 100644
index 0000000..13706d1
--- /dev/null
+++ b/ads/ads-identifier/src/androidTest/java/androidx/ads/identifier/AdvertisingIdClientTest.java
@@ -0,0 +1,323 @@
+/*
+ * 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.ads.identifier;
+
+import static androidx.ads.identifier.AdvertisingIdUtils.GET_AD_ID_ACTION;
+import static androidx.ads.identifier.MockAdvertisingIdService.TESTING_AD_ID;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.Build;
+
+import androidx.ads.identifier.internal.BlockingServiceConnection;
+import androidx.ads.identifier.provider.IAdvertisingIdService;
+import androidx.annotation.NonNull;
+import androidx.concurrent.futures.CallbackToFutureAdapter;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+
+import com.google.common.collect.Lists;
+import com.google.common.util.concurrent.ListenableFuture;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class AdvertisingIdClientTest {
+ private static final String MOCK_SERVICE_PACKAGE_NAME = "androidx.ads.identifier.test";
+ private static final String MOCK_SERVICE_NAME = MockAdvertisingIdService.class.getName();
+ private static final String MOCK_THROWS_NPE_SERVICE_NAME =
+ MockAdvertisingIdThrowsNpeService.class.getName();
+
+ @Rule
+ public MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+ @Mock
+ private PackageManager mMockPackageManager;
+
+ private Context mContext;
+
+ @Before
+ public void setUp() throws Exception {
+ MockAdvertisingIdClient.sGetServiceConnectionThrowException = false;
+ MockAdvertisingIdClient.sGetAdvertisingIdServiceThrowInterruptedException = false;
+
+ Context applicationContext = ApplicationProvider.getApplicationContext();
+
+ mContext = new ContextWrapper(applicationContext) {
+ @Override
+ public Context getApplicationContext() {
+ return this;
+ }
+
+ @Override
+ public PackageManager getPackageManager() {
+ return mMockPackageManager;
+ }
+ };
+
+ mockQueryIntentServices(Lists.newArrayList(
+ createResolveInfo(MOCK_SERVICE_PACKAGE_NAME, MOCK_SERVICE_NAME)));
+ }
+
+ @After
+ public void tearDown() {
+ Intent serviceIntent = new Intent(GET_AD_ID_ACTION);
+ serviceIntent.setClassName(MOCK_SERVICE_PACKAGE_NAME, MOCK_SERVICE_NAME);
+ mContext.stopService(serviceIntent);
+
+ Intent npeServiceIntent = new Intent(GET_AD_ID_ACTION);
+ npeServiceIntent.setClassName(MOCK_SERVICE_PACKAGE_NAME, MOCK_THROWS_NPE_SERVICE_NAME);
+ mContext.stopService(npeServiceIntent);
+ }
+
+ private void mockQueryIntentServices(List<ResolveInfo> resolveInfos) throws Exception {
+ boolean supportMatchSystemOnly = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
+ int flags = supportMatchSystemOnly ? PackageManager.MATCH_SYSTEM_ONLY : 0;
+ when(mMockPackageManager.queryIntentServices(
+ argThat(intent -> intent != null && GET_AD_ID_ACTION.equals(intent.getAction())),
+ eq(flags))).thenReturn(resolveInfos);
+ for (ResolveInfo resolveInfo : resolveInfos) {
+ if (!supportMatchSystemOnly) {
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
+ when(mMockPackageManager.getApplicationInfo(resolveInfo.serviceInfo.packageName, 0))
+ .thenReturn(applicationInfo);
+ }
+ }
+ }
+
+ @Test
+ public void getAdvertisingIdInfo() throws Exception {
+ AdvertisingIdInfo info = AdvertisingIdClient.getAdvertisingIdInfo(mContext).get();
+
+ assertThat(info).isEqualTo(AdvertisingIdInfo.builder()
+ .setId(TESTING_AD_ID)
+ .setLimitAdTrackingEnabled(true)
+ .setProviderPackageName(MOCK_SERVICE_PACKAGE_NAME)
+ .build());
+ }
+
+ public void getAdvertisingIdInfo_noProvider() throws Exception {
+ mockQueryIntentServices(Collections.emptyList());
+
+ try {
+ AdvertisingIdClient.getAdvertisingIdInfo(mContext).get();
+ } catch (ExecutionException e) {
+ assertThat(e).hasCauseThat().isInstanceOf(AdvertisingIdNotAvailableException.class);
+ return;
+ }
+ fail("Expected ExecutionException");
+ }
+
+ @Test
+ public void getAdvertisingIdInfo_serviceThrowsNullPointerException() throws Exception {
+ mockQueryIntentServices(Lists.newArrayList(
+ createResolveInfo(MOCK_SERVICE_PACKAGE_NAME, MOCK_THROWS_NPE_SERVICE_NAME)));
+
+ try {
+ AdvertisingIdClient.getAdvertisingIdInfo(mContext).get();
+ } catch (ExecutionException e) {
+ assertThat(e).hasCauseThat().isInstanceOf(AdvertisingIdNotAvailableException.class);
+ return;
+ }
+ fail("Expected ExecutionException");
+ }
+
+ @Test
+ public void getInfo_getInfoTwice() throws Exception {
+ AdvertisingIdClient client = new AdvertisingIdClient(mContext);
+ client.getInfoInternal();
+ AdvertisingIdInfo info = client.getInfoInternal();
+ client.finish();
+
+ assertThat(info).isEqualTo(AdvertisingIdInfo.builder()
+ .setId(TESTING_AD_ID)
+ .setLimitAdTrackingEnabled(true)
+ .setProviderPackageName(MOCK_SERVICE_PACKAGE_NAME)
+ .build());
+ }
+
+ @Test
+ public void getInfo_twoClients() throws Exception {
+ AdvertisingIdClient client1 = new AdvertisingIdClient(mContext);
+ AdvertisingIdClient client2 = new AdvertisingIdClient(mContext);
+ AdvertisingIdInfo info1 = client1.getInfoInternal();
+ AdvertisingIdInfo info2 = client1.getInfoInternal();
+ client1.finish();
+ client2.finish();
+
+ AdvertisingIdInfo expected = AdvertisingIdInfo.builder()
+ .setId(TESTING_AD_ID)
+ .setLimitAdTrackingEnabled(true)
+ .setProviderPackageName(MOCK_SERVICE_PACKAGE_NAME)
+ .build();
+ assertThat(info1).isEqualTo(expected);
+ assertThat(info2).isEqualTo(expected);
+ }
+
+ @Test(timeout = 11000L)
+ public void getAdvertisingIdInfo_connectionTimeout() throws Exception {
+ try {
+ MockAdvertisingIdClient.getAdvertisingIdInfo(mContext).get();
+ } catch (ExecutionException e) {
+ assertThat(e).hasCauseThat().isInstanceOf(TimeoutException.class);
+ return;
+ }
+ fail("Expected ExecutionException");
+ }
+
+ @Test
+ public void getAdvertisingIdInfo_interrupted() throws Exception {
+ MockAdvertisingIdClient.sGetAdvertisingIdServiceThrowInterruptedException = true;
+
+ try {
+ MockAdvertisingIdClient.getAdvertisingIdInfo(mContext).get();
+ } catch (ExecutionException e) {
+ assertThat(e).hasCauseThat().isInstanceOf(InterruptedException.class);
+ return;
+ }
+ fail("Expected ExecutionException");
+ }
+
+ @Test
+ public void getAdvertisingIdInfo_connectionFailed() throws Exception {
+ MockAdvertisingIdClient.sGetServiceConnectionThrowException = true;
+
+ try {
+ MockAdvertisingIdClient.getAdvertisingIdInfo(mContext).get();
+ } catch (ExecutionException e) {
+ assertThat(e).hasCauseThat().isInstanceOf(IOException.class);
+ return;
+ }
+ fail("Expected ExecutionException");
+ }
+
+ private static class MockAdvertisingIdClient extends AdvertisingIdClient {
+
+ static boolean sGetServiceConnectionThrowException = false;
+ static boolean sGetAdvertisingIdServiceThrowInterruptedException = false;
+
+ static Thread sCurrentThread;
+
+ MockAdvertisingIdClient(Context context) {
+ super(context);
+ }
+
+ @Override
+ BlockingServiceConnection getServiceConnection() throws IOException {
+ if (sGetServiceConnectionThrowException) {
+ throw new IOException();
+ }
+
+ sCurrentThread = Thread.currentThread();
+
+ // This connection does not bind to any service, so it always timeout.
+ return new BlockingServiceConnection();
+ }
+
+ @Override
+ IAdvertisingIdService getAdvertisingIdService(BlockingServiceConnection bsc)
+ throws TimeoutException, InterruptedException {
+ if (sGetAdvertisingIdServiceThrowInterruptedException) {
+ throw new InterruptedException();
+ }
+ return super.getAdvertisingIdService(bsc);
+ }
+
+ @NonNull
+ public static ListenableFuture<AdvertisingIdInfo> getAdvertisingIdInfo(
+ @NonNull Context context) {
+ return CallbackToFutureAdapter.getFuture(completer -> {
+ EXECUTOR_SERVICE.execute(() -> {
+ MockAdvertisingIdClient client = new MockAdvertisingIdClient(context);
+ try {
+ completer.set(client.getInfoInternal());
+ } catch (IOException | AdvertisingIdNotAvailableException | TimeoutException
+ | InterruptedException e) {
+ completer.setException(e);
+ }
+ // No need to call unbindService() here since not call bindService() in this
+ // mock.
+ });
+ return "getAdvertisingIdInfo";
+ });
+ }
+ }
+
+ @Test
+ public void normalizeId() throws Exception {
+ String id = AdvertisingIdClient.normalizeId("abc");
+
+ assertThat(id).isEqualTo("90015098-3cd2-3fb0-9696-3f7d28e17f72"); // UUID version 3 of "abc"
+ }
+
+ @Test
+ public void isAdvertisingIdProviderAvailable() {
+ assertThat(AdvertisingIdClient.isAdvertisingIdProviderAvailable(mContext)).isTrue();
+ }
+
+ @Test
+ public void isAdvertisingIdProviderAvailable_noProvider() throws Exception {
+ mockQueryIntentServices(Collections.emptyList());
+
+ assertThat(AdvertisingIdClient.isAdvertisingIdProviderAvailable(mContext)).isFalse();
+ }
+
+ @Test
+ public void isAdvertisingIdProviderAvailable_twoProviders() throws Exception {
+ mockQueryIntentServices(Lists.newArrayList(
+ createResolveInfo("com.a", "A"),
+ createResolveInfo("com.b", "B")));
+
+ assertThat(AdvertisingIdClient.isAdvertisingIdProviderAvailable(mContext)).isTrue();
+ }
+
+ private static ResolveInfo createResolveInfo(String packageName, String name) {
+ ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.serviceInfo = new ServiceInfo();
+ resolveInfo.serviceInfo.packageName = packageName;
+ resolveInfo.serviceInfo.name = name;
+ return resolveInfo;
+ }
+}
diff --git a/ads/ads-identifier/src/androidTest/java/androidx/ads/identifier/MockAdvertisingIdService.java b/ads/ads-identifier/src/androidTest/java/androidx/ads/identifier/MockAdvertisingIdService.java
new file mode 100644
index 0000000..24bdf9b
--- /dev/null
+++ b/ads/ads-identifier/src/androidTest/java/androidx/ads/identifier/MockAdvertisingIdService.java
@@ -0,0 +1,59 @@
+/*
+ * 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.ads.identifier;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import androidx.ads.identifier.provider.IAdvertisingIdService;
+import androidx.annotation.Nullable;
+
+/**
+ * Provide a mock for {@link androidx.ads.identifier.provider.IAdvertisingIdService}.
+ * To be used in unit tests.
+ */
+public class MockAdvertisingIdService extends Service {
+
+ static final String TESTING_AD_ID = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
+
+ private MockAdvertisingIdServiceImpl mAdvertisingIdServiceImpl;
+
+ @Override
+ public void onCreate() {
+ mAdvertisingIdServiceImpl = new MockAdvertisingIdServiceImpl();
+ }
+
+ @Nullable
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mAdvertisingIdServiceImpl;
+ }
+
+ private static class MockAdvertisingIdServiceImpl extends IAdvertisingIdService.Stub {
+ @Override
+ public String getId() throws RemoteException {
+ return TESTING_AD_ID;
+ }
+
+ @Override
+ public boolean isLimitAdTrackingEnabled() throws RemoteException {
+ return true;
+ }
+ }
+}
diff --git a/ads/ads-identifier/src/androidTest/java/androidx/ads/identifier/MockAdvertisingIdThrowsNpeService.java b/ads/ads-identifier/src/androidTest/java/androidx/ads/identifier/MockAdvertisingIdThrowsNpeService.java
new file mode 100644
index 0000000..d6c7f16
--- /dev/null
+++ b/ads/ads-identifier/src/androidTest/java/androidx/ads/identifier/MockAdvertisingIdThrowsNpeService.java
@@ -0,0 +1,57 @@
+/*
+ * 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.ads.identifier;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import androidx.ads.identifier.provider.IAdvertisingIdService;
+import androidx.annotation.Nullable;
+
+/**
+ * Provide a mock for {@link IAdvertisingIdService} which always throw {@link NullPointerException}.
+ * To be used in unit tests.
+ */
+public class MockAdvertisingIdThrowsNpeService extends Service {
+
+ private MockAdvertisingIdServiceImpl mAdvertisingIdServiceImpl;
+
+ @Override
+ public void onCreate() {
+ mAdvertisingIdServiceImpl = new MockAdvertisingIdServiceImpl();
+ }
+
+ @Nullable
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mAdvertisingIdServiceImpl;
+ }
+
+ private static class MockAdvertisingIdServiceImpl extends IAdvertisingIdService.Stub {
+ @Override
+ public String getId() throws RemoteException {
+ throw new NullPointerException();
+ }
+
+ @Override
+ public boolean isLimitAdTrackingEnabled() throws RemoteException {
+ throw new NullPointerException();
+ }
+ }
+}
diff --git a/ads/ads-identifier/src/main/java/androidx/ads/identifier/AdvertisingIdClient.java b/ads/ads-identifier/src/main/java/androidx/ads/identifier/AdvertisingIdClient.java
new file mode 100644
index 0000000..5993e0e
--- /dev/null
+++ b/ads/ads-identifier/src/main/java/androidx/ads/identifier/AdvertisingIdClient.java
@@ -0,0 +1,268 @@
+/*
+ * 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.ads.identifier;
+
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.RemoteException;
+
+import androidx.ads.identifier.internal.BlockingServiceConnection;
+import androidx.ads.identifier.provider.IAdvertisingIdService;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.annotation.WorkerThread;
+import androidx.concurrent.futures.CallbackToFutureAdapter;
+import androidx.core.util.Preconditions;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.List;
+import java.util.Locale;
+import java.util.UUID;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Client for retrieving Advertising ID related info from an AndroidX ID Provider installed on
+ * the device.
+ *
+ * <p>Typical usage would be:
+ * <ol>
+ * <li>Call {@link #isAdvertisingIdProviderAvailable} to make sure there is an Advertising ID
+ * Provider available.
+ * <li>Call {@link #getAdvertisingIdInfo} to get Advertising ID info (the Advertising ID and LAT
+ * setting).
+ * </ol>
+ */
+public class AdvertisingIdClient {
+
+ private static final long SERVICE_CONNECTION_TIMEOUT_SECONDS = 10;
+
+ @VisibleForTesting
+ static final ExecutorService EXECUTOR_SERVICE = Executors.newCachedThreadPool();
+
+ @Nullable
+ private BlockingServiceConnection mConnection;
+
+ @Nullable
+ private IAdvertisingIdService mService;
+
+ private final Context mContext;
+
+ private ComponentName mComponentName;
+
+ /** Constructs a new {@link AdvertisingIdClient} object. */
+ @VisibleForTesting
+ AdvertisingIdClient(Context context) {
+ Preconditions.checkNotNull(context);
+ mContext = context.getApplicationContext();
+ }
+
+ @WorkerThread
+ private void start() throws IOException, AdvertisingIdNotAvailableException, TimeoutException,
+ InterruptedException {
+ if (mConnection == null) {
+ mComponentName = getProviderComponentName(mContext);
+ mConnection = getServiceConnection();
+ mService = getAdvertisingIdService(mConnection);
+ }
+ }
+
+ /** Returns the Advertising ID info as {@link AdvertisingIdInfo}. */
+ @VisibleForTesting
+ @WorkerThread
+ AdvertisingIdInfo getInfoInternal() throws IOException, AdvertisingIdNotAvailableException,
+ TimeoutException, InterruptedException {
+ if (mConnection == null) {
+ start();
+ }
+ try {
+ String id = mService.getId();
+ if (id == null || id.trim().isEmpty()) {
+ throw new AdvertisingIdNotAvailableException(
+ "Advertising ID Provider does not returns an Advertising ID.");
+ }
+ return AdvertisingIdInfo.builder()
+ .setId(normalizeId(id))
+ .setProviderPackageName(mComponentName.getPackageName())
+ .setLimitAdTrackingEnabled(mService.isLimitAdTrackingEnabled())
+ .build();
+ } catch (RemoteException e) {
+ throw new IOException("Remote exception", e);
+ } catch (RuntimeException e) {
+ throw new AdvertisingIdNotAvailableException(
+ "Advertising ID Provider throws a exception.", e);
+ }
+ }
+
+ /**
+ * Checks the Advertising ID format, if it's not in UUID format, normalizes the Advertising
+ * ID to UUID format.
+ *
+ * @return Advertising ID, in lower case format using locale {@code Locale.US};
+ */
+ @VisibleForTesting
+ static String normalizeId(String id) {
+ String lowerCaseId = id.toLowerCase(Locale.US);
+ if (isUuidFormat(lowerCaseId)) {
+ return lowerCaseId;
+ }
+ return UUID.nameUUIDFromBytes(id.getBytes(Charset.forName("UTF-8"))).toString();
+ }
+
+ /* Validate the input is lowercase and is a valid UUID. */
+ private static boolean isUuidFormat(String id) {
+ try {
+ return id.equals(UUID.fromString(id).toString());
+ } catch (IllegalArgumentException iae) {
+ return false;
+ }
+ }
+
+ /** Closes the connection to the Advertising ID Provider Service. */
+ @VisibleForTesting
+ void finish() {
+ if (mConnection == null) {
+ return;
+ }
+ mContext.unbindService(mConnection);
+ mComponentName = null;
+ mConnection = null;
+ mService = null;
+ }
+
+ private static ComponentName getProviderComponentName(Context context)
+ throws AdvertisingIdNotAvailableException {
+ PackageManager packageManager = context.getPackageManager();
+ List<ResolveInfo> resolveInfos =
+ AdvertisingIdUtils.getAdvertisingIdProviderServices(packageManager);
+ ServiceInfo serviceInfo =
+ AdvertisingIdUtils.selectServiceByPriority(resolveInfos, packageManager);
+ if (serviceInfo == null) {
+ throw new AdvertisingIdNotAvailableException("No Advertising ID Provider available.");
+ }
+ return new ComponentName(serviceInfo.packageName, serviceInfo.name);
+ }
+
+ /**
+ * Retrieves BlockingServiceConnection which must be unbound after use.
+ *
+ * @throws IOException when unable to bind service successfully.
+ */
+ @VisibleForTesting
+ BlockingServiceConnection getServiceConnection() throws IOException {
+ Intent intent = new Intent(AdvertisingIdUtils.GET_AD_ID_ACTION);
+ intent.setComponent(mComponentName);
+
+ BlockingServiceConnection bsc = new BlockingServiceConnection();
+ if (mContext.bindService(intent, bsc, Service.BIND_AUTO_CREATE)) {
+ return bsc;
+ } else {
+ throw new IOException("Connection failure");
+ }
+ }
+
+ /**
+ * Get the {@link IAdvertisingIdService} from the blocking queue. This should wait until
+ * {@link android.content.ServiceConnection#onServiceConnected} event with a
+ * {@link #SERVICE_CONNECTION_TIMEOUT_SECONDS} second timeout.
+ *
+ * @throws TimeoutException if connection timeout period has expired.
+ * @throws InterruptedException if connection has been interrupted before connected.
+ */
+ @VisibleForTesting
+ @WorkerThread
+ IAdvertisingIdService getAdvertisingIdService(BlockingServiceConnection bsc)
+ throws TimeoutException, InterruptedException {
+ // Block until the bind is complete, or timeout period is over.
+ return IAdvertisingIdService.Stub.asInterface(
+ bsc.getServiceWithTimeout(
+ SERVICE_CONNECTION_TIMEOUT_SECONDS, TimeUnit.SECONDS));
+ }
+
+ /**
+ * Checks whether there is any Advertising ID Provider installed on the device.
+ *
+ * <p>This method does a quick check for the Advertising ID providers.
+ * <p>Note: Even if this method returns true, there is still a possibility that the
+ * {@link #getAdvertisingIdInfo(Context)} method throws an exception for some reason.
+ *
+ * @param context Current {@link Context} (such as the current {@link android.app.Activity}).
+ * @return whether there is an Advertising ID Provider available on the device.
+ */
+ public static boolean isAdvertisingIdProviderAvailable(@NonNull Context context) {
+ return !AdvertisingIdUtils.getAdvertisingIdProviderServices(context.getPackageManager())
+ .isEmpty();
+ }
+
+ /**
+ * Retrieves the user's Advertising ID info.
+ *
+ * <p>When multiple Advertising ID Providers are installed on the device, this method will
+ * always return the Advertising ID information from same Advertising ID Provider for all
+ * apps which use this library, using following priority:
+ * <ol>
+ * <li>System-level providers with "androidx.ads.identifier.provider.HIGH_PRIORITY" permission
+ * <li>Other system-level providers
+ * </ol>
+ * <p>If there are ties in any of the above categories, it will use this priority:
+ * <ol>
+ * <li>First app by earliest install time
+ * ({@link android.content.pm.PackageInfo#firstInstallTime})
+ * <li>First app by package name alphabetically sorted
+ * </ol>
+ *
+ * @param context Current {@link Context} (such as the current {@link android.app.Activity}).
+ * @return A {@link ListenableFuture} that will be fulfilled with a {@link AdvertisingIdInfo}
+ * which contains the user's Advertising ID info, or rejected with the following exceptions,
+ * <ul>
+ * <li><b>IOException</b> signaling connection to Advertising ID Providers failed.
+ * <li><b>AdvertisingIdNotAvailableException</b> indicating Advertising ID is not available,
+ * like no Advertising ID Provider found or provider does not return an Advertising ID.
+ * <li><b>TimeoutException</b> indicating connection timeout period has expired.
+ * <li><b>InterruptedException</b> indicating the current thread has been interrupted.
+ * </ul>
+ */
+ @NonNull
+ public static ListenableFuture<AdvertisingIdInfo> getAdvertisingIdInfo(
+ @NonNull Context context) {
+ return CallbackToFutureAdapter.getFuture(completer -> {
+ EXECUTOR_SERVICE.execute(() -> {
+ AdvertisingIdClient client = new AdvertisingIdClient(context);
+ try {
+ completer.set(client.getInfoInternal());
+ } catch (IOException | AdvertisingIdNotAvailableException | TimeoutException
+ | InterruptedException e) {
+ completer.setException(e);
+ } finally {
+ client.finish();
+ }
+ });
+ return "getAdvertisingIdInfo";
+ });
+ }
+}
diff --git a/ads/ads-identifier/src/main/java/androidx/ads/identifier/AdvertisingIdInfo.java b/ads/ads-identifier/src/main/java/androidx/ads/identifier/AdvertisingIdInfo.java
new file mode 100644
index 0000000..6821731a
--- /dev/null
+++ b/ads/ads-identifier/src/main/java/androidx/ads/identifier/AdvertisingIdInfo.java
@@ -0,0 +1,72 @@
+/*
+ * 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.ads.identifier;
+
+import androidx.annotation.NonNull;
+
+import com.google.auto.value.AutoValue;
+
+/**
+ * Advertising ID Information.
+ * Includes both the Advertising ID and the limit ad tracking setting.
+ */
+@AutoValue
+public abstract class AdvertisingIdInfo {
+
+ // Create a no-args constructor so it doesn't appear in current.txt
+ AdvertisingIdInfo() {
+ }
+
+ /**
+ * Retrieves the Advertising ID.
+ *
+ * <p>This will be in UUID format, either Advertising ID Provider provided an Advertising ID
+ * in UUID format, or this developer library will normalize the Advertising ID to UUID format.
+ */
+ @NonNull
+ public abstract String getId();
+
+ /** Retrieves the Advertising ID provider package name. */
+ @NonNull
+ public abstract String getProviderPackageName();
+
+ /** Retrieves whether the user has set Limit Advertising Tracking. */
+ public abstract boolean isLimitAdTrackingEnabled();
+
+
+ /** Create a {@link Builder}. */
+ static Builder builder() {
+ return new AutoValue_AdvertisingIdInfo.Builder();
+ }
+
+ /** The builder for {@link AdvertisingIdInfo}. */
+ @AutoValue.Builder
+ abstract static class Builder {
+
+ // Create a no-args constructor so it doesn't appear in current.txt
+ Builder() {
+ }
+
+ abstract Builder setId(String id);
+
+ abstract Builder setProviderPackageName(String providerPackageName);
+
+ abstract Builder setLimitAdTrackingEnabled(boolean limitAdTrackingEnabled);
+
+ abstract AdvertisingIdInfo build();
+ }
+}
diff --git a/ads/ads-identifier/src/main/java/androidx/ads/identifier/AdvertisingIdNotAvailableException.java b/ads/ads-identifier/src/main/java/androidx/ads/identifier/AdvertisingIdNotAvailableException.java
new file mode 100644
index 0000000..e698f1e
--- /dev/null
+++ b/ads/ads-identifier/src/main/java/androidx/ads/identifier/AdvertisingIdNotAvailableException.java
@@ -0,0 +1,32 @@
+/*
+ * 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.ads.identifier;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Indicates an AndroidX Advertising ID is not available.
+ */
+public class AdvertisingIdNotAvailableException extends Exception {
+ public AdvertisingIdNotAvailableException(@NonNull String message) {
+ super(message);
+ }
+
+ public AdvertisingIdNotAvailableException(@NonNull String message, @NonNull Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/ads/ads-identifier/src/main/java/androidx/ads/identifier/internal/BlockingServiceConnection.java b/ads/ads-identifier/src/main/java/androidx/ads/identifier/internal/BlockingServiceConnection.java
new file mode 100644
index 0000000..4963cbd
--- /dev/null
+++ b/ads/ads-identifier/src/main/java/androidx/ads/identifier/internal/BlockingServiceConnection.java
@@ -0,0 +1,69 @@
+/*
+ * 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.ads.identifier.internal;
+
+import android.content.ComponentName;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+
+import androidx.annotation.NonNull;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * A one-time use ServiceConnection that facilitates waiting for the bind to complete and the
+ * passing of the IBinder from the callback thread to the waiting thread.
+ */
+public class BlockingServiceConnection implements ServiceConnection {
+
+ // Facilitates passing of the IBinder across threads
+ private final BlockingQueue<IBinder> mBlockingQueue = new LinkedBlockingQueue<>();
+
+ @Override
+ public void onServiceConnected(@NonNull ComponentName name, @NonNull IBinder service) {
+ mBlockingQueue.add(service);
+ }
+
+ @Override
+ public void onServiceDisconnected(@NonNull ComponentName name) {
+ // Don't worry about clearing the returned binder in this case. If it does
+ // happen a RemoteException will be thrown, which is already handled.
+ }
+
+ /**
+ * Blocks until the bind is complete with a timeout and returns the bound IBinder. This must
+ * only be called once.
+ *
+ * @return the IBinder of the bound service
+ * @throws InterruptedException if the current thread is interrupted while waiting for the bind
+ * @throws IllegalStateException if called more than once
+ * @throws TimeoutException if the timeout period has elapsed
+ */
+ @NonNull
+ public IBinder getServiceWithTimeout(long timeout, @NonNull TimeUnit timeUnit)
+ throws InterruptedException, TimeoutException {
+ IBinder binder = mBlockingQueue.poll(timeout, timeUnit);
+ if (binder == null) {
+ throw new TimeoutException("Timed out waiting for the service connection");
+ } else {
+ return binder;
+ }
+ }
+}
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/JoinedKey.kt b/ads/ads-identifier/src/main/java/androidx/ads/identifier/internal/package-info.java
similarity index 75%
copy from compose/compose-runtime/src/main/java/androidx/compose/JoinedKey.kt
copy to ads/ads-identifier/src/main/java/androidx/ads/identifier/internal/package-info.java
index 601fbf1..e9ad310 100644
--- a/compose/compose-runtime/src/main/java/androidx/compose/JoinedKey.kt
+++ b/ads/ads-identifier/src/main/java/androidx/ads/identifier/internal/package-info.java
@@ -14,9 +14,12 @@
* limitations under the License.
*/
-package androidx.compose
+/**
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+package androidx.ads.identifier.internal;
-internal data class JoinedKey(
- @JvmField val left: Any?,
- @JvmField val right: Any?
-)
\ No newline at end of file
+import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import androidx.annotation.RestrictTo;
diff --git a/annotation/annotation-experimental-lint/build.gradle b/annotation/annotation-experimental-lint/build.gradle
index 6ef83b3..9fae7f7 100644
--- a/annotation/annotation-experimental-lint/build.gradle
+++ b/annotation/annotation-experimental-lint/build.gradle
@@ -44,11 +44,11 @@
androidx {
name = "Experimental annotation lint checks"
toolingProject = true
- publish = Publish.NONE
- mavenVersion = LibraryVersions.ANNOTATION
+ publish = Publish.SNAPSHOT_AND_RELEASE
+ mavenVersion = LibraryVersions.ANNOTATION_EXPERIMENTAL
mavenGroup = LibraryGroups.ANNOTATION
inceptionYear = "2019"
- description = "Lint checks for the experimental annotation library"
- url = ARCHITECTURE_URL
+ description = "Lint checks for the Experimental annotation library. Also enforces the " +
+ "semantics of Kotlin @Experimental APIs from within Android Java source code."
compilationTarget = CompilationTarget.HOST
}
diff --git a/annotation/annotation-experimental/api/1.0.0-alpha01.txt b/annotation/annotation-experimental/api/1.0.0-alpha01.txt
new file mode 100644
index 0000000..fce9faf2
--- /dev/null
+++ b/annotation/annotation-experimental/api/1.0.0-alpha01.txt
@@ -0,0 +1,18 @@
+// Signature format: 3.0
+package androidx.annotation.experimental {
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.ANNOTATION_TYPE}) public @interface Experimental {
+ method public abstract androidx.annotation.experimental.Experimental.Level level() default androidx.annotation.experimental.Experimental.Level.ERROR;
+ }
+
+ public enum Experimental.Level {
+ enum_constant public static final androidx.annotation.experimental.Experimental.Level ERROR;
+ enum_constant public static final androidx.annotation.experimental.Experimental.Level WARNING;
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.PACKAGE}) public @interface UseExperimental {
+ method public abstract Class<?> markerClass();
+ }
+
+}
+
diff --git a/annotation/annotation-experimental/api/current.txt b/annotation/annotation-experimental/api/current.txt
new file mode 100644
index 0000000..fce9faf2
--- /dev/null
+++ b/annotation/annotation-experimental/api/current.txt
@@ -0,0 +1,18 @@
+// Signature format: 3.0
+package androidx.annotation.experimental {
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.ANNOTATION_TYPE}) public @interface Experimental {
+ method public abstract androidx.annotation.experimental.Experimental.Level level() default androidx.annotation.experimental.Experimental.Level.ERROR;
+ }
+
+ public enum Experimental.Level {
+ enum_constant public static final androidx.annotation.experimental.Experimental.Level ERROR;
+ enum_constant public static final androidx.annotation.experimental.Experimental.Level WARNING;
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.PACKAGE}) public @interface UseExperimental {
+ method public abstract Class<?> markerClass();
+ }
+
+}
+
diff --git a/benchmark/api/res-1.0.0-alpha01.txt b/annotation/annotation-experimental/api/res-1.0.0-alpha01.txt
similarity index 100%
copy from benchmark/api/res-1.0.0-alpha01.txt
copy to annotation/annotation-experimental/api/res-1.0.0-alpha01.txt
diff --git a/annotation/annotation-experimental/api/restricted_1.0.0-alpha01.txt b/annotation/annotation-experimental/api/restricted_1.0.0-alpha01.txt
new file mode 100644
index 0000000..fce9faf2
--- /dev/null
+++ b/annotation/annotation-experimental/api/restricted_1.0.0-alpha01.txt
@@ -0,0 +1,18 @@
+// Signature format: 3.0
+package androidx.annotation.experimental {
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.ANNOTATION_TYPE}) public @interface Experimental {
+ method public abstract androidx.annotation.experimental.Experimental.Level level() default androidx.annotation.experimental.Experimental.Level.ERROR;
+ }
+
+ public enum Experimental.Level {
+ enum_constant public static final androidx.annotation.experimental.Experimental.Level ERROR;
+ enum_constant public static final androidx.annotation.experimental.Experimental.Level WARNING;
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.PACKAGE}) public @interface UseExperimental {
+ method public abstract Class<?> markerClass();
+ }
+
+}
+
diff --git a/annotation/annotation-experimental/api/restricted_current.txt b/annotation/annotation-experimental/api/restricted_current.txt
new file mode 100644
index 0000000..fce9faf2
--- /dev/null
+++ b/annotation/annotation-experimental/api/restricted_current.txt
@@ -0,0 +1,18 @@
+// Signature format: 3.0
+package androidx.annotation.experimental {
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.ANNOTATION_TYPE}) public @interface Experimental {
+ method public abstract androidx.annotation.experimental.Experimental.Level level() default androidx.annotation.experimental.Experimental.Level.ERROR;
+ }
+
+ public enum Experimental.Level {
+ enum_constant public static final androidx.annotation.experimental.Experimental.Level ERROR;
+ enum_constant public static final androidx.annotation.experimental.Experimental.Level WARNING;
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.PACKAGE}) public @interface UseExperimental {
+ method public abstract Class<?> markerClass();
+ }
+
+}
+
diff --git a/annotation/annotation-experimental/build.gradle b/annotation/annotation-experimental/build.gradle
index 922edae..4d61d05 100644
--- a/annotation/annotation-experimental/build.gradle
+++ b/annotation/annotation-experimental/build.gradle
@@ -30,8 +30,10 @@
androidx {
name = "Experimental annotation"
publish = Publish.SNAPSHOT_AND_RELEASE
- mavenVersion = LibraryVersions.ANNOTATION
+ mavenVersion = LibraryVersions.ANNOTATION_EXPERIMENTAL
mavenGroup = LibraryGroups.ANNOTATION
inceptionYear = "2019"
- description = "Annotation for use on unstable API surfaces"
+ description = "Java annotation for use on unstable Android API surfaces. When used in " +
+ "conjunction with the Experimental annotation lint checks, this annotation provides " +
+ "functional parity with Kotlin's Experimental annotation."
}
diff --git a/annotation/annotation/README.md b/annotation/annotation/README.md
index b7f5912..e65290d 100644
--- a/annotation/annotation/README.md
+++ b/annotation/annotation/README.md
@@ -7,7 +7,7 @@
[Release notes](https://developer.android.com/jetpack/androidx/releases/annotation)
-[Browse source](https://android.googlesource.com/platform/frameworks/support/+/androidx-master-dev/annotations/)
+[Browse source](https://android.googlesource.com/platform/frameworks/support/+/androidx-master-dev/annotation/annotation/)
[Reference documentation](https://developer.android.com/reference/androidx/classes.html)
diff --git a/appcompat/benchmark/build.gradle b/appcompat/benchmark/build.gradle
index 21e4797..962cb98 100644
--- a/appcompat/benchmark/build.gradle
+++ b/appcompat/benchmark/build.gradle
@@ -21,11 +21,12 @@
id("AndroidXPlugin")
id("com.android.library")
id("kotlin-android")
+ id("androidx.benchmark")
}
dependencies {
androidTestImplementation(project(":appcompat"))
- androidTestImplementation(project(":benchmark"))
+ androidTestImplementation(project(":benchmark:benchmark-junit4"))
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
androidTestImplementation(ANDROIDX_TEST_CORE)
androidTestImplementation(ANDROIDX_TEST_RUNNER)
diff --git a/appcompat/benchmark/src/androidTest/java/androidx/appcompat/benchmark/ViewInflationBenchmark.kt b/appcompat/benchmark/src/androidTest/java/androidx/appcompat/benchmark/ViewInflationBenchmark.kt
index 7976411..9817973 100644
--- a/appcompat/benchmark/src/androidTest/java/androidx/appcompat/benchmark/ViewInflationBenchmark.kt
+++ b/appcompat/benchmark/src/androidTest/java/androidx/appcompat/benchmark/ViewInflationBenchmark.kt
@@ -21,8 +21,8 @@
import android.widget.FrameLayout
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.benchmark.test.R
-import androidx.benchmark.BenchmarkRule
-import androidx.benchmark.measureRepeated
+import androidx.benchmark.junit4.BenchmarkRule
+import androidx.benchmark.junit4.measureRepeated
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.test.rule.ActivityTestRule
diff --git a/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatBaseImageViewTest.java b/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatBaseImageViewTest.java
index 78aa513..fe4058d 100755
--- a/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatBaseImageViewTest.java
+++ b/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatBaseImageViewTest.java
@@ -35,7 +35,7 @@
import androidx.appcompat.testutils.TestUtils;
import androidx.core.content.res.ResourcesCompat;
import androidx.core.graphics.ColorUtils;
-import androidx.test.filters.SmallTest;
+import androidx.test.filters.MediumTest;
import org.junit.Test;
@@ -65,7 +65,7 @@
* image source tint lists on the same image source.
*/
@Test
- @SmallTest
+ @MediumTest
public void testImageTintingAcrossStateChange() {
final @IdRes int viewId = R.id.view_tinted_source;
final Resources res = mActivity.getResources();
@@ -143,7 +143,7 @@
* image source tinting mode.
*/
@Test
- @SmallTest
+ @MediumTest
public void testImageTintingAcrossModeChange() {
final @IdRes int viewId = R.id.view_untinted_source;
final Resources res = mActivity.getResources();
@@ -214,7 +214,7 @@
* Tests for behavior around setting a tint list without setting a tint mode
*/
@Test
- @SmallTest
+ @MediumTest
public void testImageTintingWithDefaultMode() {
final @IdRes int viewId = R.id.view_untinted_source;
final Resources res = mActivity.getResources();
@@ -254,7 +254,7 @@
* is applied correctly after changing the image source itself.
*/
@Test
- @SmallTest
+ @MediumTest
public void testImageOpaqueTintingAcrossImageChange() {
final @IdRes int viewId = R.id.view_tinted_no_source;
final Resources res = mActivity.getResources();
@@ -309,7 +309,7 @@
* is applied correctly after changing the image source itself.
*/
@Test
- @SmallTest
+ @MediumTest
public void testImageTranslucentTintingAcrossImageChange() {
final @IdRes int viewId = R.id.view_untinted_no_source;
final Resources res = mActivity.getResources();
@@ -399,7 +399,7 @@
* affect the tinting of the image source.
*/
@Test
- @SmallTest
+ @MediumTest
public void testImageTintingAcrossBackgroundTintingChange() {
final @IdRes int viewId = R.id.view_untinted_source;
final Resources res = mActivity.getResources();
diff --git a/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImpl.java b/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImpl.java
index 1bfe3a3..fd10f53 100644
--- a/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImpl.java
+++ b/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImpl.java
@@ -2286,10 +2286,13 @@
} catch (IllegalStateException e) {
// applyOverrideConfiguration throws an IllegalStateException if its resources
// have already been created. Since there's no way to check this beforehand we
- // just have to try it and catch the exception.
- Log.e(TAG, "updateForNightMode. Calling applyOverrideConfiguration() failed"
- + " with an exception. Will fall back to using"
- + " Resources.updateConfiguration()", e);
+ // just have to try it and catch the exception. We only log if we're actually
+ // trying to apply a uiMode configuration though.
+ if (newNightMode != applicationNightMode) {
+ Log.e(TAG, "updateForNightMode. Calling applyOverrideConfiguration() failed"
+ + " with an exception. Will fall back to using"
+ + " Resources.updateConfiguration()", e);
+ }
handled = false;
}
}
diff --git a/benchmark/api/1.0.0-alpha04.txt b/benchmark/api/1.0.0-alpha04.txt
deleted file mode 100644
index 96d4bc8..0000000
--- a/benchmark/api/1.0.0-alpha04.txt
+++ /dev/null
@@ -1,35 +0,0 @@
-// Signature format: 3.0
-package androidx.benchmark {
-
- public class AndroidBenchmarkRunner extends androidx.test.runner.AndroidJUnitRunner {
- ctor public AndroidBenchmarkRunner();
- }
-
- public final class ArgumentsKt {
- ctor public ArgumentsKt();
- }
-
- public final class BenchmarkRule implements org.junit.rules.TestRule {
- ctor public BenchmarkRule();
- method public org.junit.runners.model.Statement apply(org.junit.runners.model.Statement base, org.junit.runner.Description description);
- method public androidx.benchmark.BenchmarkState getState();
- }
-
- public final class BenchmarkRule.Scope {
- method public inline <T> T! runWithTimingDisabled(kotlin.jvm.functions.Function0<? extends T> block);
- }
-
- public final class BenchmarkRuleKt {
- ctor public BenchmarkRuleKt();
- method public static inline void measureRepeated(androidx.benchmark.BenchmarkRule, kotlin.jvm.functions.Function1<? super androidx.benchmark.BenchmarkRule.Scope,kotlin.Unit> block);
- }
-
- public final class BenchmarkState {
- method public boolean keepRunning();
- method public void pauseTiming();
- method public static void reportData(String className, String testName, long totalRunTimeNs, java.util.List<java.lang.Long> dataNs, @IntRange(from=0) int warmupIterations, @IntRange(from=0) long thermalThrottleSleepSeconds, @IntRange(from=1) int repeatIterations);
- method public void resumeTiming();
- }
-
-}
-
diff --git a/benchmark/api/api_lint.ignore b/benchmark/api/api_lint.ignore
deleted file mode 100644
index 0a0591c..0000000
--- a/benchmark/api/api_lint.ignore
+++ /dev/null
@@ -1,7 +0,0 @@
-// Baseline format: 1.0
-DocumentExceptions: androidx.benchmark.BenchmarkRule#getState():
- Method BenchmarkRule.getState appears to be throwing java.lang.IllegalStateException; this should be listed in the documentation; see https://android.github.io/kotlin-guides/interop.html#document-exceptions
-DocumentExceptions: androidx.benchmark.BenchmarkState#pauseTiming():
- Method BenchmarkState.pauseTiming appears to be throwing java.lang.IllegalStateException; this should be listed in the documentation; see https://android.github.io/kotlin-guides/interop.html#document-exceptions
-DocumentExceptions: androidx.benchmark.BenchmarkState#resumeTiming():
- Method BenchmarkState.resumeTiming appears to be throwing java.lang.IllegalStateException; this should be listed in the documentation; see https://android.github.io/kotlin-guides/interop.html#document-exceptions
diff --git a/benchmark/api/current.txt b/benchmark/api/current.txt
deleted file mode 100644
index 96d4bc8..0000000
--- a/benchmark/api/current.txt
+++ /dev/null
@@ -1,35 +0,0 @@
-// Signature format: 3.0
-package androidx.benchmark {
-
- public class AndroidBenchmarkRunner extends androidx.test.runner.AndroidJUnitRunner {
- ctor public AndroidBenchmarkRunner();
- }
-
- public final class ArgumentsKt {
- ctor public ArgumentsKt();
- }
-
- public final class BenchmarkRule implements org.junit.rules.TestRule {
- ctor public BenchmarkRule();
- method public org.junit.runners.model.Statement apply(org.junit.runners.model.Statement base, org.junit.runner.Description description);
- method public androidx.benchmark.BenchmarkState getState();
- }
-
- public final class BenchmarkRule.Scope {
- method public inline <T> T! runWithTimingDisabled(kotlin.jvm.functions.Function0<? extends T> block);
- }
-
- public final class BenchmarkRuleKt {
- ctor public BenchmarkRuleKt();
- method public static inline void measureRepeated(androidx.benchmark.BenchmarkRule, kotlin.jvm.functions.Function1<? super androidx.benchmark.BenchmarkRule.Scope,kotlin.Unit> block);
- }
-
- public final class BenchmarkState {
- method public boolean keepRunning();
- method public void pauseTiming();
- method public static void reportData(String className, String testName, long totalRunTimeNs, java.util.List<java.lang.Long> dataNs, @IntRange(from=0) int warmupIterations, @IntRange(from=0) long thermalThrottleSleepSeconds, @IntRange(from=1) int repeatIterations);
- method public void resumeTiming();
- }
-
-}
-
diff --git a/benchmark/api/restricted_1.0.0-alpha04.txt b/benchmark/api/restricted_1.0.0-alpha04.txt
deleted file mode 100644
index 35c0a19..0000000
--- a/benchmark/api/restricted_1.0.0-alpha04.txt
+++ /dev/null
@@ -1,41 +0,0 @@
-// Signature format: 3.0
-package androidx.benchmark {
-
- public class AndroidBenchmarkRunner extends androidx.test.runner.AndroidJUnitRunner {
- ctor public AndroidBenchmarkRunner();
- }
-
- public final class ArgumentsKt {
- ctor public ArgumentsKt();
- }
-
- public final class BenchmarkRule implements org.junit.rules.TestRule {
- ctor public BenchmarkRule();
- method public org.junit.runners.model.Statement apply(org.junit.runners.model.Statement base, org.junit.runner.Description description);
- method public androidx.benchmark.BenchmarkState getState();
- }
-
- public final class BenchmarkRule.Scope {
- method public inline <T> T! runWithTimingDisabled(kotlin.jvm.functions.Function0<? extends T> block);
- }
-
- public final class BenchmarkRuleKt {
- ctor public BenchmarkRuleKt();
- method public static inline void measureRepeated(androidx.benchmark.BenchmarkRule, kotlin.jvm.functions.Function1<? super androidx.benchmark.BenchmarkRule.Scope,kotlin.Unit> block);
- }
-
- public final class BenchmarkState {
- method public boolean keepRunning();
- method public void pauseTiming();
- method public static void reportData(String className, String testName, long totalRunTimeNs, java.util.List<java.lang.Long> dataNs, @IntRange(from=0) int warmupIterations, @IntRange(from=0) long thermalThrottleSleepSeconds, @IntRange(from=1) int repeatIterations);
- method public void resumeTiming();
- }
-
-
- public static final class IsolationActivity.Companion {
- method @AnyThread public void finishSingleton();
- method @WorkerThread public void launchSingleton();
- }
-
-}
-
diff --git a/benchmark/api/restricted_current.txt b/benchmark/api/restricted_current.txt
deleted file mode 100644
index 35c0a19..0000000
--- a/benchmark/api/restricted_current.txt
+++ /dev/null
@@ -1,41 +0,0 @@
-// Signature format: 3.0
-package androidx.benchmark {
-
- public class AndroidBenchmarkRunner extends androidx.test.runner.AndroidJUnitRunner {
- ctor public AndroidBenchmarkRunner();
- }
-
- public final class ArgumentsKt {
- ctor public ArgumentsKt();
- }
-
- public final class BenchmarkRule implements org.junit.rules.TestRule {
- ctor public BenchmarkRule();
- method public org.junit.runners.model.Statement apply(org.junit.runners.model.Statement base, org.junit.runner.Description description);
- method public androidx.benchmark.BenchmarkState getState();
- }
-
- public final class BenchmarkRule.Scope {
- method public inline <T> T! runWithTimingDisabled(kotlin.jvm.functions.Function0<? extends T> block);
- }
-
- public final class BenchmarkRuleKt {
- ctor public BenchmarkRuleKt();
- method public static inline void measureRepeated(androidx.benchmark.BenchmarkRule, kotlin.jvm.functions.Function1<? super androidx.benchmark.BenchmarkRule.Scope,kotlin.Unit> block);
- }
-
- public final class BenchmarkState {
- method public boolean keepRunning();
- method public void pauseTiming();
- method public static void reportData(String className, String testName, long totalRunTimeNs, java.util.List<java.lang.Long> dataNs, @IntRange(from=0) int warmupIterations, @IntRange(from=0) long thermalThrottleSleepSeconds, @IntRange(from=1) int repeatIterations);
- method public void resumeTiming();
- }
-
-
- public static final class IsolationActivity.Companion {
- method @AnyThread public void finishSingleton();
- method @WorkerThread public void launchSingleton();
- }
-
-}
-
diff --git a/benchmark/benchmark/build.gradle b/benchmark/benchmark/build.gradle
index 21a0621..b540dcd 100644
--- a/benchmark/benchmark/build.gradle
+++ b/benchmark/benchmark/build.gradle
@@ -19,10 +19,11 @@
id("AndroidXPlugin")
id("com.android.library")
id("kotlin-android")
+ id("androidx.benchmark")
}
dependencies {
- androidTestImplementation(project(":benchmark"))
+ androidTestImplementation(project(":benchmark:benchmark-junit4"))
androidTestImplementation(ANDROIDX_TEST_RUNNER)
androidTestImplementation(ANDROIDX_TEST_RULES)
androidTestImplementation(JUNIT)
diff --git a/benchmark/benchmark/src/androidTest/java/androidx/benchmark/benchmark/ParameterizedBenchmark.kt b/benchmark/benchmark/src/androidTest/java/androidx/benchmark/benchmark/ParameterizedBenchmark.kt
index 214aea1..09a21f4 100644
--- a/benchmark/benchmark/src/androidTest/java/androidx/benchmark/benchmark/ParameterizedBenchmark.kt
+++ b/benchmark/benchmark/src/androidTest/java/androidx/benchmark/benchmark/ParameterizedBenchmark.kt
@@ -16,21 +16,25 @@
package androidx.benchmark.benchmark
-import androidx.benchmark.BenchmarkRule
-import androidx.benchmark.measureRepeated
+import androidx.benchmark.junit4.BenchmarkRule
+import androidx.benchmark.junit4.measureRepeated
import androidx.test.filters.LargeTest
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameters
@LargeTest
@RunWith(Parameterized::class)
-class ParameterizedBenchmark(@Suppress("unused") private val input: Int) {
+class ParameterizedBenchmark(
+ @Suppress("unused") private val input: Int,
+ @Suppress("unused") private val stringInput: String
+) {
companion object {
@JvmStatic
- @Parameterized.Parameters
- fun data(): Collection<Array<Int>> = List(2) { arrayOf(it) }
+ @Parameters(name = "size={0},str:{1}")
+ fun data(): Collection<Array<Any>> = List(2) { arrayOf(it, "$it=:") }
}
@get:Rule
diff --git a/benchmark/benchmark/src/androidTest/java/androidx/benchmark/benchmark/SynchronizedBenchmark.kt b/benchmark/benchmark/src/androidTest/java/androidx/benchmark/benchmark/SynchronizedBenchmark.kt
index 5cadaf8..4dcaa59 100644
--- a/benchmark/benchmark/src/androidTest/java/androidx/benchmark/benchmark/SynchronizedBenchmark.kt
+++ b/benchmark/benchmark/src/androidTest/java/androidx/benchmark/benchmark/SynchronizedBenchmark.kt
@@ -16,8 +16,8 @@
package androidx.benchmark.benchmark
-import androidx.benchmark.BenchmarkRule
-import androidx.benchmark.measureRepeated
+import androidx.benchmark.junit4.BenchmarkRule
+import androidx.benchmark.junit4.measureRepeated
import androidx.test.filters.LargeTest
import org.junit.Rule
import org.junit.Test
diff --git a/benchmark/benchmark/src/androidTest/java/androidx/benchmark/benchmark/TrivialJavaBenchmark.java b/benchmark/benchmark/src/androidTest/java/androidx/benchmark/benchmark/TrivialJavaBenchmark.java
index bfad065..6146758 100644
--- a/benchmark/benchmark/src/androidTest/java/androidx/benchmark/benchmark/TrivialJavaBenchmark.java
+++ b/benchmark/benchmark/src/androidTest/java/androidx/benchmark/benchmark/TrivialJavaBenchmark.java
@@ -16,8 +16,8 @@
package androidx.benchmark.benchmark;
-import androidx.benchmark.BenchmarkRule;
import androidx.benchmark.BenchmarkState;
+import androidx.benchmark.junit4.BenchmarkRule;
import androidx.test.filters.LargeTest;
import org.junit.Rule;
diff --git a/benchmark/benchmark/src/androidTest/java/androidx/benchmark/benchmark/TrivialKotlinBenchmark.kt b/benchmark/benchmark/src/androidTest/java/androidx/benchmark/benchmark/TrivialKotlinBenchmark.kt
index c06742f..7260645 100644
--- a/benchmark/benchmark/src/androidTest/java/androidx/benchmark/benchmark/TrivialKotlinBenchmark.kt
+++ b/benchmark/benchmark/src/androidTest/java/androidx/benchmark/benchmark/TrivialKotlinBenchmark.kt
@@ -16,8 +16,8 @@
package androidx.benchmark.benchmark
-import androidx.benchmark.BenchmarkRule
-import androidx.benchmark.measureRepeated
+import androidx.benchmark.junit4.BenchmarkRule
+import androidx.benchmark.junit4.measureRepeated
import androidx.test.filters.LargeTest
import org.junit.Rule
import org.junit.Test
diff --git a/benchmark/api/1.0.0-alpha01.txt b/benchmark/common/api/1.0.0-alpha01.txt
similarity index 100%
rename from benchmark/api/1.0.0-alpha01.txt
rename to benchmark/common/api/1.0.0-alpha01.txt
diff --git a/benchmark/api/1.0.0-alpha02.txt b/benchmark/common/api/1.0.0-alpha02.txt
similarity index 100%
rename from benchmark/api/1.0.0-alpha02.txt
rename to benchmark/common/api/1.0.0-alpha02.txt
diff --git a/benchmark/api/1.0.0-alpha03.txt b/benchmark/common/api/1.0.0-alpha03.txt
similarity index 100%
rename from benchmark/api/1.0.0-alpha03.txt
rename to benchmark/common/api/1.0.0-alpha03.txt
diff --git a/benchmark/common/api/1.0.0-alpha04.txt b/benchmark/common/api/1.0.0-alpha04.txt
new file mode 100644
index 0000000..6849f29
--- /dev/null
+++ b/benchmark/common/api/1.0.0-alpha04.txt
@@ -0,0 +1,16 @@
+// Signature format: 3.0
+package androidx.benchmark {
+
+ public final class ArgumentsKt {
+ ctor public ArgumentsKt();
+ }
+
+ public final class BenchmarkState {
+ method public boolean keepRunning();
+ method public void pauseTiming();
+ method public static void reportData(String className, String testName, long totalRunTimeNs, java.util.List<java.lang.Long> dataNs, @IntRange(from=0) int warmupIterations, @IntRange(from=0) long thermalThrottleSleepSeconds, @IntRange(from=1) int repeatIterations);
+ method public void resumeTiming();
+ }
+
+}
+
diff --git a/benchmark/common/api/current.txt b/benchmark/common/api/current.txt
new file mode 100644
index 0000000..6849f29
--- /dev/null
+++ b/benchmark/common/api/current.txt
@@ -0,0 +1,16 @@
+// Signature format: 3.0
+package androidx.benchmark {
+
+ public final class ArgumentsKt {
+ ctor public ArgumentsKt();
+ }
+
+ public final class BenchmarkState {
+ method public boolean keepRunning();
+ method public void pauseTiming();
+ method public static void reportData(String className, String testName, long totalRunTimeNs, java.util.List<java.lang.Long> dataNs, @IntRange(from=0) int warmupIterations, @IntRange(from=0) long thermalThrottleSleepSeconds, @IntRange(from=1) int repeatIterations);
+ method public void resumeTiming();
+ }
+
+}
+
diff --git a/benchmark/api/res-1.0.0-alpha01.txt b/benchmark/common/api/res-1.0.0-alpha01.txt
similarity index 100%
rename from benchmark/api/res-1.0.0-alpha01.txt
rename to benchmark/common/api/res-1.0.0-alpha01.txt
diff --git a/benchmark/api/res-1.0.0-alpha02.txt b/benchmark/common/api/res-1.0.0-alpha02.txt
similarity index 100%
rename from benchmark/api/res-1.0.0-alpha02.txt
rename to benchmark/common/api/res-1.0.0-alpha02.txt
diff --git a/benchmark/api/res-1.0.0-alpha03.txt b/benchmark/common/api/res-1.0.0-alpha03.txt
similarity index 100%
rename from benchmark/api/res-1.0.0-alpha03.txt
rename to benchmark/common/api/res-1.0.0-alpha03.txt
diff --git a/benchmark/api/res-1.0.0-alpha04.txt b/benchmark/common/api/res-1.0.0-alpha04.txt
similarity index 100%
rename from benchmark/api/res-1.0.0-alpha04.txt
rename to benchmark/common/api/res-1.0.0-alpha04.txt
diff --git a/benchmark/api/restricted_1.0.0-alpha01.txt b/benchmark/common/api/restricted_1.0.0-alpha01.txt
similarity index 100%
rename from benchmark/api/restricted_1.0.0-alpha01.txt
rename to benchmark/common/api/restricted_1.0.0-alpha01.txt
diff --git a/benchmark/api/restricted_1.0.0-alpha02.txt b/benchmark/common/api/restricted_1.0.0-alpha02.txt
similarity index 100%
rename from benchmark/api/restricted_1.0.0-alpha02.txt
rename to benchmark/common/api/restricted_1.0.0-alpha02.txt
diff --git a/benchmark/api/restricted_1.0.0-alpha03.txt b/benchmark/common/api/restricted_1.0.0-alpha03.txt
similarity index 100%
rename from benchmark/api/restricted_1.0.0-alpha03.txt
rename to benchmark/common/api/restricted_1.0.0-alpha03.txt
diff --git a/benchmark/common/api/restricted_1.0.0-alpha04.txt b/benchmark/common/api/restricted_1.0.0-alpha04.txt
new file mode 100644
index 0000000..4165e3e
--- /dev/null
+++ b/benchmark/common/api/restricted_1.0.0-alpha04.txt
@@ -0,0 +1,32 @@
+// Signature format: 3.0
+package androidx.benchmark {
+
+ public final class ArgumentsKt {
+ ctor public ArgumentsKt();
+ }
+
+ public final class BenchmarkState {
+ ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public BenchmarkState();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public long getMin();
+ method public boolean keepRunning();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public inline boolean keepRunningInline();
+ method public void pauseTiming();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public void report(String fullClassName, String simpleClassName, String methodName);
+ method public static void reportData(String className, String testName, long totalRunTimeNs, java.util.List<java.lang.Long> dataNs, @IntRange(from=0) int warmupIterations, @IntRange(from=0) long thermalThrottleSleepSeconds, @IntRange(from=1) int repeatIterations);
+ method public void resumeTiming();
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final class IsolationActivity extends android.app.Activity {
+ method public void actuallyFinish();
+ field public static final androidx.benchmark.IsolationActivity.Companion! Companion;
+ }
+
+ public static final class IsolationActivity.Companion {
+ method @AnyThread public void finishSingleton();
+ method public boolean getResumed();
+ method @WorkerThread public void launchSingleton();
+ property public final boolean resumed;
+ }
+
+}
+
diff --git a/benchmark/common/api/restricted_current.txt b/benchmark/common/api/restricted_current.txt
new file mode 100644
index 0000000..4165e3e
--- /dev/null
+++ b/benchmark/common/api/restricted_current.txt
@@ -0,0 +1,32 @@
+// Signature format: 3.0
+package androidx.benchmark {
+
+ public final class ArgumentsKt {
+ ctor public ArgumentsKt();
+ }
+
+ public final class BenchmarkState {
+ ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public BenchmarkState();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public long getMin();
+ method public boolean keepRunning();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public inline boolean keepRunningInline();
+ method public void pauseTiming();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public void report(String fullClassName, String simpleClassName, String methodName);
+ method public static void reportData(String className, String testName, long totalRunTimeNs, java.util.List<java.lang.Long> dataNs, @IntRange(from=0) int warmupIterations, @IntRange(from=0) long thermalThrottleSleepSeconds, @IntRange(from=1) int repeatIterations);
+ method public void resumeTiming();
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final class IsolationActivity extends android.app.Activity {
+ method public void actuallyFinish();
+ field public static final androidx.benchmark.IsolationActivity.Companion! Companion;
+ }
+
+ public static final class IsolationActivity.Companion {
+ method @AnyThread public void finishSingleton();
+ method public boolean getResumed();
+ method @WorkerThread public void launchSingleton();
+ property public final boolean resumed;
+ }
+
+}
+
diff --git a/benchmark/build.gradle b/benchmark/common/build.gradle
similarity index 83%
rename from benchmark/build.gradle
rename to benchmark/common/build.gradle
index 386f71d..a6d74c9 100644
--- a/benchmark/build.gradle
+++ b/benchmark/common/build.gradle
@@ -26,20 +26,20 @@
}
dependencies {
- implementation(ANDROIDX_TEST_RULES)
- implementation(ANDROIDX_TEST_RUNNER)
implementation(KOTLIN_STDLIB)
implementation(SUPPORT_ANNOTATIONS)
+ implementation(ANDROIDX_TEST_MONITOR)
- androidTestImplementation(ANDROIDX_TEST_CORE)
+ androidTestImplementation(ANDROIDX_TEST_RULES)
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
+ androidTestImplementation(KOTLIN_TEST_COMMON)
}
androidx {
- name = "Android Benchmark"
+ name = "Android Benchmark Common"
publish = Publish.SNAPSHOT_AND_RELEASE
mavenVersion = LibraryVersions.BENCHMARK
mavenGroup = LibraryGroups.BENCHMARK
inceptionYear = "2018"
- description = "Android Benchmark"
+ description = "Android Benchmark Common"
}
diff --git a/benchmark/src/androidTest/AndroidManifest.xml b/benchmark/common/src/androidTest/AndroidManifest.xml
similarity index 86%
rename from benchmark/src/androidTest/AndroidManifest.xml
rename to benchmark/common/src/androidTest/AndroidManifest.xml
index f5ec776..bcc3cf4 100644
--- a/benchmark/src/androidTest/AndroidManifest.xml
+++ b/benchmark/common/src/androidTest/AndroidManifest.xml
@@ -16,11 +16,8 @@
-->
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
package="androidx.benchmark.test">
<application
- android:name="androidx.benchmark.ArgumentInjectingApplication">
- <activity android:name="android.app.Activity"/>
- </application>
+ android:name="androidx.benchmark.ArgumentInjectingApplication"/>
</manifest>
\ No newline at end of file
diff --git a/benchmark/src/androidTest/java/androidx/benchmark/ArgumentInjectingApplication.kt b/benchmark/common/src/androidTest/java/androidx/benchmark/ArgumentInjectingApplication.kt
similarity index 84%
copy from benchmark/src/androidTest/java/androidx/benchmark/ArgumentInjectingApplication.kt
copy to benchmark/common/src/androidTest/java/androidx/benchmark/ArgumentInjectingApplication.kt
index 30b3bd3..31f45ce 100644
--- a/benchmark/src/androidTest/java/androidx/benchmark/ArgumentInjectingApplication.kt
+++ b/benchmark/common/src/androidTest/java/androidx/benchmark/ArgumentInjectingApplication.kt
@@ -43,9 +43,12 @@
// Since these benchmark correctness tests run as part of the regular
// (non-performance-test) suite, they will have debuggable=true, won't be clock-locked,
// can run with low-battery or on an emulator, and code coverage enabled.
+ // We also don't have the activity up for these correctness tests, instead
+ // leaving testing that behavior to the junit4 module.
putString(
"androidx.benchmark.suppressErrors",
- "CODE-COVERAGE,DEBUGGABLE,EMULATOR,LOW-BATTERY,UNLOCKED"
+ "ACTIVITY-MISSING,CODE-COVERAGE,DEBUGGABLE,EMULATOR,LOW-BATTERY,UNLOCKED," +
+ "UNSUSTAINED-ACTIVITY-MISSING"
)
}
}
diff --git a/benchmark/src/androidTest/java/androidx/benchmark/BenchmarkStateTest.kt b/benchmark/common/src/androidTest/java/androidx/benchmark/BenchmarkStateTest.kt
similarity index 98%
rename from benchmark/src/androidTest/java/androidx/benchmark/BenchmarkStateTest.kt
rename to benchmark/common/src/androidTest/java/androidx/benchmark/BenchmarkStateTest.kt
index 2c49b0a..e8ac0ab 100644
--- a/benchmark/src/androidTest/java/androidx/benchmark/BenchmarkStateTest.kt
+++ b/benchmark/common/src/androidTest/java/androidx/benchmark/BenchmarkStateTest.kt
@@ -99,7 +99,7 @@
)
// check attribute presence and naming
- val prefix = Errors.WARNING_PREFIX
+ val prefix = Errors.PREFIX
assertNotNull(bundle.get("${prefix}min"))
assertNotNull(bundle.get("${prefix}mean"))
assertNotNull(bundle.get("${prefix}count"))
diff --git a/benchmark/src/androidTest/java/androidx/benchmark/CpuInfoTest.kt b/benchmark/common/src/androidTest/java/androidx/benchmark/CpuInfoTest.kt
similarity index 100%
rename from benchmark/src/androidTest/java/androidx/benchmark/CpuInfoTest.kt
rename to benchmark/common/src/androidTest/java/androidx/benchmark/CpuInfoTest.kt
diff --git a/benchmark/src/androidTest/java/androidx/benchmark/ResultWriterTest.kt b/benchmark/common/src/androidTest/java/androidx/benchmark/ResultWriterTest.kt
similarity index 68%
rename from benchmark/src/androidTest/java/androidx/benchmark/ResultWriterTest.kt
rename to benchmark/common/src/androidTest/java/androidx/benchmark/ResultWriterTest.kt
index 20ed927..5783636 100644
--- a/benchmark/src/androidTest/java/androidx/benchmark/ResultWriterTest.kt
+++ b/benchmark/common/src/androidTest/java/androidx/benchmark/ResultWriterTest.kt
@@ -24,6 +24,7 @@
import org.junit.rules.TemporaryFolder
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
+import kotlin.test.assertTrue
@SmallTest
@RunWith(JUnit4::class)
@@ -66,7 +67,7 @@
fun validateJson() {
val tempFile = tempFolder.newFile()
- val sustainedPerformanceModeInUse = AndroidBenchmarkRunner.sustainedPerformanceModeInUse
+ val sustainedPerformanceModeInUse = IsolationActivity.sustainedPerformanceModeInUse
ResultWriter.writeReport(tempFile, listOf(reportA, reportB))
assertEquals(
@@ -90,6 +91,7 @@
"benchmarks": [
{
"name": "MethodA",
+ "params": {},
"className": "package.Class1",
"totalRunTimeNs": 900000000,
"metrics": {
@@ -110,6 +112,7 @@
},
{
"name": "MethodB",
+ "params": {},
"className": "package.Class2",
"totalRunTimeNs": 900000000,
"metrics": {
@@ -134,4 +137,61 @@
tempFile.readText()
)
}
+
+ @Test
+ fun validateJsonWithParams() {
+ val reportWithParams = BenchmarkState.Report(
+ testName = "MethodWithParams[number=2,primeNumber=true]",
+ className = "package.Class",
+ totalRunTimeNs = 900000000,
+ data = listOf(100, 101, 102),
+ repeatIterations = 100000,
+ thermalThrottleSleepSeconds = 90000000,
+ warmupIterations = 8000
+ )
+
+ val tempFile = tempFolder.newFile()
+ ResultWriter.writeReport(tempFile, listOf(reportWithParams))
+ val reportText = tempFile.readText()
+
+ assertTrue {
+ reportText.contains(
+ """
+ | "name": "MethodWithParams[number=2,primeNumber=true]",
+ | "params": {
+ | "number": "2",
+ | "primeNumber": "true"
+ | },
+ """.trimMargin()
+ )
+ }
+ }
+
+ @Test
+ fun validateJsonWithInvalidParams() {
+ val reportWithInvalidParams = BenchmarkState.Report(
+ testName = "MethodWithParams[number=2,=true,]",
+ className = "package.Class",
+ totalRunTimeNs = 900000000,
+ data = listOf(100, 101, 102),
+ repeatIterations = 100000,
+ thermalThrottleSleepSeconds = 90000000,
+ warmupIterations = 8000
+ )
+
+ val tempFile = tempFolder.newFile()
+ ResultWriter.writeReport(tempFile, listOf(reportWithInvalidParams))
+ val reportText = tempFile.readText()
+
+ assertTrue {
+ reportText.contains(
+ """
+ | "name": "MethodWithParams[number=2,=true,]",
+ | "params": {
+ | "number": "2"
+ | },
+ """.trimMargin()
+ )
+ }
+ }
}
diff --git a/benchmark/src/androidTest/java/androidx/benchmark/StatsTest.kt b/benchmark/common/src/androidTest/java/androidx/benchmark/StatsTest.kt
similarity index 100%
rename from benchmark/src/androidTest/java/androidx/benchmark/StatsTest.kt
rename to benchmark/common/src/androidTest/java/androidx/benchmark/StatsTest.kt
diff --git a/benchmark/src/androidTest/java/androidx/benchmark/WarmupManagerTest.kt b/benchmark/common/src/androidTest/java/androidx/benchmark/WarmupManagerTest.kt
similarity index 100%
rename from benchmark/src/androidTest/java/androidx/benchmark/WarmupManagerTest.kt
rename to benchmark/common/src/androidTest/java/androidx/benchmark/WarmupManagerTest.kt
diff --git a/benchmark/src/main/AndroidManifest.xml b/benchmark/common/src/main/AndroidManifest.xml
similarity index 100%
rename from benchmark/src/main/AndroidManifest.xml
rename to benchmark/common/src/main/AndroidManifest.xml
diff --git a/benchmark/src/main/java/androidx/benchmark/Arguments.kt b/benchmark/common/src/main/java/androidx/benchmark/Arguments.kt
similarity index 81%
rename from benchmark/src/main/java/androidx/benchmark/Arguments.kt
rename to benchmark/common/src/main/java/androidx/benchmark/Arguments.kt
index e6163bf..fb89659 100644
--- a/benchmark/src/main/java/androidx/benchmark/Arguments.kt
+++ b/benchmark/common/src/main/java/androidx/benchmark/Arguments.kt
@@ -32,15 +32,20 @@
val additionalTestOutputDir: String?
val outputEnable: Boolean
val startupMode: Boolean
+ val dryRunMode: Boolean
val suppressedErrors: Set<String>
init {
val prefix = "androidx.benchmark"
val arguments = argumentSource ?: InstrumentationRegistry.getArguments()
- startupMode = arguments.getString("$prefix.startupMode.enable")?.toBoolean() ?: false
+ dryRunMode = arguments.getString("$prefix.dryRunMode.enable")?.toBoolean() ?: false
- outputEnable = arguments.getString("$prefix.output.enable")?.toBoolean() ?: false
+ startupMode = !dryRunMode &&
+ (arguments.getString("$prefix.startupMode.enable")?.toBoolean() ?: false)
+
+ outputEnable = !dryRunMode &&
+ (arguments.getString("$prefix.output.enable")?.toBoolean() ?: false)
// Transform comma-delimited list into set of suppressed errors
// E.g. "DEBUGGABLE, UNLOCKED" -> setOf("DEBUGGABLE", "UNLOCKED")
diff --git a/benchmark/src/main/java/androidx/benchmark/BenchmarkState.kt b/benchmark/common/src/main/java/androidx/benchmark/BenchmarkState.kt
similarity index 86%
rename from benchmark/src/main/java/androidx/benchmark/BenchmarkState.kt
rename to benchmark/common/src/main/java/androidx/benchmark/BenchmarkState.kt
index 4e3b050..6bb4a7e 100644
--- a/benchmark/src/main/java/androidx/benchmark/BenchmarkState.kt
+++ b/benchmark/common/src/main/java/androidx/benchmark/BenchmarkState.kt
@@ -22,9 +22,10 @@
import android.os.Debug
import android.util.Log
import androidx.annotation.IntRange
+import androidx.annotation.RestrictTo
import androidx.annotation.VisibleForTesting
+import androidx.benchmark.Errors.PREFIX
import androidx.test.platform.app.InstrumentationRegistry
-import org.junit.Assert.fail
import java.io.File
import java.text.NumberFormat
import java.util.ArrayList
@@ -53,7 +54,12 @@
*
* @see BenchmarkRule#getState()
*/
-class BenchmarkState internal constructor() {
+class BenchmarkState {
+ /** @hide */
+ @Suppress("ConvertSecondaryConstructorToPrimary")
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ constructor() {}
+
private var warmupIteration = 0 // increasing iteration count during warmup
/**
@@ -122,6 +128,14 @@
}
/**
+ * Used for testing in other modules
+ *
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ fun getMin(): Long = stats.min
+
+ /**
* Stops the benchmark timer.
*
* This method can be called only when the timer is running.
@@ -141,6 +155,8 @@
* }
* ```
*
+ * @throws [IllegalStateException] if the benchmark is already paused.
+ *
* @see resumeTiming
*/
fun pauseTiming() {
@@ -171,6 +187,9 @@
* processBitmap(input);
* }
* }
+ *
+ * @throws [IllegalStateException] if the benchmark is already running.
+ *
* ```
*
* @see pauseTiming
@@ -202,14 +221,7 @@
Log.d(TAG, "Tracing to: " + f.absolutePath)
Debug.startMethodTracingSampling(f.absolutePath, 16 * 1024 * 1024, 100)
}
- maxIterations = if (Arguments.startupMode) {
- // never average multiple loops together in startupMode
- 1
- } else {
- val idealIterations =
- (TARGET_TEST_DURATION_NS / warmupManager.estimatedIterationTime).toInt()
- idealIterations.coerceIn(MIN_TEST_ITERATIONS, MAX_TEST_ITERATIONS)
- }
+ maxIterations = OVERRIDE_ITERATIONS ?: computeIterationsFromWarmup()
pausedDurationNs = 0
iterationsRemaining = maxIterations
repeatCount = 0
@@ -218,6 +230,12 @@
startTimeNs = System.nanoTime()
}
+ private fun computeIterationsFromWarmup(): Int {
+ val idealIterations =
+ (TARGET_TEST_DURATION_NS / warmupManager.estimatedIterationTime).toInt()
+ return idealIterations.coerceIn(MIN_TEST_ITERATIONS, MAX_TEST_ITERATIONS)
+ }
+
private fun startNextTestRun(): Boolean {
val currentTime = System.nanoTime()
@@ -258,10 +276,12 @@
* This codepath uses exclusively @JvmField/const members, so there are no method calls at all
* in the inlined loop. On recent Android Platform versions, ART inlines these accessors anyway,
* but we want to be sure it's as simple as possible.
+ *
+ * @hide
*/
@Suppress("NOTHING_TO_INLINE")
- @PublishedApi
- internal inline fun keepRunningInline(): Boolean {
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ inline fun keepRunningInline(): Boolean {
if (iterationsRemaining > 1) {
iterationsRemaining--
return true
@@ -292,12 +312,12 @@
when (state) {
NOT_STARTED -> {
if (Errors.UNSUPPRESSED_WARNING_MESSAGE != null) {
- fail(Errors.UNSUPPRESSED_WARNING_MESSAGE)
+ throw AssertionError(Errors.UNSUPPRESSED_WARNING_MESSAGE)
}
-
if (!firstBenchmark && Arguments.startupMode) {
- fail("Error - multiple benchmarks in startup mode. Only one benchmark " +
- "may be run per 'am instrument' call, to ensure result isolation.")
+ throw AssertionError("Error - multiple benchmarks in startup mode. Only one " +
+ "benchmark may be run per 'am instrument' call, to ensure result " +
+ "isolation.")
}
firstBenchmark = false
@@ -307,13 +327,13 @@
}
if (performThrottleChecks &&
!CpuInfo.locked &&
- !AndroidBenchmarkRunner.sustainedPerformanceModeInUse &&
+ !IsolationActivity.sustainedPerformanceModeInUse &&
!Errors.isEmulator
) {
ThrottleDetector.computeThrottleBaseline()
}
- if (Arguments.startupMode) {
+ if (Arguments.dryRunMode || Arguments.startupMode) {
beginBenchmark()
} else {
beginWarmup()
@@ -393,17 +413,16 @@
Log.i(TAG, key + summaryLine())
val status = Bundle()
- val prefix = Errors.WARNING_PREFIX
- status.putLong("${prefix}median", stats.median)
- status.putLong("${prefix}mean", stats.mean.toLong())
- status.putLong("${prefix}min", stats.min)
- status.putLong("${prefix}standardDeviation", stats.standardDeviation.toLong())
- status.putLong("${prefix}count", maxIterations.toLong())
+ status.putLong("${PREFIX}median", stats.median)
+ status.putLong("${PREFIX}mean", stats.mean.toLong())
+ status.putLong("${PREFIX}min", stats.min)
+ status.putLong("${PREFIX}standardDeviation", stats.standardDeviation.toLong())
+ status.putLong("${PREFIX}count", maxIterations.toLong())
status.putIdeSummaryLine(key, stats.min)
return status
}
- internal fun sendStatus(testName: String) {
+ private fun sendStatus(testName: String) {
val bundle = getFullStatusReport(testName)
InstrumentationRegistry.getInstrumentation().sendStatus(Activity.RESULT_OK, bundle)
}
@@ -420,6 +439,26 @@
else -> false
}
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ fun report(
+ fullClassName: String,
+ simpleClassName: String,
+ methodName: String
+ ) {
+ val fullTestName = "$PREFIX$simpleClassName.$methodName"
+ sendStatus(fullTestName)
+
+ ResultWriter.appendReport(
+ getReport(
+ testName = PREFIX + methodName,
+ className = fullClassName
+ )
+ )
+ }
+
internal companion object {
private const val TAG = "Benchmark"
private const val STUDIO_OUTPUT_KEY_PREFIX = "android.studio.display."
@@ -434,7 +473,15 @@
// Values determined empirically.
@VisibleForTesting
- internal val REPEAT_COUNT = if (Arguments.startupMode) 10 else 50
+ internal val REPEAT_COUNT = when {
+ Arguments.dryRunMode -> 1
+ Arguments.startupMode -> 10
+ else -> 50
+ }
+ private val OVERRIDE_ITERATIONS = when {
+ Arguments.dryRunMode || Arguments.startupMode -> 1
+ else -> null
+ }
private val TARGET_TEST_DURATION_NS = TimeUnit.MICROSECONDS.toNanos(500)
private const val MAX_TEST_ITERATIONS = 1000000
private const val MIN_TEST_ITERATIONS = 1
@@ -483,7 +530,7 @@
// Report value to Studio console
val bundle = Bundle()
- val fullTestName = Errors.WARNING_PREFIX +
+ val fullTestName = Errors.PREFIX +
if (className.isNotEmpty()) "$className.$testName" else testName
bundle.putIdeSummaryLine(fullTestName, report.stats.min)
InstrumentationRegistry.getInstrumentation().sendStatus(Activity.RESULT_OK, bundle)
diff --git a/benchmark/src/main/java/androidx/benchmark/CpuInfo.kt b/benchmark/common/src/main/java/androidx/benchmark/CpuInfo.kt
similarity index 100%
rename from benchmark/src/main/java/androidx/benchmark/CpuInfo.kt
rename to benchmark/common/src/main/java/androidx/benchmark/CpuInfo.kt
diff --git a/benchmark/src/main/java/androidx/benchmark/Errors.kt b/benchmark/common/src/main/java/androidx/benchmark/Errors.kt
similarity index 90%
rename from benchmark/src/main/java/androidx/benchmark/Errors.kt
rename to benchmark/common/src/main/java/androidx/benchmark/Errors.kt
index 41c4e90e..ce47af7 100644
--- a/benchmark/src/main/java/androidx/benchmark/Errors.kt
+++ b/benchmark/common/src/main/java/androidx/benchmark/Errors.kt
@@ -26,7 +26,7 @@
import java.io.File
/**
- * Lazy-initialized test-suite global state for warnings around measurement inaccuracy.
+ * Lazy-initialized test-suite global state for errors around measurement inaccuracy.
*/
internal object Errors {
/**
@@ -44,7 +44,7 @@
private const val TAG = "Benchmark"
- val WARNING_PREFIX: String
+ val PREFIX: String
val UNSUPPRESSED_WARNING_MESSAGE: String?
private var warningString: String? = null
@@ -147,23 +147,24 @@
}
if (!CpuInfo.locked &&
- AndroidBenchmarkRunner.isSustainedPerformanceModeSupported() &&
- !AndroidBenchmarkRunner.sustainedPerformanceModeInUse
+ IsolationActivity.isSustainedPerformanceModeSupported() &&
+ !IsolationActivity.sustainedPerformanceModeInUse
) {
- warningPrefix += "UNSUSTAINED-RUNNER-MISSING_"
+ warningPrefix += "UNSUSTAINED-ACTIVITY-MISSING_"
warningString += """
- |WARNING: Cannot use SustainedPerformanceMode without AndroidBenchmarkRunner
+ |WARNING: Cannot use SustainedPerformanceMode without IsolationActivity
| Benchmark running on device that supports Window.setSustainedPerformanceMode,
- | but not using the AndroidBenchmarkRunner. This runner is required to limit
- | CPU clock max frequency, to prevent thermal throttling. To fix this, add the
- | following to your benchmark module-level build.gradle:
+ | but not launching IsolationActivity via the AndroidBenchmarkRunner. This
+ | Activity is required to limit CPU clock max frequency, to prevent thermal
+ | throttling. To fix this, add the following to your benchmark module-level
+ | build.gradle:
| android.defaultConfig.testInstrumentationRunner
| = "androidx.benchmark.AndroidBenchmarkRunner"
""".trimMarginWrapNewlines()
- } else if (!AndroidBenchmarkRunner.runnerInUse) {
- warningPrefix += "RUNNER-MISSING_"
+ } else if (IsolationActivity.singleton.get() == null) {
+ warningPrefix += "ACTIVITY-MISSING_"
warningString += """
- |WARNING: Not using AndroidBenchmarkRunner
+ |WARNING: Not using IsolationActivity via AndroidBenchmarkRunner
| AndroidBenchmarkRunner should be used to isolate benchmarks from interference
| from other visible apps. To fix this, add the following to your module-level
| build.gradle:
@@ -189,13 +190,13 @@
""".trimMarginWrapNewlines()
}
- WARNING_PREFIX = warningPrefix
+ PREFIX = warningPrefix
if (warningString.isNotEmpty()) {
this.warningString = warningString
warningString.split("\n").map { Log.w(TAG, it) }
}
- val warningSet = WARNING_PREFIX
+ val warningSet = PREFIX
.split('_')
.filter { it.isNotEmpty() }
.toSet()
diff --git a/benchmark/common/src/main/java/androidx/benchmark/IsolationActivity.kt b/benchmark/common/src/main/java/androidx/benchmark/IsolationActivity.kt
new file mode 100644
index 0000000..bf34af0
--- /dev/null
+++ b/benchmark/common/src/main/java/androidx/benchmark/IsolationActivity.kt
@@ -0,0 +1,201 @@
+/*
+ * 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.benchmark
+
+import android.annotation.SuppressLint
+import android.app.Activity
+import android.app.Application
+import android.app.KeyguardManager
+import android.content.Context
+import android.content.Intent
+import android.os.Build
+import android.os.Bundle
+import android.os.PowerManager
+import android.os.Process
+import android.util.Log
+import android.view.WindowManager
+import android.widget.TextView
+import androidx.annotation.AnyThread
+import androidx.annotation.RestrictTo
+import androidx.annotation.WorkerThread
+import androidx.test.platform.app.InstrumentationRegistry
+import java.util.concurrent.atomic.AtomicReference
+import kotlin.concurrent.thread
+
+/**
+ * Simple opaque activity used to reduce benchmark interference from other windows.
+ *
+ * For example, sources of potential interference:
+ * - live wallpaper rendering
+ * - homescreen widget updates
+ * - hotword detection
+ * - status bar repaints
+ * - running in background (some cores may be foreground-app only)
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+class IsolationActivity : android.app.Activity() {
+ private var destroyed = false
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.isolation_activity)
+
+ // disable launch animation
+ overridePendingTransition(0, 0)
+
+ if (firstInit) {
+ if (!CpuInfo.locked && isSustainedPerformanceModeSupported()) {
+ sustainedPerformanceModeInUse = true
+ }
+ application.registerActivityLifecycleCallbacks(activityLifecycleCallbacks)
+
+ // trigger the one missed lifecycle event, from registering the callbacks late
+ activityLifecycleCallbacks.onActivityCreated(this, savedInstanceState)
+
+ if (sustainedPerformanceModeInUse) {
+ // Keep at least one core busy. Together with a single threaded benchmark, this
+ // makes the process get multi-threaded setSustainedPerformanceMode.
+ //
+ // We want to keep to the relatively lower clocks of the multi-threaded benchmark
+ // mode to avoid any benchmarks running at higher clocks than any others.
+ //
+ // Note, thread names have 15 char max in Systrace
+ thread(name = "BenchSpinThread") {
+ Process.setThreadPriority(Process.THREAD_PRIORITY_LOWEST)
+ while (true) {
+ }
+ }
+ }
+ firstInit = false
+ }
+
+ val old = singleton.getAndSet(this)
+ if (old != null && !old.destroyed && !old.isFinishing) {
+ throw IllegalStateException("Only one IsolationActivity should exist")
+ }
+
+ findViewById<TextView>(R.id.clock_state).text = when {
+ CpuInfo.locked -> "Locked Clocks"
+ sustainedPerformanceModeInUse -> "Sustained Performance Mode"
+ else -> ""
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+ resumed = true
+ }
+
+ override fun onPause() {
+ super.onPause()
+ resumed = false
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ destroyed = true
+ }
+
+ /** finish is ignored! we defer until [actuallyFinish] is called. */
+ override fun finish() {
+ }
+
+ fun actuallyFinish() {
+ // disable close animation
+ overridePendingTransition(0, 0)
+ super.finish()
+ }
+
+ companion object {
+ private const val TAG = "Benchmark"
+ internal val singleton = AtomicReference<IsolationActivity>()
+ private var firstInit = true
+ internal var sustainedPerformanceModeInUse = false
+ private set
+ var resumed = false
+ private set
+
+ @WorkerThread
+ fun launchSingleton() {
+ val intent = Intent(Intent.ACTION_MAIN).apply {
+ Log.d(TAG, "launching Benchmark IsolationActivity")
+ setClassName(
+ InstrumentationRegistry.getInstrumentation().targetContext.packageName,
+ IsolationActivity::class.java.name
+ )
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
+ }
+ InstrumentationRegistry.getInstrumentation().startActivitySync(intent)
+ }
+
+ @AnyThread
+ fun finishSingleton() {
+ Log.d(TAG, "Benchmark runner being destroyed, tearing down activities")
+ singleton.getAndSet(null)?.apply {
+ runOnUiThread {
+ actuallyFinish()
+ }
+ }
+ }
+
+ internal fun isSustainedPerformanceModeSupported(): Boolean =
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ val context = InstrumentationRegistry.getInstrumentation().targetContext
+ val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
+ powerManager.isSustainedPerformanceModeSupported
+ } else {
+ false
+ }
+
+ private val activityLifecycleCallbacks = object : Application.ActivityLifecycleCallbacks {
+ @SuppressLint("NewApi") // window API guarded by [isSustainedPerformanceModeSupported]
+ override fun onActivityCreated(activity: Activity, bundle: Bundle?) {
+ if (sustainedPerformanceModeInUse) {
+ activity.window.setSustainedPerformanceMode(true)
+ }
+
+ // Forcibly wake the device, and keep the screen on to prevent benchmark failures.
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
+ val keyguardManager =
+ activity.getSystemService(KEYGUARD_SERVICE) as KeyguardManager
+ keyguardManager.requestDismissKeyguard(activity, null)
+ activity.setShowWhenLocked(true)
+ activity.setTurnScreenOn(true)
+ activity.window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
+ } else {
+ @Suppress("DEPRECATION")
+ activity.window.addFlags(
+ WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+ or WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
+ or WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
+ or WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
+ )
+ }
+ }
+
+ override fun onActivityDestroyed(activity: Activity) {}
+ override fun onActivitySaveInstanceState(activity: Activity, bundle: Bundle) {}
+ override fun onActivityStarted(activity: Activity) {}
+ override fun onActivityStopped(activity: Activity) {}
+ override fun onActivityPaused(activity: Activity) {}
+ override fun onActivityResumed(activity: Activity) {}
+ }
+ }
+}
\ No newline at end of file
diff --git a/benchmark/src/main/java/androidx/benchmark/MemInfo.kt b/benchmark/common/src/main/java/androidx/benchmark/MemInfo.kt
similarity index 100%
rename from benchmark/src/main/java/androidx/benchmark/MemInfo.kt
rename to benchmark/common/src/main/java/androidx/benchmark/MemInfo.kt
diff --git a/benchmark/src/main/java/androidx/benchmark/ResultWriter.kt b/benchmark/common/src/main/java/androidx/benchmark/ResultWriter.kt
similarity index 67%
rename from benchmark/src/main/java/androidx/benchmark/ResultWriter.kt
rename to benchmark/common/src/main/java/androidx/benchmark/ResultWriter.kt
index 6f0e516..d71d75b 100644
--- a/benchmark/src/main/java/androidx/benchmark/ResultWriter.kt
+++ b/benchmark/common/src/main/java/androidx/benchmark/ResultWriter.kt
@@ -23,6 +23,7 @@
import androidx.annotation.VisibleForTesting
import androidx.test.platform.app.InstrumentationRegistry
import java.io.File
+import java.io.IOException
internal object ResultWriter {
@VisibleForTesting
@@ -50,7 +51,21 @@
file.run {
if (!exists()) {
parentFile?.mkdirs()
- createNewFile()
+ try {
+ createNewFile()
+ } catch (exception: IOException) {
+ throw IOException(
+ """
+ Failed to create file for benchmark report. Make sure the
+ instrumentation argument additionalOutputDir is set to a writable
+ directory on device. If using a version of Android Gradle Plugin that
+ doesn't support additionalOutputDir, ensure your app's manifest file
+ enables legacy storage behavior by adding the application attribute:
+ android:requestLegacyExternalStorage="true"
+ """.trimIndent(),
+ exception
+ )
+ }
}
val writer = JsonWriter(bufferedWriter())
@@ -65,7 +80,7 @@
.name("cpuMaxFreqHz").value(CpuInfo.maxFreqHz)
.name("memTotalBytes").value(MemInfo.memTotalBytes)
.name("sustainedPerformanceModeEnabled")
- .value(AndroidBenchmarkRunner.sustainedPerformanceModeInUse)
+ .value(IsolationActivity.sustainedPerformanceModeInUse)
writer.endObject()
writer.name("benchmarks").beginArray()
@@ -91,16 +106,41 @@
private fun JsonWriter.reportObject(report: BenchmarkState.Report): JsonWriter {
beginObject()
.name("name").value(report.testName)
+ .name("params").paramsObject(report)
.name("className").value(report.className)
.name("totalRunTimeNs").value(report.totalRunTimeNs)
.name("metrics").metricsObject(report)
.name("warmupIterations").value(report.warmupIterations)
.name("repeatIterations").value(report.repeatIterations)
.name("thermalThrottleSleepSeconds").value(report.thermalThrottleSleepSeconds)
-
return endObject()
}
+ private fun JsonWriter.paramsObject(report: BenchmarkState.Report): JsonWriter {
+ beginObject()
+ getParams(report.testName).forEach { name(it.key).value(it.value) }
+ return endObject()
+ }
+
+ private fun getParams(testName: String): Map<String, String> {
+ val parameterStrStart = testName.indexOf('[')
+ val parameterStrEnd = testName.lastIndexOf(']')
+
+ val params = HashMap<String, String>()
+ if (parameterStrStart >= 0 && parameterStrEnd >= 0) {
+ val paramListString = testName.substring(parameterStrStart + 1, parameterStrEnd)
+ paramListString.split(",").forEach { paramString ->
+ val separatorIndex = paramString.indexOfFirst { it == ':' || it == '=' }
+ if (separatorIndex in 1 until paramString.length - 1) {
+ val key = paramString.substring(0, separatorIndex)
+ val value = paramString.substring(separatorIndex + 1)
+ params[key] = value
+ }
+ }
+ }
+ return params
+ }
+
private fun JsonWriter.metricsObject(report: BenchmarkState.Report): JsonWriter {
beginObject()
diff --git a/benchmark/src/main/java/androidx/benchmark/Stats.kt b/benchmark/common/src/main/java/androidx/benchmark/Stats.kt
similarity index 100%
rename from benchmark/src/main/java/androidx/benchmark/Stats.kt
rename to benchmark/common/src/main/java/androidx/benchmark/Stats.kt
diff --git a/benchmark/src/main/java/androidx/benchmark/ThrottleDetector.kt b/benchmark/common/src/main/java/androidx/benchmark/ThrottleDetector.kt
similarity index 100%
rename from benchmark/src/main/java/androidx/benchmark/ThrottleDetector.kt
rename to benchmark/common/src/main/java/androidx/benchmark/ThrottleDetector.kt
diff --git a/benchmark/src/main/java/androidx/benchmark/WarmupManager.kt b/benchmark/common/src/main/java/androidx/benchmark/WarmupManager.kt
similarity index 100%
rename from benchmark/src/main/java/androidx/benchmark/WarmupManager.kt
rename to benchmark/common/src/main/java/androidx/benchmark/WarmupManager.kt
diff --git a/benchmark/src/main/res/drawable-nodpi/logo.png b/benchmark/common/src/main/res/drawable-nodpi/logo.png
similarity index 100%
rename from benchmark/src/main/res/drawable-nodpi/logo.png
rename to benchmark/common/src/main/res/drawable-nodpi/logo.png
Binary files differ
diff --git a/benchmark/src/main/res/layout/isolation_activity.xml b/benchmark/common/src/main/res/layout/isolation_activity.xml
similarity index 100%
rename from benchmark/src/main/res/layout/isolation_activity.xml
rename to benchmark/common/src/main/res/layout/isolation_activity.xml
diff --git a/benchmark/gradle-plugin/build.gradle b/benchmark/gradle-plugin/build.gradle
index 6ce036b..755ede5 100644
--- a/benchmark/gradle-plugin/build.gradle
+++ b/benchmark/gradle-plugin/build.gradle
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-
+import androidx.build.BuildServerConfigurationKt
import androidx.build.CompilationTarget
import androidx.build.LibraryGroups
import androidx.build.LibraryVersions
@@ -41,7 +41,7 @@
dependencies {
implementation gradleApi()
- implementation(build_libs.gradle)
+ implementation(ANDROID_GRADLE_PLUGIN)
implementation(KOTLIN_STDLIB)
testImplementation gradleTestKit()
@@ -70,6 +70,17 @@
tasks["compileTestJava"].dependsOn generateSdkResource
+task buildOnServer(type: Copy) {
+ from {
+ def f = project.file("src/main/resources/scripts/lockClocks.sh")
+ if (!f.exists()) {
+ throw new GradleException(f.toString() + " does not exist")
+ }
+ return f
+ }
+ destinationDir BuildServerConfigurationKt.getDistributionDirectory(rootProject)
+}
+
gradlePlugin {
plugins {
benchmark {
diff --git a/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/BenchmarkPlugin.kt b/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/BenchmarkPlugin.kt
index 7365bc8..608e3f1 100644
--- a/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/BenchmarkPlugin.kt
+++ b/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/BenchmarkPlugin.kt
@@ -17,8 +17,8 @@
package androidx.benchmark.gradle
import com.android.build.gradle.AppExtension
-import com.android.build.gradle.BaseExtension
import com.android.build.gradle.LibraryExtension
+import com.android.build.gradle.TestedExtension
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.tasks.StopExecutionException
@@ -57,15 +57,20 @@
private fun configureWithAndroidPlugin(project: Project) {
if (!foundAndroidPlugin) {
foundAndroidPlugin = true
- val extension = project.extensions.getByType(BaseExtension::class.java)
+ val extension = project.extensions.getByType(TestedExtension::class.java)
configureWithAndroidExtension(project, extension)
}
}
- private fun configureWithAndroidExtension(project: Project, extension: BaseExtension) {
+ private fun configureWithAndroidExtension(project: Project, extension: TestedExtension) {
val defaultConfig = extension.defaultConfig
val testInstrumentationArgs = defaultConfig.testInstrumentationRunnerArguments
+ defaultConfig.testInstrumentationRunner = "androidx.benchmark.junit4.AndroidBenchmarkRunner"
+
+ // disable overhead from test coverage by default, even if we use a debug variant
+ extension.buildTypes.getByName("debug").isTestCoverageEnabled = false
+
// Registering this block as a configureEach callback is only necessary because Studio skips
// Gradle if there are no changes, which stops this plugin from being re-applied.
var enabledOutput = false
@@ -88,11 +93,17 @@
}
}
- project.tasks.register("lockClocks", LockClocksTask::class.java).configure {
- it.adbPath.set(extension.adbExecutable.absolutePath)
+ if (project.rootProject.tasks.findByName("lockClocks") == null) {
+ project.rootProject.tasks.register("lockClocks", LockClocksTask::class.java).configure {
+ it.adbPath.set(extension.adbExecutable.absolutePath)
+ }
}
- project.tasks.register("unlockClocks", UnlockClocksTask::class.java).configure {
- it.adbPath.set(extension.adbExecutable.absolutePath)
+
+ if (project.rootProject.tasks.findByName("unlockClocks") == null) {
+ project.rootProject.tasks.register("unlockClocks", UnlockClocksTask::class.java)
+ .configure {
+ it.adbPath.set(extension.adbExecutable.absolutePath)
+ }
}
val extensionVariants = when (extension) {
diff --git a/benchmark/gradle-plugin/src/test/kotlin/androidx/benchmark/gradle/BenchmarkPluginTest.kt b/benchmark/gradle-plugin/src/test/kotlin/androidx/benchmark/gradle/BenchmarkPluginTest.kt
index 73a987d..204096b 100644
--- a/benchmark/gradle-plugin/src/test/kotlin/androidx/benchmark/gradle/BenchmarkPluginTest.kt
+++ b/benchmark/gradle-plugin/src/test/kotlin/androidx/benchmark/gradle/BenchmarkPluginTest.kt
@@ -326,4 +326,111 @@
val argsOutput = gradleRunner.withArguments("printInstrumentationArgs").build()
assertTrue { argsOutput.output.contains("no-isolated-storage:1") }
}
+
+ @Test
+ fun applyPluginDefaultAgpProperties() {
+ buildFile.writeText(
+ """
+ import com.android.build.gradle.TestedExtension
+
+ plugins {
+ id('com.android.library')
+ id('androidx.benchmark')
+ }
+
+ repositories {
+ maven { url "$prebuiltsRepo/androidx/external" }
+ maven { url "$prebuiltsRepo/androidx/internal" }
+ }
+
+ android {
+ compileSdkVersion $compileSdkVersion
+ buildToolsVersion "$buildToolsVersion"
+
+ defaultConfig {
+ minSdkVersion $minSdkVersion
+ }
+ }
+
+ dependencies {
+ androidTestImplementation "androidx.benchmark:benchmark:1.0.0-alpha01"
+
+ }
+
+ tasks.register("printTestInstrumentationRunner") {
+ println android.defaultConfig.testInstrumentationRunner
+ }
+
+ tasks.register("printTestCoverageEnabled") {
+ def extension = project.extensions.getByType(TestedExtension)
+ println extension.buildTypes.getByName("debug").testCoverageEnabled
+ }
+ """.trimIndent()
+ )
+
+ val runnerOutput = gradleRunner.withArguments("printTestInstrumentationRunner").build()
+ assertTrue {
+ runnerOutput.output.contains("androidx.benchmark.junit4.AndroidBenchmarkRunner")
+ }
+
+ val codeCoverageOutput = gradleRunner.withArguments("printTestCoverageEnabled").build()
+ assertTrue { codeCoverageOutput.output.contains("false") }
+ }
+
+ @Test
+ fun applyPluginOverrideAgpProperties() {
+ buildFile.writeText(
+ """
+ import com.android.build.gradle.TestedExtension
+
+ plugins {
+ id('com.android.library')
+ id('androidx.benchmark')
+ }
+
+ repositories {
+ maven { url "$prebuiltsRepo/androidx/external" }
+ maven { url "$prebuiltsRepo/androidx/internal" }
+ }
+
+ android {
+ compileSdkVersion $compileSdkVersion
+ buildToolsVersion "$buildToolsVersion"
+
+ defaultConfig {
+ minSdkVersion $minSdkVersion
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ debug {
+ testCoverageEnabled = true
+ }
+ }
+ }
+
+ dependencies {
+ androidTestImplementation "androidx.benchmark:benchmark:1.0.0-alpha01"
+
+ }
+
+ tasks.register("printTestInstrumentationRunner") {
+ println android.defaultConfig.testInstrumentationRunner
+ }
+
+ tasks.register("printTestCoverageEnabled") {
+ def extension = project.extensions.getByType(TestedExtension)
+ println extension.buildTypes.getByName("debug").testCoverageEnabled
+ }
+ """.trimIndent()
+ )
+
+ val runnerOutput = gradleRunner.withArguments("printTestInstrumentationRunner").build()
+ assertTrue {
+ runnerOutput.output.contains("androidx.test.runner.AndroidJUnitRunner")
+ }
+
+ val codeCoverageOutput = gradleRunner.withArguments("printTestCoverageEnabled").build()
+ assertTrue { codeCoverageOutput.output.contains("true") }
+ }
}
diff --git a/benchmark/integration-tests/dry-run-benchmark/.gitignore b/benchmark/integration-tests/dry-run-benchmark/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/benchmark/integration-tests/dry-run-benchmark/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/benchmark/integration-tests/dry-run-benchmark/README.md b/benchmark/integration-tests/dry-run-benchmark/README.md
new file mode 100644
index 0000000..3d21250
--- /dev/null
+++ b/benchmark/integration-tests/dry-run-benchmark/README.md
@@ -0,0 +1,3 @@
+Runs a single benchmark in startup mode (androidx.benchmark.startupMode.enable = true)
+
+Verifies warmup / looping behavior is simplified for startup measurement
\ No newline at end of file
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/Trace.kt b/benchmark/integration-tests/dry-run-benchmark/build.gradle
similarity index 60%
copy from compose/compose-runtime/src/main/java/androidx/compose/Trace.kt
copy to benchmark/integration-tests/dry-run-benchmark/build.gradle
index e451296..80085a3 100644
--- a/compose/compose-runtime/src/main/java/androidx/compose/Trace.kt
+++ b/benchmark/integration-tests/dry-run-benchmark/build.gradle
@@ -14,19 +14,18 @@
* limitations under the License.
*/
-package androidx.compose
+import static androidx.build.dependencies.DependenciesKt.*
-import android.os.Trace
+plugins {
+ id("AndroidXPlugin")
+ id("com.android.library")
+ id("kotlin-android")
+ id("androidx.benchmark")
+}
-/**
- * Wrap the specified [block] in calls to [Trace.beginSection] (with the supplied [sectionName])
- * and [Trace.endSection].
- */
-inline fun <T> trace(sectionName: String, block: () -> T): T {
- Trace.beginSection(sectionName)
- try {
- return block()
- } finally {
- Trace.endSection()
- }
-}
\ No newline at end of file
+dependencies {
+ androidTestImplementation(project(":benchmark:benchmark-junit4"))
+ androidTestImplementation(ANDROIDX_TEST_RUNNER)
+ androidTestImplementation(JUNIT)
+ androidTestImplementation(KOTLIN_STDLIB)
+}
diff --git a/benchmark/src/androidTest/AndroidManifest.xml b/benchmark/integration-tests/dry-run-benchmark/src/androidTest/AndroidManifest.xml
similarity index 72%
copy from benchmark/src/androidTest/AndroidManifest.xml
copy to benchmark/integration-tests/dry-run-benchmark/src/androidTest/AndroidManifest.xml
index f5ec776..dbe6a73 100644
--- a/benchmark/src/androidTest/AndroidManifest.xml
+++ b/benchmark/integration-tests/dry-run-benchmark/src/androidTest/AndroidManifest.xml
@@ -17,10 +17,11 @@
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
- package="androidx.benchmark.test">
-
+ package="androidx.benchmark.integration.dryrun.benchmark.test">
<application
- android:name="androidx.benchmark.ArgumentInjectingApplication">
- <activity android:name="android.app.Activity"/>
- </application>
-</manifest>
\ No newline at end of file
+ android:name="androidx.benchmark.integration.dryrun.benchmark.ArgumentInjectingApplication"
+ android:debuggable="false"
+ tools:ignore="HardcodedDebugMode"
+ tools:replace="android:debuggable" />
+ />
+</manifest>
diff --git a/benchmark/src/androidTest/java/androidx/benchmark/ArgumentInjectingApplication.kt b/benchmark/integration-tests/dry-run-benchmark/src/androidTest/java/androidx/benchmark/integration/dryrun/benchmark/ArgumentInjectingApplication.kt
similarity index 68%
copy from benchmark/src/androidTest/java/androidx/benchmark/ArgumentInjectingApplication.kt
copy to benchmark/integration-tests/dry-run-benchmark/src/androidTest/java/androidx/benchmark/integration/dryrun/benchmark/ArgumentInjectingApplication.kt
index 30b3bd3..dc94441 100644
--- a/benchmark/src/androidTest/java/androidx/benchmark/ArgumentInjectingApplication.kt
+++ b/benchmark/integration-tests/dry-run-benchmark/src/androidTest/java/androidx/benchmark/integration/dryrun/benchmark/ArgumentInjectingApplication.kt
@@ -14,10 +14,11 @@
* limitations under the License.
*/
-package androidx.benchmark
+package androidx.benchmark.integration.dryrun.benchmark
import android.app.Application
import android.os.Bundle
+import androidx.benchmark.argumentSource
/**
* Hack to enable overriding benchmark arguments (since we can't easily do this in CI, per apk)
@@ -27,8 +28,10 @@
* ```
* android {
* defaultConfig {
- * testInstrumentationRunnerArgument 'androidx.benchmark.suppressErrors',
- * 'CODE-COVERAGE,DEBUGGABLE,EMULATOR,LOW-BATTERY,UNLOCKED'
+ * // Enable startup measurement mode
+ * testInstrumentationRunnerArgument 'androidx.benchmark.startupMode.enable', 'true'
+ * // Enable dry run mode, which overrides startup
+ * testInstrumentationRunnerArgument 'androidx.benchmark.dryRunMode.enable', 'true'
* }
* }
* ```
@@ -39,14 +42,8 @@
argumentSource = Bundle().apply {
putString("androidx.benchmark.output.enable", "true")
-
- // Since these benchmark correctness tests run as part of the regular
- // (non-performance-test) suite, they will have debuggable=true, won't be clock-locked,
- // can run with low-battery or on an emulator, and code coverage enabled.
- putString(
- "androidx.benchmark.suppressErrors",
- "CODE-COVERAGE,DEBUGGABLE,EMULATOR,LOW-BATTERY,UNLOCKED"
- )
+ putString("androidx.benchmark.startupMode.enable", "true") // this should be ignored
+ putString("androidx.benchmark.dryRunMode.enable", "true")
}
}
}
\ No newline at end of file
diff --git a/benchmark/integration-tests/dry-run-benchmark/src/androidTest/java/androidx/benchmark/integration/dryrun/benchmark/DryRunBenchmark.kt b/benchmark/integration-tests/dry-run-benchmark/src/androidTest/java/androidx/benchmark/integration/dryrun/benchmark/DryRunBenchmark.kt
new file mode 100644
index 0000000..5ded92a
--- /dev/null
+++ b/benchmark/integration-tests/dry-run-benchmark/src/androidTest/java/androidx/benchmark/integration/dryrun/benchmark/DryRunBenchmark.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.benchmark.integration.dryrun.benchmark
+
+import androidx.benchmark.junit4.BenchmarkRule
+import androidx.benchmark.junit4.measureRepeated
+import androidx.test.filters.LargeTest
+import org.junit.Assert.assertEquals
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+@LargeTest
+class DryRunBenchmark {
+ @get:Rule
+ val benchmarkRule = BenchmarkRule()
+
+ @Test
+ fun benchmarkOne() {
+ var iterationCount = 0
+ benchmarkRule.measureRepeated {
+ iterationCount++
+ }
+ // dry run mode always runs 1 loops
+ assertEquals(1, iterationCount)
+ }
+
+ // We have two benchmarks here to verify startupMode is overridden
+ @Test
+ fun benchmarkTwo() {
+ var iterationCount = 0
+ benchmarkRule.measureRepeated {
+ iterationCount++
+ }
+ // dry run mode always runs 1 loops
+ assertEquals(1, iterationCount)
+ }
+}
\ No newline at end of file
diff --git a/media2/widget/src/main/res/values/public.xml b/benchmark/integration-tests/dry-run-benchmark/src/main/AndroidManifest.xml
similarity index 72%
copy from media2/widget/src/main/res/values/public.xml
copy to benchmark/integration-tests/dry-run-benchmark/src/main/AndroidManifest.xml
index 6f7f00f..22daa1a 100644
--- a/media2/widget/src/main/res/values/public.xml
+++ b/benchmark/integration-tests/dry-run-benchmark/src/main/AndroidManifest.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright 2018 The Android Open Source Project
+ ~ 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.
@@ -14,9 +14,5 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
-<!-- Definitions of attributes to be exposed as public -->
-<resources>
- <public type="attr" name="enableControlView" />
- <public type="attr" name="viewType" />
-</resources>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="androidx.benchmark.integration.dryrun.benchmark"/>
diff --git a/benchmark/integration-tests/startup-benchmark/build.gradle b/benchmark/integration-tests/startup-benchmark/build.gradle
index 3467603..80085a3 100644
--- a/benchmark/integration-tests/startup-benchmark/build.gradle
+++ b/benchmark/integration-tests/startup-benchmark/build.gradle
@@ -20,10 +20,11 @@
id("AndroidXPlugin")
id("com.android.library")
id("kotlin-android")
+ id("androidx.benchmark")
}
dependencies {
- androidTestImplementation(project(":benchmark"))
+ androidTestImplementation(project(":benchmark:benchmark-junit4"))
androidTestImplementation(ANDROIDX_TEST_RUNNER)
androidTestImplementation(JUNIT)
androidTestImplementation(KOTLIN_STDLIB)
diff --git a/benchmark/integration-tests/startup-benchmark/src/androidTest/java/androidx/benchmark/integration/startup/benchmark/StartupBenchmark.kt b/benchmark/integration-tests/startup-benchmark/src/androidTest/java/androidx/benchmark/integration/startup/benchmark/StartupBenchmark.kt
index aa4f280..d4f9464 100644
--- a/benchmark/integration-tests/startup-benchmark/src/androidTest/java/androidx/benchmark/integration/startup/benchmark/StartupBenchmark.kt
+++ b/benchmark/integration-tests/startup-benchmark/src/androidTest/java/androidx/benchmark/integration/startup/benchmark/StartupBenchmark.kt
@@ -16,8 +16,8 @@
package androidx.benchmark.integration.startup.benchmark
-import androidx.benchmark.BenchmarkRule
-import androidx.benchmark.measureRepeated
+import androidx.benchmark.junit4.BenchmarkRule
+import androidx.benchmark.junit4.measureRepeated
import androidx.test.filters.LargeTest
import org.junit.Assert.assertEquals
import org.junit.Rule
diff --git a/benchmark/junit4/api/1.0.0-alpha04.txt b/benchmark/junit4/api/1.0.0-alpha04.txt
new file mode 100644
index 0000000..756b010
--- /dev/null
+++ b/benchmark/junit4/api/1.0.0-alpha04.txt
@@ -0,0 +1,24 @@
+// Signature format: 3.0
+package androidx.benchmark.junit4 {
+
+ public class AndroidBenchmarkRunner extends androidx.test.runner.AndroidJUnitRunner {
+ ctor public AndroidBenchmarkRunner();
+ }
+
+ public final class BenchmarkRule implements org.junit.rules.TestRule {
+ ctor public BenchmarkRule();
+ method public org.junit.runners.model.Statement apply(org.junit.runners.model.Statement base, org.junit.runner.Description description);
+ method public androidx.benchmark.BenchmarkState getState();
+ }
+
+ public final class BenchmarkRule.Scope {
+ method public inline <T> T! runWithTimingDisabled(kotlin.jvm.functions.Function0<? extends T> block);
+ }
+
+ public final class BenchmarkRuleKt {
+ ctor public BenchmarkRuleKt();
+ method public static inline void measureRepeated(androidx.benchmark.junit4.BenchmarkRule, kotlin.jvm.functions.Function1<? super androidx.benchmark.junit4.BenchmarkRule.Scope,kotlin.Unit> block);
+ }
+
+}
+
diff --git a/benchmark/junit4/api/current.txt b/benchmark/junit4/api/current.txt
new file mode 100644
index 0000000..756b010
--- /dev/null
+++ b/benchmark/junit4/api/current.txt
@@ -0,0 +1,24 @@
+// Signature format: 3.0
+package androidx.benchmark.junit4 {
+
+ public class AndroidBenchmarkRunner extends androidx.test.runner.AndroidJUnitRunner {
+ ctor public AndroidBenchmarkRunner();
+ }
+
+ public final class BenchmarkRule implements org.junit.rules.TestRule {
+ ctor public BenchmarkRule();
+ method public org.junit.runners.model.Statement apply(org.junit.runners.model.Statement base, org.junit.runner.Description description);
+ method public androidx.benchmark.BenchmarkState getState();
+ }
+
+ public final class BenchmarkRule.Scope {
+ method public inline <T> T! runWithTimingDisabled(kotlin.jvm.functions.Function0<? extends T> block);
+ }
+
+ public final class BenchmarkRuleKt {
+ ctor public BenchmarkRuleKt();
+ method public static inline void measureRepeated(androidx.benchmark.junit4.BenchmarkRule, kotlin.jvm.functions.Function1<? super androidx.benchmark.junit4.BenchmarkRule.Scope,kotlin.Unit> block);
+ }
+
+}
+
diff --git a/benchmark/api/res-1.0.0-alpha04.txt b/benchmark/junit4/api/res-1.0.0-alpha04.txt
similarity index 100%
copy from benchmark/api/res-1.0.0-alpha04.txt
copy to benchmark/junit4/api/res-1.0.0-alpha04.txt
diff --git a/benchmark/junit4/api/restricted_1.0.0-alpha04.txt b/benchmark/junit4/api/restricted_1.0.0-alpha04.txt
new file mode 100644
index 0000000..756b010
--- /dev/null
+++ b/benchmark/junit4/api/restricted_1.0.0-alpha04.txt
@@ -0,0 +1,24 @@
+// Signature format: 3.0
+package androidx.benchmark.junit4 {
+
+ public class AndroidBenchmarkRunner extends androidx.test.runner.AndroidJUnitRunner {
+ ctor public AndroidBenchmarkRunner();
+ }
+
+ public final class BenchmarkRule implements org.junit.rules.TestRule {
+ ctor public BenchmarkRule();
+ method public org.junit.runners.model.Statement apply(org.junit.runners.model.Statement base, org.junit.runner.Description description);
+ method public androidx.benchmark.BenchmarkState getState();
+ }
+
+ public final class BenchmarkRule.Scope {
+ method public inline <T> T! runWithTimingDisabled(kotlin.jvm.functions.Function0<? extends T> block);
+ }
+
+ public final class BenchmarkRuleKt {
+ ctor public BenchmarkRuleKt();
+ method public static inline void measureRepeated(androidx.benchmark.junit4.BenchmarkRule, kotlin.jvm.functions.Function1<? super androidx.benchmark.junit4.BenchmarkRule.Scope,kotlin.Unit> block);
+ }
+
+}
+
diff --git a/benchmark/junit4/api/restricted_current.txt b/benchmark/junit4/api/restricted_current.txt
new file mode 100644
index 0000000..756b010
--- /dev/null
+++ b/benchmark/junit4/api/restricted_current.txt
@@ -0,0 +1,24 @@
+// Signature format: 3.0
+package androidx.benchmark.junit4 {
+
+ public class AndroidBenchmarkRunner extends androidx.test.runner.AndroidJUnitRunner {
+ ctor public AndroidBenchmarkRunner();
+ }
+
+ public final class BenchmarkRule implements org.junit.rules.TestRule {
+ ctor public BenchmarkRule();
+ method public org.junit.runners.model.Statement apply(org.junit.runners.model.Statement base, org.junit.runner.Description description);
+ method public androidx.benchmark.BenchmarkState getState();
+ }
+
+ public final class BenchmarkRule.Scope {
+ method public inline <T> T! runWithTimingDisabled(kotlin.jvm.functions.Function0<? extends T> block);
+ }
+
+ public final class BenchmarkRuleKt {
+ ctor public BenchmarkRuleKt();
+ method public static inline void measureRepeated(androidx.benchmark.junit4.BenchmarkRule, kotlin.jvm.functions.Function1<? super androidx.benchmark.junit4.BenchmarkRule.Scope,kotlin.Unit> block);
+ }
+
+}
+
diff --git a/benchmark/build.gradle b/benchmark/junit4/build.gradle
similarity index 75%
copy from benchmark/build.gradle
copy to benchmark/junit4/build.gradle
index 386f71d..d4f47cc 100644
--- a/benchmark/build.gradle
+++ b/benchmark/junit4/build.gradle
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * 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.
@@ -25,21 +25,30 @@
id("kotlin-android")
}
+android {
+ defaultConfig {
+ testInstrumentationRunner = "androidx.benchmark.junit4.AndroidBenchmarkRunner"
+ }
+}
+
dependencies {
+ api(project(":benchmark:benchmark-common"))
+
+ api(JUNIT)
+ api(KOTLIN_STDLIB)
+
implementation(ANDROIDX_TEST_RULES)
implementation(ANDROIDX_TEST_RUNNER)
- implementation(KOTLIN_STDLIB)
implementation(SUPPORT_ANNOTATIONS)
- androidTestImplementation(ANDROIDX_TEST_CORE)
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
}
androidx {
- name = "Android Benchmark"
+ name = "Android Benchmark - JUnit4"
publish = Publish.SNAPSHOT_AND_RELEASE
mavenVersion = LibraryVersions.BENCHMARK
mavenGroup = LibraryGroups.BENCHMARK
- inceptionYear = "2018"
- description = "Android Benchmark"
+ inceptionYear = "2019"
+ description = "Android Benchmark - JUnit4"
}
diff --git a/benchmark/src/androidTest/AndroidManifest.xml b/benchmark/junit4/src/androidTest/AndroidManifest.xml
similarity index 83%
copy from benchmark/src/androidTest/AndroidManifest.xml
copy to benchmark/junit4/src/androidTest/AndroidManifest.xml
index f5ec776..7398a5f 100644
--- a/benchmark/src/androidTest/AndroidManifest.xml
+++ b/benchmark/junit4/src/androidTest/AndroidManifest.xml
@@ -16,11 +16,10 @@
-->
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- package="androidx.benchmark.test">
+ package="androidx.benchmark.junit4.test">
<application
- android:name="androidx.benchmark.ArgumentInjectingApplication">
+ android:name="androidx.benchmark.junit4.ArgumentInjectingApplication">
<activity android:name="android.app.Activity"/>
</application>
</manifest>
\ No newline at end of file
diff --git a/benchmark/src/androidTest/java/androidx/benchmark/ActivityBenchmarkTests.kt b/benchmark/junit4/src/androidTest/java/androidx/benchmark/junit4/ActivityBenchmarkTests.kt
similarity index 93%
rename from benchmark/src/androidTest/java/androidx/benchmark/ActivityBenchmarkTests.kt
rename to benchmark/junit4/src/androidTest/java/androidx/benchmark/junit4/ActivityBenchmarkTests.kt
index a053d3a..d8db6e7 100644
--- a/benchmark/src/androidTest/java/androidx/benchmark/ActivityBenchmarkTests.kt
+++ b/benchmark/junit4/src/androidTest/java/androidx/benchmark/junit4/ActivityBenchmarkTests.kt
@@ -14,15 +14,16 @@
* limitations under the License.
*/
-package androidx.benchmark
+package androidx.benchmark.junit4
import android.app.Activity
+import androidx.benchmark.IsolationActivity
import androidx.test.annotation.UiThreadTest
import androidx.test.core.app.ActivityScenario
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.filters.LargeTest
import androidx.test.rule.ActivityTestRule
-import org.junit.Assert
+import org.junit.Assert.assertFalse
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -31,7 +32,7 @@
fun BenchmarkRule.validateRunWithIsolationActivityHidden() {
// isolation activity *not* on top
- Assert.assertFalse(IsolationActivity.singleton.get()!!.resumed)
+ assertFalse(IsolationActivity.resumed)
measureRepeated {}
}
diff --git a/benchmark/src/androidTest/java/androidx/benchmark/AndroidBenchmarkRunnerTest.kt b/benchmark/junit4/src/androidTest/java/androidx/benchmark/junit4/AndroidBenchmarkRunnerTest.kt
similarity index 88%
rename from benchmark/src/androidTest/java/androidx/benchmark/AndroidBenchmarkRunnerTest.kt
rename to benchmark/junit4/src/androidTest/java/androidx/benchmark/junit4/AndroidBenchmarkRunnerTest.kt
index 0b1f11f..d5b70f9 100644
--- a/benchmark/src/androidTest/java/androidx/benchmark/AndroidBenchmarkRunnerTest.kt
+++ b/benchmark/junit4/src/androidTest/java/androidx/benchmark/junit4/AndroidBenchmarkRunnerTest.kt
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package androidx.benchmark
+package androidx.benchmark.junit4
+import androidx.benchmark.IsolationActivity
import androidx.test.annotation.UiThreadTest
import androidx.test.filters.SmallTest
import org.junit.Assert.assertTrue
@@ -29,6 +30,6 @@
@UiThreadTest
@Test
fun checkActivityVisibility() {
- assertTrue(IsolationActivity.singleton.get()!!.resumed)
+ assertTrue(IsolationActivity.resumed)
}
}
diff --git a/benchmark/src/androidTest/java/androidx/benchmark/ArgumentInjectingApplication.kt b/benchmark/junit4/src/androidTest/java/androidx/benchmark/junit4/ArgumentInjectingApplication.kt
similarity index 95%
rename from benchmark/src/androidTest/java/androidx/benchmark/ArgumentInjectingApplication.kt
rename to benchmark/junit4/src/androidTest/java/androidx/benchmark/junit4/ArgumentInjectingApplication.kt
index 30b3bd3..233fc11 100644
--- a/benchmark/src/androidTest/java/androidx/benchmark/ArgumentInjectingApplication.kt
+++ b/benchmark/junit4/src/androidTest/java/androidx/benchmark/junit4/ArgumentInjectingApplication.kt
@@ -14,10 +14,11 @@
* limitations under the License.
*/
-package androidx.benchmark
+package androidx.benchmark.junit4
import android.app.Application
import android.os.Bundle
+import androidx.benchmark.argumentSource
/**
* Hack to enable overriding benchmark arguments (since we can't easily do this in CI, per apk)
diff --git a/benchmark/src/androidTest/java/androidx/benchmark/BenchmarkRuleAnnotationTest.kt b/benchmark/junit4/src/androidTest/java/androidx/benchmark/junit4/BenchmarkRuleAnnotationTest.kt
similarity index 96%
rename from benchmark/src/androidTest/java/androidx/benchmark/BenchmarkRuleAnnotationTest.kt
rename to benchmark/junit4/src/androidTest/java/androidx/benchmark/junit4/BenchmarkRuleAnnotationTest.kt
index c6c8ddc..6a0c62c 100644
--- a/benchmark/src/androidTest/java/androidx/benchmark/BenchmarkRuleAnnotationTest.kt
+++ b/benchmark/junit4/src/androidTest/java/androidx/benchmark/junit4/BenchmarkRuleAnnotationTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.benchmark
+package androidx.benchmark.junit4
import androidx.test.filters.SmallTest
import org.junit.Test
diff --git a/benchmark/src/androidTest/java/androidx/benchmark/BenchmarkRuleTest.kt b/benchmark/junit4/src/androidTest/java/androidx/benchmark/junit4/BenchmarkRuleTest.kt
similarity index 93%
rename from benchmark/src/androidTest/java/androidx/benchmark/BenchmarkRuleTest.kt
rename to benchmark/junit4/src/androidTest/java/androidx/benchmark/junit4/BenchmarkRuleTest.kt
index 8e988f0..87e5697 100644
--- a/benchmark/src/androidTest/java/androidx/benchmark/BenchmarkRuleTest.kt
+++ b/benchmark/junit4/src/androidTest/java/androidx/benchmark/junit4/BenchmarkRuleTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.benchmark
+package androidx.benchmark.junit4
import androidx.test.filters.LargeTest
import org.junit.Assert.assertTrue
@@ -37,7 +37,7 @@
Thread.sleep(5)
}
}
- val min = benchmarkRule.getState().stats.min
+ val min = benchmarkRule.getState().getMin()
assertTrue("minimum $min should be less than 1ms",
min < TimeUnit.MILLISECONDS.toNanos(1))
}
diff --git a/media2/widget/src/main/res/values/public.xml b/benchmark/junit4/src/main/AndroidManifest.xml
similarity index 72%
copy from media2/widget/src/main/res/values/public.xml
copy to benchmark/junit4/src/main/AndroidManifest.xml
index 6f7f00f..ee4f8b9 100644
--- a/media2/widget/src/main/res/values/public.xml
+++ b/benchmark/junit4/src/main/AndroidManifest.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright 2018 The Android Open Source Project
+ ~ 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.
@@ -14,9 +14,4 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
-<!-- Definitions of attributes to be exposed as public -->
-<resources>
- <public type="attr" name="enableControlView" />
- <public type="attr" name="viewType" />
-</resources>
+<manifest package="androidx.benchmark.junit4"/>
diff --git a/benchmark/junit4/src/main/java/androidx/benchmark/junit4/AndroidBenchmarkRunner.kt b/benchmark/junit4/src/main/java/androidx/benchmark/junit4/AndroidBenchmarkRunner.kt
new file mode 100644
index 0000000..1ca90b3
--- /dev/null
+++ b/benchmark/junit4/src/main/java/androidx/benchmark/junit4/AndroidBenchmarkRunner.kt
@@ -0,0 +1,90 @@
+/*
+ * 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.benchmark.junit4
+
+import androidx.annotation.CallSuper
+import androidx.benchmark.IsolationActivity
+import androidx.test.runner.AndroidJUnitRunner
+
+/**
+ * Instrumentation runner for benchmarks, used to increase stability of measurements and minimize
+ * interference.
+ *
+ * To use this runner, put the following in your module level `build.gradle`:
+ *
+ * ```
+ * android {
+ * defaultConfig {
+ * testInstrumentationRunner "androidx.benchmark.AndroidBenchmarkRunner"
+ * }
+ * }
+ * ```
+ *
+ * ## Minimizing Interference
+ *
+ * This runner launches a simple opaque activity used to reduce benchmark interference from other
+ * windows. Launching other activities is supported e.g. via ActivityTestRule and ActivityScenario -
+ * the opaque activity will be relaunched if not actively running before each test, and after each
+ * test's cleanup is complete.
+ *
+ * For example, sources of potential interference:
+ * - live wallpaper rendering
+ * - homescreen widget updates
+ * - hotword detection
+ * - status bar repaints
+ * - running in background (some cores may be foreground-app only)
+ *
+ * ## Clock Stability
+ *
+ * While it is better for performance stability to lock clocks with the `./gradlew lockClocks` task
+ * provided by the gradle plugin, this is not possible on most devices. The runner provides a
+ * fallback mode for preventing thermal throttling.
+ *
+ * On devices that support [android.view.Window.setSustainedPerformanceMode], the runner will set
+ * this mode on the window of every Activity launched (including the opaque Activity mentioned
+ * above). The runner will also launch a continuously spinning Thread. Together, these ensure that
+ * the app runs in the multithreaded stable performance mode, which locks the maximum clock
+ * frequency to prevent thermal throttling. This ensures stable clock levels across all benchmarks,
+ * even if a continuous suite of benchmarks runs for many minutes on end.
+ */
+@Suppress("unused") // Note: not referenced by code
+open class AndroidBenchmarkRunner : AndroidJUnitRunner() {
+
+ @CallSuper
+ override fun waitForActivitiesToComplete() {
+ // We don't call the super method here, since we have
+ // an activity we intend to persist between tests
+ // TODO: somehow wait for every activity but IsolationActivity
+
+ // Before/After each test, from the test thread, synchronously launch
+ // our IsolationActivity if it's not already resumed
+ var isResumed = false
+ runOnMainSync {
+ isResumed = IsolationActivity.resumed
+ }
+ if (!isResumed) {
+ IsolationActivity.launchSingleton()
+ }
+ }
+
+ @CallSuper
+ override fun onDestroy() {
+ IsolationActivity.finishSingleton()
+ super.waitForActivitiesToComplete()
+ super.onDestroy()
+ }
+}
\ No newline at end of file
diff --git a/benchmark/src/main/java/androidx/benchmark/BenchmarkRule.kt b/benchmark/junit4/src/main/java/androidx/benchmark/junit4/BenchmarkRule.kt
similarity index 82%
rename from benchmark/src/main/java/androidx/benchmark/BenchmarkRule.kt
rename to benchmark/junit4/src/main/java/androidx/benchmark/junit4/BenchmarkRule.kt
index 97b114f..496d536 100644
--- a/benchmark/src/main/java/androidx/benchmark/BenchmarkRule.kt
+++ b/benchmark/junit4/src/main/java/androidx/benchmark/junit4/BenchmarkRule.kt
@@ -14,12 +14,12 @@
* limitations under the License.
*/
-package androidx.benchmark
+package androidx.benchmark.junit4
import android.Manifest
import android.util.Log
import androidx.annotation.RestrictTo
-import androidx.benchmark.Errors.WARNING_PREFIX
+import androidx.benchmark.BenchmarkState
import androidx.test.rule.GrantPermissionRule
import org.junit.Assert.assertTrue
import org.junit.rules.RuleChain
@@ -106,6 +106,8 @@
* ...
* }
* ```
+ *
+ * @throws [IllegalStateException] if the BenchmarkRule isn't correctly applied to a test.
*/
fun getState(): BenchmarkState {
// Note: this is an explicit method instead of an accessor to help convey it's only for Java
@@ -169,38 +171,37 @@
.apply(base, description)
}
- private fun applyInternal(base: Statement, description: Description) = Statement {
- applied = true
- var invokeMethodName = description.methodName
- Log.i(TAG, "Running ${description.className}#$invokeMethodName")
-
- // validate and simplify the function name.
- // First, remove the "test" prefix which normally comes from CTS test.
- // Then make sure the [subTestName] is valid, not just numbers like [0].
- if (invokeMethodName.startsWith("test")) {
- assertTrue(
- "The test name $invokeMethodName is too short",
- invokeMethodName.length > 5
+ private fun applyInternal(base: Statement, description: Description) =
+ Statement {
+ applied = true
+ var invokeMethodName = description.methodName
+ Log.i(
+ TAG,
+ "Running ${description.className}#$invokeMethodName"
)
- invokeMethodName = invokeMethodName.substring(4, 5).toLowerCase() +
- invokeMethodName.substring(5)
- }
- base.evaluate()
-
- if (enableReport) {
- val fullTestName =
- WARNING_PREFIX + description.testClass.simpleName + "." + invokeMethodName
- internalState.sendStatus(fullTestName)
-
- ResultWriter.appendReport(
- internalState.getReport(
- testName = WARNING_PREFIX + invokeMethodName,
- className = description.className
+ // validate and simplify the function name.
+ // First, remove the "test" prefix which normally comes from CTS test.
+ // Then make sure the [subTestName] is valid, not just numbers like [0].
+ if (invokeMethodName.startsWith("test")) {
+ assertTrue(
+ "The test name $invokeMethodName is too short",
+ invokeMethodName.length > 5
)
- )
+ invokeMethodName = invokeMethodName.substring(4, 5).toLowerCase() +
+ invokeMethodName.substring(5)
+ }
+
+ base.evaluate()
+
+ if (enableReport) {
+ internalState.report(
+ fullClassName = description.className,
+ simpleClassName = description.testClass.simpleName,
+ methodName = invokeMethodName
+ )
+ }
}
- }
internal companion object {
private const val TAG = "BenchmarkRule"
diff --git a/benchmark/src/main/java/androidx/benchmark/AndroidBenchmarkRunner.kt b/benchmark/src/main/java/androidx/benchmark/AndroidBenchmarkRunner.kt
deleted file mode 100644
index 592be43..0000000
--- a/benchmark/src/main/java/androidx/benchmark/AndroidBenchmarkRunner.kt
+++ /dev/null
@@ -1,156 +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.benchmark
-
-import android.annotation.SuppressLint
-import android.app.Activity
-import android.content.Context
-import android.os.Build
-import android.os.Bundle
-import android.os.PowerManager
-import androidx.annotation.CallSuper
-import androidx.test.platform.app.InstrumentationRegistry
-import androidx.test.runner.AndroidJUnitRunner
-import kotlin.concurrent.thread
-
-/**
- * Instrumentation runner for benchmarks, used to increase stability of measurements and minimize
- * interference.
- *
- * To use this runner, put the following in your module level `build.gradle`:
- *
- * ```
- * android {
- * defaultConfig {
- * testInstrumentationRunner "androidx.benchmark.AndroidBenchmarkRunner"
- * }
- * }
- * ```
- *
- * ## Minimizing Interference
- *
- * This runner launches a simple opaque activity used to reduce benchmark interference from other
- * windows. Launching other activities is supported e.g. via ActivityTestRule and ActivityScenario -
- * the opaque activity will be relaunched if not actively running before each test, and after each
- * test's cleanup is complete.
- *
- * For example, sources of potential interference:
- * - live wallpaper rendering
- * - homescreen widget updates
- * - hotword detection
- * - status bar repaints
- * - running in background (some cores may be foreground-app only)
- *
- * ## Clock Stability
- *
- * While it is better for performance stability to lock clocks with the `./gradlew lockClocks` task
- * provided by the gradle plugin, this is not possible on most devices. The runner provides a
- * fallback mode for preventing thermal throttling.
- *
- * On devices that support [android.view.Window.setSustainedPerformanceMode], the runner will set
- * this mode on the window of every Activity launched (including the opaque Activity mentioned
- * above). The runner will also launch a continuously spinning Thread. Together, these ensure that
- * the app runs in the multithreaded stable performance mode, which locks the maximum clock
- * frequency to prevent thermal throttling. This ensures stable clock levels across all benchmarks,
- * even if a continuous suite of benchmarks runs for many minutes on end.
- */
-@Suppress("unused") // Note: not referenced by code
-open class AndroidBenchmarkRunner : AndroidJUnitRunner() {
- @CallSuper
- override fun onCreate(arguments: Bundle?) {
- super.onCreate(arguments)
-
- // Because these values are used by Errors, it's important to set this flag as early
- // as possible, before Errors gets lazily initialized. Otherwise we may print false
- // warnings about needing the runner, when the runner simply hasn't initialized yet.
- runnerInUse = true
- sustainedPerformanceModeInUse = !CpuInfo.locked && isSustainedPerformanceModeSupported()
-
- if (sustainedPerformanceModeInUse) {
- // Keep at least one core busy. Together with a single threaded benchmark, this makes
- // the process get multi-threaded setSustainedPerformanceMode.
- //
- // We want to keep to the relatively lower clocks of the multi-threaded benchmark mode
- // to avoid any benchmarks running at higher clocks than any others.
- //
- // Note, thread names have 15 char max in Systrace
- thread(name = "BenchSpinThread") {
- android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_LOWEST)
- while (true) {}
- }
- }
- }
-
- @CallSuper
- override fun callActivityOnStart(activity: Activity) {
- super.callActivityOnStart(activity)
-
- @SuppressLint("NewApi") // window API guarded by [sustainedPerfMode]
- if (sustainedPerformanceModeInUse) {
- activity.window.setSustainedPerformanceMode(true)
- }
- }
-
- @CallSuper
- override fun waitForActivitiesToComplete() {
- // We don't call the super method here, since we have
- // an activity we intend to persist between tests
- // TODO: somehow wait for every activity but IsolationActivity
-
- // Before/After each test, from the test thread, synchronously launch
- // our IsolationActivity if it's not already resumed
- var isResumed = false
- runOnMainSync {
- val activity = IsolationActivity.singleton.get()
- if (activity != null) {
- isResumed = activity.resumed
- }
- }
- if (!isResumed) {
- IsolationActivity.launchSingleton()
- }
- }
-
- @CallSuper
- override fun onDestroy() {
- IsolationActivity.finishSingleton()
- super.waitForActivitiesToComplete()
- super.onDestroy()
- }
-
- internal companion object {
- /**
- * Tracks whether Runner is in use.
- */
- var runnerInUse = false
-
- /**
- * Tracks whether Runner is using [android.view.Window.setSustainedPerformanceMode] to
- * prevent thermal throttling.
- */
- var sustainedPerformanceModeInUse = false
-
- fun isSustainedPerformanceModeSupported(): Boolean =
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- val context = InstrumentationRegistry.getInstrumentation().targetContext
- val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
- powerManager.isSustainedPerformanceModeSupported
- } else {
- false
- }
- }
-}
\ No newline at end of file
diff --git a/benchmark/src/main/java/androidx/benchmark/IsolationActivity.kt b/benchmark/src/main/java/androidx/benchmark/IsolationActivity.kt
deleted file mode 100644
index ef10026..0000000
--- a/benchmark/src/main/java/androidx/benchmark/IsolationActivity.kt
+++ /dev/null
@@ -1,118 +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.benchmark
-
-import android.content.Intent
-import android.os.Bundle
-import android.util.Log
-import android.widget.TextView
-import androidx.annotation.AnyThread
-import androidx.annotation.RestrictTo
-import androidx.annotation.WorkerThread
-import androidx.test.platform.app.InstrumentationRegistry
-import java.util.concurrent.atomic.AtomicReference
-
-/**
- * Simple opaque activity used to reduce benchmark interference from other windows.
- *
- * For example, sources of potential interference:
- * - live wallpaper rendering
- * - homescreen widget updates
- * - hotword detection
- * - status bar repaints
- * - running in background (some cores may be foreground-app only)
- *
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY)
-class IsolationActivity : android.app.Activity() {
- var resumed = false
- private var destroyed = false
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.isolation_activity)
-
- // disable launch animation
- overridePendingTransition(0, 0)
-
- val old = singleton.getAndSet(this)
- if (old != null && !old.destroyed && !old.isFinishing) {
- throw IllegalStateException("Only one IsolationActivity should exist")
- }
-
- findViewById<TextView>(R.id.clock_state).text = when {
- CpuInfo.locked -> "Locked Clocks"
- AndroidBenchmarkRunner.sustainedPerformanceModeInUse -> "Sustained Performance Mode"
- else -> ""
- }
- }
-
- override fun onResume() {
- super.onResume()
- resumed = true
- }
-
- override fun onPause() {
- super.onPause()
- resumed = false
- }
-
- override fun onDestroy() {
- super.onDestroy()
- destroyed = true
- }
-
- /** finish is ignored! we defer until [actuallyFinish] is called. */
- override fun finish() {
- }
-
- fun actuallyFinish() {
- // disable close animation
- overridePendingTransition(0, 0)
- super.finish()
- }
-
- companion object {
- private const val TAG = "Benchmark"
- internal val singleton = AtomicReference<IsolationActivity>()
-
- @WorkerThread
- fun launchSingleton() {
- val intent = Intent(Intent.ACTION_MAIN).apply {
- Log.d(TAG, "launching Benchmark IsolationActivity")
- setClassName(
- InstrumentationRegistry.getInstrumentation().targetContext.packageName,
- IsolationActivity::class.java.name
- )
- addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
- }
- InstrumentationRegistry.getInstrumentation().startActivitySync(intent)
- }
-
- @AnyThread
- fun finishSingleton() {
- Log.d(TAG, "Benchmark runner being destroyed, tearing down activities")
- singleton.getAndSet(null)?.apply {
- runOnUiThread {
- actuallyFinish()
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/browser/api/1.2.0-alpha07.txt b/browser/api/1.2.0-alpha07.txt
new file mode 100644
index 0000000..798e756
--- /dev/null
+++ b/browser/api/1.2.0-alpha07.txt
@@ -0,0 +1,316 @@
+// Signature format: 3.0
+package androidx.browser.browseractions {
+
+ @Deprecated public class BrowserActionItem {
+ ctor @Deprecated public BrowserActionItem(String, android.app.PendingIntent, @DrawableRes int);
+ ctor @Deprecated public BrowserActionItem(String, android.app.PendingIntent);
+ method @Deprecated public android.app.PendingIntent getAction();
+ method @Deprecated public int getIconId();
+ method @Deprecated public String getTitle();
+ }
+
+ @Deprecated public class BrowserActionsIntent {
+ method @Deprecated public static String? getCreatorPackageName(android.content.Intent);
+ method @Deprecated public android.content.Intent getIntent();
+ method @Deprecated public static String? getUntrustedCreatorPackageName(android.content.Intent);
+ method @Deprecated public static void launchIntent(android.content.Context!, android.content.Intent!);
+ method @Deprecated public static void openBrowserAction(android.content.Context!, android.net.Uri!);
+ method @Deprecated public static void openBrowserAction(android.content.Context!, android.net.Uri!, int, java.util.ArrayList<androidx.browser.browseractions.BrowserActionItem!>!, android.app.PendingIntent!);
+ method @Deprecated public static java.util.List<androidx.browser.browseractions.BrowserActionItem!>! parseBrowserActionItems(java.util.ArrayList<android.os.Bundle!>!);
+ field @Deprecated public static final String ACTION_BROWSER_ACTIONS_OPEN = "androidx.browser.browseractions.browser_action_open";
+ field @Deprecated public static final String EXTRA_APP_ID = "androidx.browser.browseractions.APP_ID";
+ field @Deprecated public static final String EXTRA_MENU_ITEMS = "androidx.browser.browseractions.extra.MENU_ITEMS";
+ field @Deprecated public static final String EXTRA_SELECTED_ACTION_PENDING_INTENT = "androidx.browser.browseractions.extra.SELECTED_ACTION_PENDING_INTENT";
+ field @Deprecated public static final String EXTRA_TYPE = "androidx.browser.browseractions.extra.TYPE";
+ field @Deprecated public static final int ITEM_COPY = 3; // 0x3
+ field @Deprecated public static final int ITEM_DOWNLOAD = 2; // 0x2
+ field @Deprecated public static final int ITEM_INVALID_ITEM = -1; // 0xffffffff
+ field @Deprecated public static final int ITEM_OPEN_IN_INCOGNITO = 1; // 0x1
+ field @Deprecated public static final int ITEM_OPEN_IN_NEW_TAB = 0; // 0x0
+ field @Deprecated public static final int ITEM_SHARE = 4; // 0x4
+ field @Deprecated public static final String KEY_ACTION = "androidx.browser.browseractions.ACTION";
+ field @Deprecated public static final String KEY_ICON_ID = "androidx.browser.browseractions.ICON_ID";
+ field @Deprecated public static final String KEY_TITLE = "androidx.browser.browseractions.TITLE";
+ field @Deprecated public static final int MAX_CUSTOM_ITEMS = 5; // 0x5
+ field @Deprecated public static final int URL_TYPE_AUDIO = 3; // 0x3
+ field @Deprecated public static final int URL_TYPE_FILE = 4; // 0x4
+ field @Deprecated public static final int URL_TYPE_IMAGE = 1; // 0x1
+ field @Deprecated public static final int URL_TYPE_NONE = 0; // 0x0
+ field @Deprecated public static final int URL_TYPE_PLUGIN = 5; // 0x5
+ field @Deprecated public static final int URL_TYPE_VIDEO = 2; // 0x2
+ }
+
+ @Deprecated public static final class BrowserActionsIntent.Builder {
+ ctor @Deprecated public BrowserActionsIntent.Builder(android.content.Context!, android.net.Uri!);
+ method @Deprecated public androidx.browser.browseractions.BrowserActionsIntent! build();
+ method @Deprecated public androidx.browser.browseractions.BrowserActionsIntent.Builder! setCustomItems(java.util.ArrayList<androidx.browser.browseractions.BrowserActionItem!>!);
+ method @Deprecated public androidx.browser.browseractions.BrowserActionsIntent.Builder! setCustomItems(androidx.browser.browseractions.BrowserActionItem!...);
+ method @Deprecated public androidx.browser.browseractions.BrowserActionsIntent.Builder! setOnItemSelectedAction(android.app.PendingIntent!);
+ method @Deprecated public androidx.browser.browseractions.BrowserActionsIntent.Builder! setUrlType(int);
+ }
+
+}
+
+package androidx.browser.customtabs {
+
+ public final class CustomTabColorSchemeParams {
+ field @ColorInt public final Integer? navigationBarColor;
+ field @ColorInt public final Integer? secondaryToolbarColor;
+ field @ColorInt public final Integer? toolbarColor;
+ }
+
+ public static final class CustomTabColorSchemeParams.Builder {
+ ctor public CustomTabColorSchemeParams.Builder();
+ method public androidx.browser.customtabs.CustomTabColorSchemeParams build();
+ method public androidx.browser.customtabs.CustomTabColorSchemeParams.Builder setNavigationBarColor(@ColorInt int);
+ method public androidx.browser.customtabs.CustomTabColorSchemeParams.Builder setSecondaryToolbarColor(@ColorInt int);
+ method public androidx.browser.customtabs.CustomTabColorSchemeParams.Builder setToolbarColor(@ColorInt int);
+ }
+
+ public class CustomTabsCallback {
+ ctor public CustomTabsCallback();
+ method public void extraCallback(String!, android.os.Bundle!);
+ method public void onMessageChannelReady(android.os.Bundle!);
+ method public void onNavigationEvent(int, android.os.Bundle!);
+ method public void onPostMessage(String!, android.os.Bundle!);
+ method public void onRelationshipValidationResult(@androidx.browser.customtabs.CustomTabsService.Relation int, android.net.Uri!, boolean, android.os.Bundle!);
+ field public static final int NAVIGATION_ABORTED = 4; // 0x4
+ field public static final int NAVIGATION_FAILED = 3; // 0x3
+ field public static final int NAVIGATION_FINISHED = 2; // 0x2
+ field public static final int NAVIGATION_STARTED = 1; // 0x1
+ field public static final int TAB_HIDDEN = 6; // 0x6
+ field public static final int TAB_SHOWN = 5; // 0x5
+ }
+
+ public class CustomTabsClient {
+ method public static boolean bindCustomTabsService(android.content.Context, String?, androidx.browser.customtabs.CustomTabsServiceConnection);
+ method public static boolean connectAndInitialize(android.content.Context, String);
+ method public android.os.Bundle? extraCommand(String, android.os.Bundle?);
+ method public static String? getPackageName(android.content.Context, java.util.List<java.lang.String!>?);
+ method public static String? getPackageName(android.content.Context, java.util.List<java.lang.String!>?, boolean);
+ method public androidx.browser.customtabs.CustomTabsSession? newSession(androidx.browser.customtabs.CustomTabsCallback?);
+ method public androidx.browser.customtabs.CustomTabsSession? newSession(androidx.browser.customtabs.CustomTabsCallback?, int);
+ method public boolean warmup(long);
+ }
+
+ public final class CustomTabsIntent {
+ method public static androidx.browser.customtabs.CustomTabColorSchemeParams getColorSchemeParams(android.content.Intent, int);
+ method public static int getMaxToolbarItems();
+ method public void launchUrl(android.content.Context!, android.net.Uri!);
+ method public static android.content.Intent! setAlwaysUseBrowserUI(android.content.Intent!);
+ method public static boolean shouldAlwaysUseBrowserUI(android.content.Intent!);
+ field public static final int COLOR_SCHEME_DARK = 2; // 0x2
+ field public static final int COLOR_SCHEME_LIGHT = 1; // 0x1
+ field public static final int COLOR_SCHEME_SYSTEM = 0; // 0x0
+ field public static final String EXTRA_ACTION_BUTTON_BUNDLE = "android.support.customtabs.extra.ACTION_BUTTON_BUNDLE";
+ field public static final String EXTRA_CLOSE_BUTTON_ICON = "android.support.customtabs.extra.CLOSE_BUTTON_ICON";
+ field public static final String EXTRA_COLOR_SCHEME = "androidx.browser.customtabs.extra.COLOR_SCHEME";
+ field public static final String EXTRA_COLOR_SCHEME_PARAMS = "androidx.browser.customtabs.extra.COLOR_SCHEME_PARAMS";
+ field public static final String EXTRA_DEFAULT_SHARE_MENU_ITEM = "android.support.customtabs.extra.SHARE_MENU_ITEM";
+ field public static final String EXTRA_ENABLE_INSTANT_APPS = "android.support.customtabs.extra.EXTRA_ENABLE_INSTANT_APPS";
+ field public static final String EXTRA_ENABLE_URLBAR_HIDING = "android.support.customtabs.extra.ENABLE_URLBAR_HIDING";
+ field public static final String EXTRA_EXIT_ANIMATION_BUNDLE = "android.support.customtabs.extra.EXIT_ANIMATION_BUNDLE";
+ field public static final String EXTRA_MENU_ITEMS = "android.support.customtabs.extra.MENU_ITEMS";
+ field public static final String EXTRA_NAVIGATION_BAR_COLOR = "androidx.browser.customtabs.extra.NAVIGATION_BAR_COLOR";
+ field public static final String EXTRA_REMOTEVIEWS = "android.support.customtabs.extra.EXTRA_REMOTEVIEWS";
+ field public static final String EXTRA_REMOTEVIEWS_CLICKED_ID = "android.support.customtabs.extra.EXTRA_REMOTEVIEWS_CLICKED_ID";
+ field public static final String EXTRA_REMOTEVIEWS_PENDINGINTENT = "android.support.customtabs.extra.EXTRA_REMOTEVIEWS_PENDINGINTENT";
+ field public static final String EXTRA_REMOTEVIEWS_VIEW_IDS = "android.support.customtabs.extra.EXTRA_REMOTEVIEWS_VIEW_IDS";
+ field public static final String EXTRA_SECONDARY_TOOLBAR_COLOR = "android.support.customtabs.extra.SECONDARY_TOOLBAR_COLOR";
+ field public static final String EXTRA_SESSION = "android.support.customtabs.extra.SESSION";
+ field public static final String EXTRA_TINT_ACTION_BUTTON = "android.support.customtabs.extra.TINT_ACTION_BUTTON";
+ field public static final String EXTRA_TITLE_VISIBILITY_STATE = "android.support.customtabs.extra.TITLE_VISIBILITY";
+ field public static final String EXTRA_TOOLBAR_COLOR = "android.support.customtabs.extra.TOOLBAR_COLOR";
+ field public static final String EXTRA_TOOLBAR_ITEMS = "android.support.customtabs.extra.TOOLBAR_ITEMS";
+ field public static final String KEY_DESCRIPTION = "android.support.customtabs.customaction.DESCRIPTION";
+ field public static final String KEY_ICON = "android.support.customtabs.customaction.ICON";
+ field public static final String KEY_ID = "android.support.customtabs.customaction.ID";
+ field public static final String KEY_MENU_ITEM_TITLE = "android.support.customtabs.customaction.MENU_ITEM_TITLE";
+ field public static final String KEY_PENDING_INTENT = "android.support.customtabs.customaction.PENDING_INTENT";
+ field public static final int NO_TITLE = 0; // 0x0
+ field public static final int SHOW_PAGE_TITLE = 1; // 0x1
+ field public static final int TOOLBAR_ACTION_BUTTON_ID = 0; // 0x0
+ field public final android.content.Intent intent;
+ field public final android.os.Bundle? startAnimationBundle;
+ }
+
+ public static final class CustomTabsIntent.Builder {
+ ctor public CustomTabsIntent.Builder();
+ ctor public CustomTabsIntent.Builder(androidx.browser.customtabs.CustomTabsSession?);
+ method public androidx.browser.customtabs.CustomTabsIntent.Builder addDefaultShareMenuItem();
+ method public androidx.browser.customtabs.CustomTabsIntent.Builder addMenuItem(String, android.app.PendingIntent);
+ method @Deprecated public androidx.browser.customtabs.CustomTabsIntent.Builder addToolbarItem(int, android.graphics.Bitmap, String, android.app.PendingIntent!) throws java.lang.IllegalStateException;
+ method public androidx.browser.customtabs.CustomTabsIntent build();
+ method public androidx.browser.customtabs.CustomTabsIntent.Builder enableUrlBarHiding();
+ method public androidx.browser.customtabs.CustomTabsIntent.Builder setActionButton(android.graphics.Bitmap, String, android.app.PendingIntent, boolean);
+ method public androidx.browser.customtabs.CustomTabsIntent.Builder setActionButton(android.graphics.Bitmap, String, android.app.PendingIntent);
+ method public androidx.browser.customtabs.CustomTabsIntent.Builder setCloseButtonIcon(android.graphics.Bitmap);
+ method public androidx.browser.customtabs.CustomTabsIntent.Builder setColorScheme(int);
+ method public androidx.browser.customtabs.CustomTabsIntent.Builder setColorSchemeParams(int, androidx.browser.customtabs.CustomTabColorSchemeParams);
+ method public androidx.browser.customtabs.CustomTabsIntent.Builder setExitAnimations(android.content.Context, @AnimRes int, @AnimRes int);
+ method public androidx.browser.customtabs.CustomTabsIntent.Builder setInstantAppsEnabled(boolean);
+ method public androidx.browser.customtabs.CustomTabsIntent.Builder setNavigationBarColor(@ColorInt int);
+ method public androidx.browser.customtabs.CustomTabsIntent.Builder setSecondaryToolbarColor(@ColorInt int);
+ method public androidx.browser.customtabs.CustomTabsIntent.Builder setSecondaryToolbarViews(android.widget.RemoteViews, int[]?, android.app.PendingIntent?);
+ method public androidx.browser.customtabs.CustomTabsIntent.Builder setSession(androidx.browser.customtabs.CustomTabsSession);
+ method public androidx.browser.customtabs.CustomTabsIntent.Builder setShowTitle(boolean);
+ method public androidx.browser.customtabs.CustomTabsIntent.Builder setStartAnimations(android.content.Context, @AnimRes int, @AnimRes int);
+ method public androidx.browser.customtabs.CustomTabsIntent.Builder setToolbarColor(@ColorInt int);
+ }
+
+ public abstract class CustomTabsService extends android.app.Service {
+ ctor public CustomTabsService();
+ method protected boolean cleanUpSession(androidx.browser.customtabs.CustomTabsSessionToken!);
+ method protected abstract android.os.Bundle! extraCommand(String!, android.os.Bundle!);
+ method protected abstract boolean mayLaunchUrl(androidx.browser.customtabs.CustomTabsSessionToken!, android.net.Uri!, android.os.Bundle!, java.util.List<android.os.Bundle!>!);
+ method protected abstract boolean newSession(androidx.browser.customtabs.CustomTabsSessionToken!);
+ method public android.os.IBinder! onBind(android.content.Intent!);
+ method @androidx.browser.customtabs.CustomTabsService.Result protected abstract int postMessage(androidx.browser.customtabs.CustomTabsSessionToken!, String!, android.os.Bundle!);
+ method protected abstract boolean receiveFile(androidx.browser.customtabs.CustomTabsSessionToken, android.net.Uri, int, android.os.Bundle?);
+ method protected abstract boolean requestPostMessageChannel(androidx.browser.customtabs.CustomTabsSessionToken!, android.net.Uri!);
+ method protected abstract boolean updateVisuals(androidx.browser.customtabs.CustomTabsSessionToken!, android.os.Bundle!);
+ method protected abstract boolean validateRelationship(androidx.browser.customtabs.CustomTabsSessionToken!, @androidx.browser.customtabs.CustomTabsService.Relation int, android.net.Uri!, android.os.Bundle!);
+ method protected abstract boolean warmup(long);
+ field public static final String ACTION_CUSTOM_TABS_CONNECTION = "android.support.customtabs.action.CustomTabsService";
+ field public static final String CATEGORY_COLOR_SCHEME_CUSTOMIZATION = "androidx.browser.customtabs.category.ColorSchemeCustomization";
+ field public static final String CATEGORY_NAVBAR_COLOR_CUSTOMIZATION = "androidx.browser.customtabs.category.NavBarColorCustomization";
+ field public static final int FILE_PURPOSE_TRUSTED_WEB_ACTIVITY_SPLASH_IMAGE = 1; // 0x1
+ field public static final String KEY_URL = "android.support.customtabs.otherurls.URL";
+ field public static final int RELATION_HANDLE_ALL_URLS = 2; // 0x2
+ field public static final int RELATION_USE_AS_ORIGIN = 1; // 0x1
+ field public static final int RESULT_FAILURE_DISALLOWED = -1; // 0xffffffff
+ field public static final int RESULT_FAILURE_MESSAGING_ERROR = -3; // 0xfffffffd
+ field public static final int RESULT_FAILURE_REMOTE_ERROR = -2; // 0xfffffffe
+ field public static final int RESULT_SUCCESS = 0; // 0x0
+ field public static final String TRUSTED_WEB_ACTIVITY_CATEGORY = "androidx.browser.trusted.category.TrustedWebActivities";
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @IntDef({androidx.browser.customtabs.CustomTabsService.RELATION_USE_AS_ORIGIN, androidx.browser.customtabs.CustomTabsService.RELATION_HANDLE_ALL_URLS}) public static @interface CustomTabsService.Relation {
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @IntDef({androidx.browser.customtabs.CustomTabsService.RESULT_SUCCESS, androidx.browser.customtabs.CustomTabsService.RESULT_FAILURE_DISALLOWED, androidx.browser.customtabs.CustomTabsService.RESULT_FAILURE_REMOTE_ERROR, androidx.browser.customtabs.CustomTabsService.RESULT_FAILURE_MESSAGING_ERROR}) public static @interface CustomTabsService.Result {
+ }
+
+ public abstract class CustomTabsServiceConnection implements android.content.ServiceConnection {
+ ctor public CustomTabsServiceConnection();
+ method public abstract void onCustomTabsServiceConnected(android.content.ComponentName!, androidx.browser.customtabs.CustomTabsClient!);
+ method public final void onServiceConnected(android.content.ComponentName!, android.os.IBinder!);
+ }
+
+ public final class CustomTabsSession {
+ method @VisibleForTesting public static androidx.browser.customtabs.CustomTabsSession createMockSessionForTesting(android.content.ComponentName);
+ method public boolean mayLaunchUrl(android.net.Uri!, android.os.Bundle!, java.util.List<android.os.Bundle!>!);
+ method @androidx.browser.customtabs.CustomTabsService.Result public int postMessage(String!, android.os.Bundle!);
+ method public boolean receiveFile(android.net.Uri, int, android.os.Bundle?);
+ method public boolean requestPostMessageChannel(android.net.Uri!);
+ method public boolean setActionButton(android.graphics.Bitmap, String);
+ method public boolean setSecondaryToolbarViews(android.widget.RemoteViews?, int[]?, android.app.PendingIntent?);
+ method @Deprecated public boolean setToolbarItem(int, android.graphics.Bitmap, String);
+ method public boolean validateRelationship(@androidx.browser.customtabs.CustomTabsService.Relation int, android.net.Uri, android.os.Bundle?);
+ }
+
+ public class CustomTabsSessionToken {
+ method public static androidx.browser.customtabs.CustomTabsSessionToken createMockSessionTokenForTesting();
+ method public androidx.browser.customtabs.CustomTabsCallback? getCallback();
+ method public static androidx.browser.customtabs.CustomTabsSessionToken? getSessionTokenFromIntent(android.content.Intent);
+ method public boolean isAssociatedWith(androidx.browser.customtabs.CustomTabsSession);
+ }
+
+ public class PostMessageService extends android.app.Service {
+ ctor public PostMessageService();
+ method public android.os.IBinder! onBind(android.content.Intent!);
+ }
+
+ public abstract class PostMessageServiceConnection implements android.content.ServiceConnection {
+ ctor public PostMessageServiceConnection(androidx.browser.customtabs.CustomTabsSessionToken!);
+ method public boolean bindSessionToPostMessageService(android.content.Context!, String!);
+ method public final boolean notifyMessageChannelReady(android.os.Bundle!);
+ method public void onPostMessageServiceConnected();
+ method public void onPostMessageServiceDisconnected();
+ method public final void onServiceConnected(android.content.ComponentName!, android.os.IBinder!);
+ method public final void onServiceDisconnected(android.content.ComponentName!);
+ method public final boolean postMessage(String!, android.os.Bundle!);
+ method public void unbindFromContext(android.content.Context!);
+ }
+
+ public class TrustedWebUtils {
+ method @Deprecated public static void launchAsTrustedWebActivity(android.content.Context, androidx.browser.customtabs.CustomTabsIntent, android.net.Uri);
+ method public static boolean splashScreensAreSupported(android.content.Context, String, String);
+ method @WorkerThread public static boolean transferSplashImage(android.content.Context, java.io.File, String, String, androidx.browser.customtabs.CustomTabsSession);
+ field public static final String EXTRA_LAUNCH_AS_TRUSTED_WEB_ACTIVITY = "android.support.customtabs.extra.LAUNCH_AS_TRUSTED_WEB_ACTIVITY";
+ }
+
+}
+
+package androidx.browser.trusted {
+
+ public class TrustedWebActivityIntentBuilder {
+ ctor public TrustedWebActivityIntentBuilder(android.net.Uri);
+ method public android.content.Intent build(androidx.browser.customtabs.CustomTabsSession);
+ method public androidx.browser.customtabs.CustomTabsIntent buildCustomTabsIntent();
+ method public android.net.Uri getUrl();
+ method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setAdditionalTrustedOrigins(java.util.List<java.lang.String!>);
+ method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setColorScheme(int);
+ method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setColorSchemeParams(int, androidx.browser.customtabs.CustomTabColorSchemeParams);
+ method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setNavigationBarColor(@ColorInt int);
+ method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setSplashScreenParams(android.os.Bundle);
+ method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setToolbarColor(@ColorInt int);
+ field public static final String EXTRA_ADDITIONAL_TRUSTED_ORIGINS = "android.support.customtabs.extra.ADDITIONAL_TRUSTED_ORIGINS";
+ field public static final String EXTRA_SPLASH_SCREEN_PARAMS = "androidx.browser.trusted.EXTRA_SPLASH_SCREEN_PARAMS";
+ }
+
+ public class TrustedWebActivityService extends android.app.Service {
+ ctor public TrustedWebActivityService();
+ method public boolean areNotificationsEnabled(String);
+ method public void cancelNotification(String, int);
+ method public android.os.Bundle getSmallIconBitmap();
+ method public int getSmallIconId();
+ method public boolean notifyNotificationWithChannel(String, int, android.app.Notification, String);
+ method public final android.os.IBinder? onBind(android.content.Intent?);
+ method public final boolean onUnbind(android.content.Intent?);
+ method public static final void setVerifiedProvider(android.content.Context, String?);
+ field public static final String ACTION_TRUSTED_WEB_ACTIVITY_SERVICE = "android.support.customtabs.trusted.TRUSTED_WEB_ACTIVITY_SERVICE";
+ field public static final String KEY_SMALL_ICON_BITMAP = "android.support.customtabs.trusted.SMALL_ICON_BITMAP";
+ field public static final String META_DATA_NAME_SMALL_ICON = "android.support.customtabs.trusted.SMALL_ICON";
+ field public static final int SMALL_ICON_NOT_SET = -1; // 0xffffffff
+ }
+
+ public class TrustedWebActivityServiceConnectionManager {
+ ctor public TrustedWebActivityServiceConnectionManager(android.content.Context);
+ method @MainThread public boolean execute(android.net.Uri, String, androidx.browser.trusted.TrustedWebActivityServiceConnectionManager.ExecutionCallback);
+ method public static java.util.Set<java.lang.String!> getVerifiedPackages(android.content.Context, String);
+ method public static void registerClient(android.content.Context, String, String);
+ method @MainThread public boolean serviceExistsForScope(android.net.Uri, String);
+ }
+
+ public static interface TrustedWebActivityServiceConnectionManager.ExecutionCallback {
+ method public void onConnected(androidx.browser.trusted.TrustedWebActivityServiceWrapper?) throws android.os.RemoteException;
+ }
+
+ public class TrustedWebActivityServiceWrapper {
+ method public boolean areNotificationsEnabled(String);
+ method public void cancel(String, int);
+ method public android.content.ComponentName getComponentName();
+ method public android.graphics.Bitmap? getSmallIconBitmap();
+ method public int getSmallIconId();
+ method public boolean notify(String, int, android.app.Notification, String);
+ }
+
+}
+
+package androidx.browser.trusted.splashscreens {
+
+ public final class SplashScreenParamKey {
+ field public static final String BACKGROUND_COLOR = "androidx.browser.trusted.trusted.KEY_SPLASH_SCREEN_BACKGROUND_COLOR";
+ field public static final String FADE_OUT_DURATION_MS = "androidx.browser.trusted.KEY_SPLASH_SCREEN_FADE_OUT_DURATION";
+ field public static final String IMAGE_TRANSFORMATION_MATRIX = "androidx.browser.trusted.KEY_SPLASH_SCREEN_TRANSFORMATION_MATRIX";
+ field public static final String SCALE_TYPE = "androidx.browser.trusted.KEY_SPLASH_SCREEN_SCALE_TYPE";
+ field public static final String VERSION = "androidx.browser.trusted.KEY_SPLASH_SCREEN_VERSION";
+ }
+
+ public final class SplashScreenVersion {
+ field public static final String V1 = "androidx.browser.trusted.category.TrustedWebActivitySplashScreensV1";
+ }
+
+}
+
diff --git a/browser/api/current.txt b/browser/api/current.txt
index a38cd9b..798e756 100644
--- a/browser/api/current.txt
+++ b/browser/api/current.txt
@@ -259,6 +259,43 @@
field public static final String EXTRA_SPLASH_SCREEN_PARAMS = "androidx.browser.trusted.EXTRA_SPLASH_SCREEN_PARAMS";
}
+ public class TrustedWebActivityService extends android.app.Service {
+ ctor public TrustedWebActivityService();
+ method public boolean areNotificationsEnabled(String);
+ method public void cancelNotification(String, int);
+ method public android.os.Bundle getSmallIconBitmap();
+ method public int getSmallIconId();
+ method public boolean notifyNotificationWithChannel(String, int, android.app.Notification, String);
+ method public final android.os.IBinder? onBind(android.content.Intent?);
+ method public final boolean onUnbind(android.content.Intent?);
+ method public static final void setVerifiedProvider(android.content.Context, String?);
+ field public static final String ACTION_TRUSTED_WEB_ACTIVITY_SERVICE = "android.support.customtabs.trusted.TRUSTED_WEB_ACTIVITY_SERVICE";
+ field public static final String KEY_SMALL_ICON_BITMAP = "android.support.customtabs.trusted.SMALL_ICON_BITMAP";
+ field public static final String META_DATA_NAME_SMALL_ICON = "android.support.customtabs.trusted.SMALL_ICON";
+ field public static final int SMALL_ICON_NOT_SET = -1; // 0xffffffff
+ }
+
+ public class TrustedWebActivityServiceConnectionManager {
+ ctor public TrustedWebActivityServiceConnectionManager(android.content.Context);
+ method @MainThread public boolean execute(android.net.Uri, String, androidx.browser.trusted.TrustedWebActivityServiceConnectionManager.ExecutionCallback);
+ method public static java.util.Set<java.lang.String!> getVerifiedPackages(android.content.Context, String);
+ method public static void registerClient(android.content.Context, String, String);
+ method @MainThread public boolean serviceExistsForScope(android.net.Uri, String);
+ }
+
+ public static interface TrustedWebActivityServiceConnectionManager.ExecutionCallback {
+ method public void onConnected(androidx.browser.trusted.TrustedWebActivityServiceWrapper?) throws android.os.RemoteException;
+ }
+
+ public class TrustedWebActivityServiceWrapper {
+ method public boolean areNotificationsEnabled(String);
+ method public void cancel(String, int);
+ method public android.content.ComponentName getComponentName();
+ method public android.graphics.Bitmap? getSmallIconBitmap();
+ method public int getSmallIconId();
+ method public boolean notify(String, int, android.app.Notification, String);
+ }
+
}
package androidx.browser.trusted.splashscreens {
diff --git a/benchmark/api/res-1.0.0-alpha01.txt b/browser/api/res-1.2.0-alpha07.txt
similarity index 100%
copy from benchmark/api/res-1.0.0-alpha01.txt
copy to browser/api/res-1.2.0-alpha07.txt
diff --git a/browser/api/restricted_1.2.0-alpha07.txt b/browser/api/restricted_1.2.0-alpha07.txt
new file mode 100644
index 0000000..9cdf40e
--- /dev/null
+++ b/browser/api/restricted_1.2.0-alpha07.txt
@@ -0,0 +1,333 @@
+// Signature format: 3.0
+package androidx.browser.browseractions {
+
+ @Deprecated public class BrowserActionItem {
+ ctor @Deprecated public BrowserActionItem(String, android.app.PendingIntent, @DrawableRes int);
+ ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public BrowserActionItem(String, android.app.PendingIntent, android.net.Uri);
+ ctor @Deprecated public BrowserActionItem(String, android.app.PendingIntent);
+ method @Deprecated public android.app.PendingIntent getAction();
+ method @Deprecated public int getIconId();
+ method @Deprecated public String getTitle();
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class BrowserActionsFallbackMenuView extends android.widget.LinearLayout {
+ ctor public BrowserActionsFallbackMenuView(android.content.Context!, android.util.AttributeSet!);
+ }
+
+ @Deprecated public class BrowserActionsIntent {
+ method @Deprecated public static String? getCreatorPackageName(android.content.Intent);
+ method @Deprecated public android.content.Intent getIntent();
+ method @Deprecated public static String? getUntrustedCreatorPackageName(android.content.Intent);
+ method @Deprecated public static void launchIntent(android.content.Context!, android.content.Intent!);
+ method @Deprecated public static void openBrowserAction(android.content.Context!, android.net.Uri!);
+ method @Deprecated public static void openBrowserAction(android.content.Context!, android.net.Uri!, int, java.util.ArrayList<androidx.browser.browseractions.BrowserActionItem!>!, android.app.PendingIntent!);
+ method @Deprecated public static java.util.List<androidx.browser.browseractions.BrowserActionItem!>! parseBrowserActionItems(java.util.ArrayList<android.os.Bundle!>!);
+ field @Deprecated public static final String ACTION_BROWSER_ACTIONS_OPEN = "androidx.browser.browseractions.browser_action_open";
+ field @Deprecated public static final String EXTRA_APP_ID = "androidx.browser.browseractions.APP_ID";
+ field @Deprecated public static final String EXTRA_MENU_ITEMS = "androidx.browser.browseractions.extra.MENU_ITEMS";
+ field @Deprecated public static final String EXTRA_SELECTED_ACTION_PENDING_INTENT = "androidx.browser.browseractions.extra.SELECTED_ACTION_PENDING_INTENT";
+ field @Deprecated public static final String EXTRA_TYPE = "androidx.browser.browseractions.extra.TYPE";
+ field @Deprecated public static final int ITEM_COPY = 3; // 0x3
+ field @Deprecated public static final int ITEM_DOWNLOAD = 2; // 0x2
+ field @Deprecated public static final int ITEM_INVALID_ITEM = -1; // 0xffffffff
+ field @Deprecated public static final int ITEM_OPEN_IN_INCOGNITO = 1; // 0x1
+ field @Deprecated public static final int ITEM_OPEN_IN_NEW_TAB = 0; // 0x0
+ field @Deprecated public static final int ITEM_SHARE = 4; // 0x4
+ field @Deprecated public static final String KEY_ACTION = "androidx.browser.browseractions.ACTION";
+ field @Deprecated public static final String KEY_ICON_ID = "androidx.browser.browseractions.ICON_ID";
+ field @Deprecated public static final String KEY_TITLE = "androidx.browser.browseractions.TITLE";
+ field @Deprecated public static final int MAX_CUSTOM_ITEMS = 5; // 0x5
+ field @Deprecated public static final int URL_TYPE_AUDIO = 3; // 0x3
+ field @Deprecated public static final int URL_TYPE_FILE = 4; // 0x4
+ field @Deprecated public static final int URL_TYPE_IMAGE = 1; // 0x1
+ field @Deprecated public static final int URL_TYPE_NONE = 0; // 0x0
+ field @Deprecated public static final int URL_TYPE_PLUGIN = 5; // 0x5
+ field @Deprecated public static final int URL_TYPE_VIDEO = 2; // 0x2
+ }
+
+ @Deprecated @IntDef({androidx.browser.browseractions.BrowserActionsIntent.ITEM_INVALID_ITEM, androidx.browser.browseractions.BrowserActionsIntent.ITEM_OPEN_IN_NEW_TAB, androidx.browser.browseractions.BrowserActionsIntent.ITEM_OPEN_IN_INCOGNITO, androidx.browser.browseractions.BrowserActionsIntent.ITEM_DOWNLOAD, androidx.browser.browseractions.BrowserActionsIntent.ITEM_COPY, androidx.browser.browseractions.BrowserActionsIntent.ITEM_SHARE}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface BrowserActionsIntent.BrowserActionsItemId {
+ }
+
+ @Deprecated @IntDef({androidx.browser.browseractions.BrowserActionsIntent.URL_TYPE_NONE, androidx.browser.browseractions.BrowserActionsIntent.URL_TYPE_IMAGE, androidx.browser.browseractions.BrowserActionsIntent.URL_TYPE_VIDEO, androidx.browser.browseractions.BrowserActionsIntent.URL_TYPE_AUDIO, androidx.browser.browseractions.BrowserActionsIntent.URL_TYPE_FILE, androidx.browser.browseractions.BrowserActionsIntent.URL_TYPE_PLUGIN}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface BrowserActionsIntent.BrowserActionsUrlType {
+ }
+
+ @Deprecated public static final class BrowserActionsIntent.Builder {
+ ctor @Deprecated public BrowserActionsIntent.Builder(android.content.Context!, android.net.Uri!);
+ method @Deprecated public androidx.browser.browseractions.BrowserActionsIntent! build();
+ method @Deprecated public androidx.browser.browseractions.BrowserActionsIntent.Builder! setCustomItems(java.util.ArrayList<androidx.browser.browseractions.BrowserActionItem!>!);
+ method @Deprecated public androidx.browser.browseractions.BrowserActionsIntent.Builder! setCustomItems(androidx.browser.browseractions.BrowserActionItem!...);
+ method @Deprecated public androidx.browser.browseractions.BrowserActionsIntent.Builder! setOnItemSelectedAction(android.app.PendingIntent!);
+ method @Deprecated public androidx.browser.browseractions.BrowserActionsIntent.Builder! setUrlType(@androidx.browser.browseractions.BrowserActionsIntent.BrowserActionsUrlType int);
+ }
+
+
+}
+
+package androidx.browser.customtabs {
+
+ public final class CustomTabColorSchemeParams {
+ field @ColorInt public final Integer? navigationBarColor;
+ field @ColorInt public final Integer? secondaryToolbarColor;
+ field @ColorInt public final Integer? toolbarColor;
+ }
+
+ public static final class CustomTabColorSchemeParams.Builder {
+ ctor public CustomTabColorSchemeParams.Builder();
+ method public androidx.browser.customtabs.CustomTabColorSchemeParams build();
+ method public androidx.browser.customtabs.CustomTabColorSchemeParams.Builder setNavigationBarColor(@ColorInt int);
+ method public androidx.browser.customtabs.CustomTabColorSchemeParams.Builder setSecondaryToolbarColor(@ColorInt int);
+ method public androidx.browser.customtabs.CustomTabColorSchemeParams.Builder setToolbarColor(@ColorInt int);
+ }
+
+ public class CustomTabsCallback {
+ ctor public CustomTabsCallback();
+ method public void extraCallback(String!, android.os.Bundle!);
+ method public void onMessageChannelReady(android.os.Bundle!);
+ method public void onNavigationEvent(int, android.os.Bundle!);
+ method public void onPostMessage(String!, android.os.Bundle!);
+ method public void onRelationshipValidationResult(@androidx.browser.customtabs.CustomTabsService.Relation int, android.net.Uri!, boolean, android.os.Bundle!);
+ field public static final int NAVIGATION_ABORTED = 4; // 0x4
+ field public static final int NAVIGATION_FAILED = 3; // 0x3
+ field public static final int NAVIGATION_FINISHED = 2; // 0x2
+ field public static final int NAVIGATION_STARTED = 1; // 0x1
+ field public static final int TAB_HIDDEN = 6; // 0x6
+ field public static final int TAB_SHOWN = 5; // 0x5
+ }
+
+ public class CustomTabsClient {
+ method public static boolean bindCustomTabsService(android.content.Context, String?, androidx.browser.customtabs.CustomTabsServiceConnection);
+ method public static boolean connectAndInitialize(android.content.Context, String);
+ method public android.os.Bundle? extraCommand(String, android.os.Bundle?);
+ method public static String? getPackageName(android.content.Context, java.util.List<java.lang.String!>?);
+ method public static String? getPackageName(android.content.Context, java.util.List<java.lang.String!>?, boolean);
+ method public androidx.browser.customtabs.CustomTabsSession? newSession(androidx.browser.customtabs.CustomTabsCallback?);
+ method public androidx.browser.customtabs.CustomTabsSession? newSession(androidx.browser.customtabs.CustomTabsCallback?, int);
+ method public boolean warmup(long);
+ }
+
+ public final class CustomTabsIntent {
+ method public static androidx.browser.customtabs.CustomTabColorSchemeParams getColorSchemeParams(android.content.Intent, @androidx.browser.customtabs.CustomTabsIntent.ColorScheme int);
+ method public static int getMaxToolbarItems();
+ method public void launchUrl(android.content.Context!, android.net.Uri!);
+ method public static android.content.Intent! setAlwaysUseBrowserUI(android.content.Intent!);
+ method public static boolean shouldAlwaysUseBrowserUI(android.content.Intent!);
+ field public static final int COLOR_SCHEME_DARK = 2; // 0x2
+ field public static final int COLOR_SCHEME_LIGHT = 1; // 0x1
+ field public static final int COLOR_SCHEME_SYSTEM = 0; // 0x0
+ field public static final String EXTRA_ACTION_BUTTON_BUNDLE = "android.support.customtabs.extra.ACTION_BUTTON_BUNDLE";
+ field public static final String EXTRA_CLOSE_BUTTON_ICON = "android.support.customtabs.extra.CLOSE_BUTTON_ICON";
+ field public static final String EXTRA_COLOR_SCHEME = "androidx.browser.customtabs.extra.COLOR_SCHEME";
+ field public static final String EXTRA_COLOR_SCHEME_PARAMS = "androidx.browser.customtabs.extra.COLOR_SCHEME_PARAMS";
+ field public static final String EXTRA_DEFAULT_SHARE_MENU_ITEM = "android.support.customtabs.extra.SHARE_MENU_ITEM";
+ field public static final String EXTRA_ENABLE_INSTANT_APPS = "android.support.customtabs.extra.EXTRA_ENABLE_INSTANT_APPS";
+ field public static final String EXTRA_ENABLE_URLBAR_HIDING = "android.support.customtabs.extra.ENABLE_URLBAR_HIDING";
+ field public static final String EXTRA_EXIT_ANIMATION_BUNDLE = "android.support.customtabs.extra.EXIT_ANIMATION_BUNDLE";
+ field public static final String EXTRA_MENU_ITEMS = "android.support.customtabs.extra.MENU_ITEMS";
+ field public static final String EXTRA_NAVIGATION_BAR_COLOR = "androidx.browser.customtabs.extra.NAVIGATION_BAR_COLOR";
+ field public static final String EXTRA_REMOTEVIEWS = "android.support.customtabs.extra.EXTRA_REMOTEVIEWS";
+ field public static final String EXTRA_REMOTEVIEWS_CLICKED_ID = "android.support.customtabs.extra.EXTRA_REMOTEVIEWS_CLICKED_ID";
+ field public static final String EXTRA_REMOTEVIEWS_PENDINGINTENT = "android.support.customtabs.extra.EXTRA_REMOTEVIEWS_PENDINGINTENT";
+ field public static final String EXTRA_REMOTEVIEWS_VIEW_IDS = "android.support.customtabs.extra.EXTRA_REMOTEVIEWS_VIEW_IDS";
+ field public static final String EXTRA_SECONDARY_TOOLBAR_COLOR = "android.support.customtabs.extra.SECONDARY_TOOLBAR_COLOR";
+ field public static final String EXTRA_SESSION = "android.support.customtabs.extra.SESSION";
+ field public static final String EXTRA_TINT_ACTION_BUTTON = "android.support.customtabs.extra.TINT_ACTION_BUTTON";
+ field public static final String EXTRA_TITLE_VISIBILITY_STATE = "android.support.customtabs.extra.TITLE_VISIBILITY";
+ field public static final String EXTRA_TOOLBAR_COLOR = "android.support.customtabs.extra.TOOLBAR_COLOR";
+ field public static final String EXTRA_TOOLBAR_ITEMS = "android.support.customtabs.extra.TOOLBAR_ITEMS";
+ field public static final String KEY_DESCRIPTION = "android.support.customtabs.customaction.DESCRIPTION";
+ field public static final String KEY_ICON = "android.support.customtabs.customaction.ICON";
+ field public static final String KEY_ID = "android.support.customtabs.customaction.ID";
+ field public static final String KEY_MENU_ITEM_TITLE = "android.support.customtabs.customaction.MENU_ITEM_TITLE";
+ field public static final String KEY_PENDING_INTENT = "android.support.customtabs.customaction.PENDING_INTENT";
+ field public static final int NO_TITLE = 0; // 0x0
+ field public static final int SHOW_PAGE_TITLE = 1; // 0x1
+ field public static final int TOOLBAR_ACTION_BUTTON_ID = 0; // 0x0
+ field public final android.content.Intent intent;
+ field public final android.os.Bundle? startAnimationBundle;
+ }
+
+ public static final class CustomTabsIntent.Builder {
+ ctor public CustomTabsIntent.Builder();
+ ctor public CustomTabsIntent.Builder(androidx.browser.customtabs.CustomTabsSession?);
+ method public androidx.browser.customtabs.CustomTabsIntent.Builder addDefaultShareMenuItem();
+ method public androidx.browser.customtabs.CustomTabsIntent.Builder addMenuItem(String, android.app.PendingIntent);
+ method @Deprecated public androidx.browser.customtabs.CustomTabsIntent.Builder addToolbarItem(int, android.graphics.Bitmap, String, android.app.PendingIntent!) throws java.lang.IllegalStateException;
+ method public androidx.browser.customtabs.CustomTabsIntent build();
+ method public androidx.browser.customtabs.CustomTabsIntent.Builder enableUrlBarHiding();
+ method public androidx.browser.customtabs.CustomTabsIntent.Builder setActionButton(android.graphics.Bitmap, String, android.app.PendingIntent, boolean);
+ method public androidx.browser.customtabs.CustomTabsIntent.Builder setActionButton(android.graphics.Bitmap, String, android.app.PendingIntent);
+ method public androidx.browser.customtabs.CustomTabsIntent.Builder setCloseButtonIcon(android.graphics.Bitmap);
+ method public androidx.browser.customtabs.CustomTabsIntent.Builder setColorScheme(@androidx.browser.customtabs.CustomTabsIntent.ColorScheme int);
+ method public androidx.browser.customtabs.CustomTabsIntent.Builder setColorSchemeParams(@androidx.browser.customtabs.CustomTabsIntent.ColorScheme int, androidx.browser.customtabs.CustomTabColorSchemeParams);
+ method public androidx.browser.customtabs.CustomTabsIntent.Builder setExitAnimations(android.content.Context, @AnimRes int, @AnimRes int);
+ method public androidx.browser.customtabs.CustomTabsIntent.Builder setInstantAppsEnabled(boolean);
+ method public androidx.browser.customtabs.CustomTabsIntent.Builder setNavigationBarColor(@ColorInt int);
+ method public androidx.browser.customtabs.CustomTabsIntent.Builder setSecondaryToolbarColor(@ColorInt int);
+ method public androidx.browser.customtabs.CustomTabsIntent.Builder setSecondaryToolbarViews(android.widget.RemoteViews, int[]?, android.app.PendingIntent?);
+ method public androidx.browser.customtabs.CustomTabsIntent.Builder setSession(androidx.browser.customtabs.CustomTabsSession);
+ method public androidx.browser.customtabs.CustomTabsIntent.Builder setShowTitle(boolean);
+ method public androidx.browser.customtabs.CustomTabsIntent.Builder setStartAnimations(android.content.Context, @AnimRes int, @AnimRes int);
+ method public androidx.browser.customtabs.CustomTabsIntent.Builder setToolbarColor(@ColorInt int);
+ }
+
+
+ public abstract class CustomTabsService extends android.app.Service {
+ ctor public CustomTabsService();
+ method protected boolean cleanUpSession(androidx.browser.customtabs.CustomTabsSessionToken!);
+ method protected abstract android.os.Bundle! extraCommand(String!, android.os.Bundle!);
+ method protected abstract boolean mayLaunchUrl(androidx.browser.customtabs.CustomTabsSessionToken!, android.net.Uri!, android.os.Bundle!, java.util.List<android.os.Bundle!>!);
+ method protected abstract boolean newSession(androidx.browser.customtabs.CustomTabsSessionToken!);
+ method public android.os.IBinder! onBind(android.content.Intent!);
+ method @androidx.browser.customtabs.CustomTabsService.Result protected abstract int postMessage(androidx.browser.customtabs.CustomTabsSessionToken!, String!, android.os.Bundle!);
+ method protected abstract boolean receiveFile(androidx.browser.customtabs.CustomTabsSessionToken, android.net.Uri, @androidx.browser.customtabs.CustomTabsService.FilePurpose int, android.os.Bundle?);
+ method protected abstract boolean requestPostMessageChannel(androidx.browser.customtabs.CustomTabsSessionToken!, android.net.Uri!);
+ method protected abstract boolean updateVisuals(androidx.browser.customtabs.CustomTabsSessionToken!, android.os.Bundle!);
+ method protected abstract boolean validateRelationship(androidx.browser.customtabs.CustomTabsSessionToken!, @androidx.browser.customtabs.CustomTabsService.Relation int, android.net.Uri!, android.os.Bundle!);
+ method protected abstract boolean warmup(long);
+ field public static final String ACTION_CUSTOM_TABS_CONNECTION = "android.support.customtabs.action.CustomTabsService";
+ field public static final String CATEGORY_COLOR_SCHEME_CUSTOMIZATION = "androidx.browser.customtabs.category.ColorSchemeCustomization";
+ field public static final String CATEGORY_NAVBAR_COLOR_CUSTOMIZATION = "androidx.browser.customtabs.category.NavBarColorCustomization";
+ field public static final int FILE_PURPOSE_TRUSTED_WEB_ACTIVITY_SPLASH_IMAGE = 1; // 0x1
+ field public static final String KEY_URL = "android.support.customtabs.otherurls.URL";
+ field public static final int RELATION_HANDLE_ALL_URLS = 2; // 0x2
+ field public static final int RELATION_USE_AS_ORIGIN = 1; // 0x1
+ field public static final int RESULT_FAILURE_DISALLOWED = -1; // 0xffffffff
+ field public static final int RESULT_FAILURE_MESSAGING_ERROR = -3; // 0xfffffffd
+ field public static final int RESULT_FAILURE_REMOTE_ERROR = -2; // 0xfffffffe
+ field public static final int RESULT_SUCCESS = 0; // 0x0
+ field public static final String TRUSTED_WEB_ACTIVITY_CATEGORY = "androidx.browser.trusted.category.TrustedWebActivities";
+ }
+
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @IntDef({androidx.browser.customtabs.CustomTabsService.RELATION_USE_AS_ORIGIN, androidx.browser.customtabs.CustomTabsService.RELATION_HANDLE_ALL_URLS}) public static @interface CustomTabsService.Relation {
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @IntDef({androidx.browser.customtabs.CustomTabsService.RESULT_SUCCESS, androidx.browser.customtabs.CustomTabsService.RESULT_FAILURE_DISALLOWED, androidx.browser.customtabs.CustomTabsService.RESULT_FAILURE_REMOTE_ERROR, androidx.browser.customtabs.CustomTabsService.RESULT_FAILURE_MESSAGING_ERROR}) public static @interface CustomTabsService.Result {
+ }
+
+ public abstract class CustomTabsServiceConnection implements android.content.ServiceConnection {
+ ctor public CustomTabsServiceConnection();
+ method public abstract void onCustomTabsServiceConnected(android.content.ComponentName!, androidx.browser.customtabs.CustomTabsClient!);
+ method public final void onServiceConnected(android.content.ComponentName!, android.os.IBinder!);
+ }
+
+ public final class CustomTabsSession {
+ method @VisibleForTesting public static androidx.browser.customtabs.CustomTabsSession createMockSessionForTesting(android.content.ComponentName);
+ method public boolean mayLaunchUrl(android.net.Uri!, android.os.Bundle!, java.util.List<android.os.Bundle!>!);
+ method @androidx.browser.customtabs.CustomTabsService.Result public int postMessage(String!, android.os.Bundle!);
+ method public boolean receiveFile(android.net.Uri, @androidx.browser.customtabs.CustomTabsService.FilePurpose int, android.os.Bundle?);
+ method public boolean requestPostMessageChannel(android.net.Uri!);
+ method public boolean setActionButton(android.graphics.Bitmap, String);
+ method public boolean setSecondaryToolbarViews(android.widget.RemoteViews?, int[]?, android.app.PendingIntent?);
+ method @Deprecated public boolean setToolbarItem(int, android.graphics.Bitmap, String);
+ method public boolean validateRelationship(@androidx.browser.customtabs.CustomTabsService.Relation int, android.net.Uri, android.os.Bundle?);
+ }
+
+
+ public class CustomTabsSessionToken {
+ method public static androidx.browser.customtabs.CustomTabsSessionToken createMockSessionTokenForTesting();
+ method public androidx.browser.customtabs.CustomTabsCallback? getCallback();
+ method public static androidx.browser.customtabs.CustomTabsSessionToken? getSessionTokenFromIntent(android.content.Intent);
+ method public boolean isAssociatedWith(androidx.browser.customtabs.CustomTabsSession);
+ }
+
+
+ public class PostMessageService extends android.app.Service {
+ ctor public PostMessageService();
+ method public android.os.IBinder! onBind(android.content.Intent!);
+ }
+
+ public abstract class PostMessageServiceConnection implements androidx.browser.customtabs.PostMessageBackend android.content.ServiceConnection {
+ ctor public PostMessageServiceConnection(androidx.browser.customtabs.CustomTabsSessionToken!);
+ method public boolean bindSessionToPostMessageService(android.content.Context!, String!);
+ method public final boolean notifyMessageChannelReady(android.os.Bundle!);
+ method public void onPostMessageServiceConnected();
+ method public void onPostMessageServiceDisconnected();
+ method public final void onServiceConnected(android.content.ComponentName!, android.os.IBinder!);
+ method public final void onServiceDisconnected(android.content.ComponentName!);
+ method public final boolean postMessage(String!, android.os.Bundle!);
+ method public void unbindFromContext(android.content.Context!);
+ }
+
+ public class TrustedWebUtils {
+ method @Deprecated public static void launchAsTrustedWebActivity(android.content.Context, androidx.browser.customtabs.CustomTabsIntent, android.net.Uri);
+ method public static boolean splashScreensAreSupported(android.content.Context, String, String);
+ method @WorkerThread public static boolean transferSplashImage(android.content.Context, java.io.File, String, String, androidx.browser.customtabs.CustomTabsSession);
+ field public static final String EXTRA_LAUNCH_AS_TRUSTED_WEB_ACTIVITY = "android.support.customtabs.extra.LAUNCH_AS_TRUSTED_WEB_ACTIVITY";
+ }
+
+}
+
+package androidx.browser.trusted {
+
+
+ public class TrustedWebActivityIntentBuilder {
+ ctor public TrustedWebActivityIntentBuilder(android.net.Uri);
+ method public android.content.Intent build(androidx.browser.customtabs.CustomTabsSession);
+ method public androidx.browser.customtabs.CustomTabsIntent buildCustomTabsIntent();
+ method public android.net.Uri getUrl();
+ method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setAdditionalTrustedOrigins(java.util.List<java.lang.String!>);
+ method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setColorScheme(int);
+ method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setColorSchemeParams(int, androidx.browser.customtabs.CustomTabColorSchemeParams);
+ method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setNavigationBarColor(@ColorInt int);
+ method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setSplashScreenParams(android.os.Bundle);
+ method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setToolbarColor(@ColorInt int);
+ field public static final String EXTRA_ADDITIONAL_TRUSTED_ORIGINS = "android.support.customtabs.extra.ADDITIONAL_TRUSTED_ORIGINS";
+ field public static final String EXTRA_SPLASH_SCREEN_PARAMS = "androidx.browser.trusted.EXTRA_SPLASH_SCREEN_PARAMS";
+ }
+
+ public class TrustedWebActivityService extends android.app.Service {
+ ctor public TrustedWebActivityService();
+ method public boolean areNotificationsEnabled(String);
+ method public void cancelNotification(String, int);
+ method public android.os.Bundle getSmallIconBitmap();
+ method public int getSmallIconId();
+ method public boolean notifyNotificationWithChannel(String, int, android.app.Notification, String);
+ method public final android.os.IBinder? onBind(android.content.Intent?);
+ method public final boolean onUnbind(android.content.Intent?);
+ method public static final void setVerifiedProvider(android.content.Context, String?);
+ field public static final String ACTION_TRUSTED_WEB_ACTIVITY_SERVICE = "android.support.customtabs.trusted.TRUSTED_WEB_ACTIVITY_SERVICE";
+ field public static final String KEY_SMALL_ICON_BITMAP = "android.support.customtabs.trusted.SMALL_ICON_BITMAP";
+ field public static final String META_DATA_NAME_SMALL_ICON = "android.support.customtabs.trusted.SMALL_ICON";
+ field public static final int SMALL_ICON_NOT_SET = -1; // 0xffffffff
+ }
+
+ public class TrustedWebActivityServiceConnectionManager {
+ ctor public TrustedWebActivityServiceConnectionManager(android.content.Context);
+ method @MainThread public boolean execute(android.net.Uri, String, androidx.browser.trusted.TrustedWebActivityServiceConnectionManager.ExecutionCallback);
+ method public static java.util.Set<java.lang.String!> getVerifiedPackages(android.content.Context, String);
+ method public static void registerClient(android.content.Context, String, String);
+ method @MainThread public boolean serviceExistsForScope(android.net.Uri, String);
+ }
+
+ public static interface TrustedWebActivityServiceConnectionManager.ExecutionCallback {
+ method public void onConnected(androidx.browser.trusted.TrustedWebActivityServiceWrapper?) throws android.os.RemoteException;
+ }
+
+ public class TrustedWebActivityServiceWrapper {
+ method public boolean areNotificationsEnabled(String);
+ method public void cancel(String, int);
+ method public android.content.ComponentName getComponentName();
+ method public android.graphics.Bitmap? getSmallIconBitmap();
+ method public int getSmallIconId();
+ method public boolean notify(String, int, android.app.Notification, String);
+ }
+
+}
+
+package androidx.browser.trusted.splashscreens {
+
+ public final class SplashScreenParamKey {
+ field public static final String BACKGROUND_COLOR = "androidx.browser.trusted.trusted.KEY_SPLASH_SCREEN_BACKGROUND_COLOR";
+ field public static final String FADE_OUT_DURATION_MS = "androidx.browser.trusted.KEY_SPLASH_SCREEN_FADE_OUT_DURATION";
+ field public static final String IMAGE_TRANSFORMATION_MATRIX = "androidx.browser.trusted.KEY_SPLASH_SCREEN_TRANSFORMATION_MATRIX";
+ field public static final String SCALE_TYPE = "androidx.browser.trusted.KEY_SPLASH_SCREEN_SCALE_TYPE";
+ field public static final String VERSION = "androidx.browser.trusted.KEY_SPLASH_SCREEN_VERSION";
+ }
+
+ public final class SplashScreenVersion {
+ field public static final String V1 = "androidx.browser.trusted.category.TrustedWebActivitySplashScreensV1";
+ }
+
+}
+
diff --git a/browser/api/restricted_current.txt b/browser/api/restricted_current.txt
index 8f41848..9cdf40e 100644
--- a/browser/api/restricted_current.txt
+++ b/browser/api/restricted_current.txt
@@ -276,9 +276,42 @@
field public static final String EXTRA_SPLASH_SCREEN_PARAMS = "androidx.browser.trusted.EXTRA_SPLASH_SCREEN_PARAMS";
}
+ public class TrustedWebActivityService extends android.app.Service {
+ ctor public TrustedWebActivityService();
+ method public boolean areNotificationsEnabled(String);
+ method public void cancelNotification(String, int);
+ method public android.os.Bundle getSmallIconBitmap();
+ method public int getSmallIconId();
+ method public boolean notifyNotificationWithChannel(String, int, android.app.Notification, String);
+ method public final android.os.IBinder? onBind(android.content.Intent?);
+ method public final boolean onUnbind(android.content.Intent?);
+ method public static final void setVerifiedProvider(android.content.Context, String?);
+ field public static final String ACTION_TRUSTED_WEB_ACTIVITY_SERVICE = "android.support.customtabs.trusted.TRUSTED_WEB_ACTIVITY_SERVICE";
+ field public static final String KEY_SMALL_ICON_BITMAP = "android.support.customtabs.trusted.SMALL_ICON_BITMAP";
+ field public static final String META_DATA_NAME_SMALL_ICON = "android.support.customtabs.trusted.SMALL_ICON";
+ field public static final int SMALL_ICON_NOT_SET = -1; // 0xffffffff
+ }
+ public class TrustedWebActivityServiceConnectionManager {
+ ctor public TrustedWebActivityServiceConnectionManager(android.content.Context);
+ method @MainThread public boolean execute(android.net.Uri, String, androidx.browser.trusted.TrustedWebActivityServiceConnectionManager.ExecutionCallback);
+ method public static java.util.Set<java.lang.String!> getVerifiedPackages(android.content.Context, String);
+ method public static void registerClient(android.content.Context, String, String);
+ method @MainThread public boolean serviceExistsForScope(android.net.Uri, String);
+ }
+ public static interface TrustedWebActivityServiceConnectionManager.ExecutionCallback {
+ method public void onConnected(androidx.browser.trusted.TrustedWebActivityServiceWrapper?) throws android.os.RemoteException;
+ }
+ public class TrustedWebActivityServiceWrapper {
+ method public boolean areNotificationsEnabled(String);
+ method public void cancel(String, int);
+ method public android.content.ComponentName getComponentName();
+ method public android.graphics.Bitmap? getSmallIconBitmap();
+ method public int getSmallIconId();
+ method public boolean notify(String, int, android.app.Notification, String);
+ }
}
diff --git a/browser/build.gradle b/browser/build.gradle
index ccddcd8..06018de 100644
--- a/browser/build.gradle
+++ b/browser/build.gradle
@@ -19,10 +19,10 @@
dependencies {
api("androidx.core:core:1.1.0-rc01")
api("androidx.annotation:annotation:1.1.0")
- api(project(":interpolator"))
- implementation("androidx.collection:collection:1.1.0")
- implementation(project(":concurrent:concurrent-futures"))
+ implementation("androidx.collection:collection:1.1.0")
+ implementation("androidx.concurrent:concurrent-futures:1.0.0-beta01")
+ implementation("androidx.interpolator:interpolator:1.0.0")
annotationProcessor(NULLAWAY)
diff --git a/browser/src/androidTest/java/androidx/browser/trusted/TestTrustedWebActivityService.java b/browser/src/androidTest/java/androidx/browser/trusted/TestTrustedWebActivityService.java
index d4f87fd..37574f0 100644
--- a/browser/src/androidTest/java/androidx/browser/trusted/TestTrustedWebActivityService.java
+++ b/browser/src/androidTest/java/androidx/browser/trusted/TestTrustedWebActivityService.java
@@ -19,26 +19,28 @@
import android.app.Notification;
import android.os.Parcelable;
+import androidx.annotation.NonNull;
+
public class TestTrustedWebActivityService extends TrustedWebActivityService {
public static final int SMALL_ICON_ID = 666;
@Override
- protected boolean notifyNotificationWithChannel(String platformTag, int platformId,
- Notification notification, String channelName) {
+ public boolean notifyNotificationWithChannel(@NonNull String platformTag, int platformId,
+ @NonNull Notification notification, @NonNull String channelName) {
return true;
}
@Override
- protected void cancelNotification(String platformTag, int platformId) {
+ public void cancelNotification(@NonNull String platformTag, int platformId) {
}
@Override
- protected Parcelable[] getActiveNotifications() {
+ public Parcelable[] getActiveNotifications() {
return new Parcelable[] { null };
}
@Override
- protected int getSmallIconId() {
+ public int getSmallIconId() {
return SMALL_ICON_ID;
}
}
diff --git a/browser/src/main/java/androidx/browser/trusted/TrustedWebActivityService.java b/browser/src/main/java/androidx/browser/trusted/TrustedWebActivityService.java
index efa1e34..4b08730 100644
--- a/browser/src/main/java/androidx/browser/trusted/TrustedWebActivityService.java
+++ b/browser/src/main/java/androidx/browser/trusted/TrustedWebActivityService.java
@@ -16,6 +16,7 @@
package androidx.browser.trusted;
+import android.annotation.SuppressLint;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.Service;
@@ -35,6 +36,7 @@
import android.support.customtabs.trusted.ITrustedWebActivityService;
import androidx.annotation.CallSuper;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.browser.trusted.TrustedWebActivityServiceWrapper.ActiveNotificationsArgs;
@@ -49,7 +51,7 @@
/**
* The TrustedWebActivityService lives in a client app and serves requests from a Trusted Web
- * Activity provider. At present it only serves requests to display notifications.
+ * Activity provider. At present it only serves requests to do with notifications.
* <p>
* When the provider receives a notification from a scope that is associated with a Trusted Web
* Activity client app, it will attempt to connect to a TrustedWebActivityService and forward calls.
@@ -60,7 +62,7 @@
*
* <pre>
* <service
- * android:name="android.support.customtabs.trusted.TrustedWebActivityService"
+ * android:name="androidx.browser.trusted.TrustedWebActivityService"
* android:enabled="true"
* android:exported="true">
*
@@ -77,42 +79,43 @@
* The SMALL_ICON resource should point to a drawable to be used for the notification's small icon.
* <p>
* Alternatively for greater customization, TrustedWebActivityService can be extended and
- * {@link #onCreate}, {@link #getSmallIconId}, {@link #notifyNotificationWithChannel} and
- * {@link #cancelNotification} can be overridden. In this case the manifest entry should be updated
- * to point to the extending class.
+ * overridden. In this case the manifest entry should be updated to point to the extending class.
* <p>
* As this is an AIDL Service, calls to {@link #getSmallIconId},
* {@link #notifyNotificationWithChannel} and {@link #cancelNotification} can occur on different
* Binder threads, so overriding implementations need to be thread-safe.
* <p>
* For security, the TrustedWebActivityService will check that whatever connects to it is the
- * Trusted Web Activity provider that it was previously verified with. For testing,
- * {@link #setVerifiedProviderSynchronouslyForTesting} can be used to to allow connections from the
- * given package.
- *
- * @hide
+ * Trusted Web Activity provider that it was previously verified with (through
+ * {@link #setVerifiedProvider}).
*/
-@RestrictTo(RestrictTo.Scope.LIBRARY)
public class TrustedWebActivityService extends Service {
/** An Intent Action used by the provider to find the TrustedWebActivityService or subclass. */
- public static final String INTENT_ACTION =
+ @SuppressLint({
+ "ActionValue", // This value was being used before being moved into AndroidX.
+ "ServiceName", // This variable is an Action, but Metalava thinks it's a Service.
+ })
+ public static final String ACTION_TRUSTED_WEB_ACTIVITY_SERVICE =
"android.support.customtabs.trusted.TRUSTED_WEB_ACTIVITY_SERVICE";
+
/** The Android Manifest meta-data name to specify a small icon id to use. */
- public static final String SMALL_ICON_META_DATA_NAME =
+ public static final String META_DATA_NAME_SMALL_ICON =
"android.support.customtabs.trusted.SMALL_ICON";
+ /** The key to use to store a Bitmap to return from the {@link #getSmallIconBitmap()} method. */
+ public static final String KEY_SMALL_ICON_BITMAP =
+ "android.support.customtabs.trusted.SMALL_ICON_BITMAP";
+
/** Used as a return value of {@link #getSmallIconId} when the icon is not provided. */
- public static final int NO_ID = -1;
+ public static final int SMALL_ICON_NOT_SET = -1;
private static final String PREFS_FILE = "TrustedWebActivityVerifiedProvider";
private static final String PREFS_VERIFIED_PROVIDER = "Provider";
- static final String KEY_SMALL_ICON_BITMAP =
- "android.support.customtabs.trusted.SMALL_ICON_BITMAP";
-
private NotificationManager mNotificationManager;
- public int mVerifiedUid = -1;
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ int mVerifiedUid = -1;
private final ITrustedWebActivityService.Stub mBinder =
new ITrustedWebActivityService.Stub() {
@@ -214,7 +217,7 @@
* @param channelName The name of the notification channel to be used on Android O+.
* @return Whether notifications are enabled.
*/
- protected boolean areNotificationsEnabled(String channelName) {
+ public boolean areNotificationsEnabled(@NonNull String channelName) {
ensureOnCreateCalled();
if (!NotificationManagerCompat.from(this).areNotificationsEnabled()) return false;
@@ -238,8 +241,8 @@
* @return Whether the notification was successfully displayed (the channel/app may be blocked
* by the user).
*/
- protected boolean notifyNotificationWithChannel(String platformTag, int platformId,
- Notification notification, String channelName) {
+ public boolean notifyNotificationWithChannel(@NonNull String platformTag, int platformId,
+ @NonNull Notification notification, @NonNull String channelName) {
ensureOnCreateCalled();
if (!NotificationManagerCompat.from(this).areNotificationsEnabled()) return false;
@@ -265,7 +268,7 @@
* @param platformId The notification id, see
* {@link NotificationManager#cancel(String, int)}.
*/
- protected void cancelNotification(String platformTag, int platformId) {
+ public void cancelNotification(@NonNull String platformTag, int platformId) {
ensureOnCreateCalled();
mNotificationManager.cancel(platformTag, platformId);
}
@@ -275,9 +278,11 @@
* NotificationManager#getActiveNotifications. The default implementation does not work on
* pre-Android M.
* @return An array of StatusBarNotifications as Parcelables.
+ *
+ * @hide
*/
-
- protected Parcelable[] getActiveNotifications() {
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ public Parcelable[] getActiveNotifications() {
ensureOnCreateCalled();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return NotificationApiHelperForM.getActiveNotifications(mNotificationManager);
@@ -285,10 +290,15 @@
throw new IllegalStateException("getActiveNotifications cannot be called pre-M.");
}
- Bundle getSmallIconBitmap() {
+ /**
+ * Returns a Bundle containing a bitmap to be use as the small icon for any notifications.
+ * @return A Bundle that may contain a Bitmap contained with key {@link #KEY_SMALL_ICON_BITMAP}.
+ * The bundle may be empty if the client app does not provide a small icon.
+ */
+ public @NonNull Bundle getSmallIconBitmap() {
int id = getSmallIconId();
Bundle bundle = new Bundle();
- if (id == NO_ID) {
+ if (id == SMALL_ICON_NOT_SET) {
return bundle;
}
bundle.putParcelable(KEY_SMALL_ICON_BITMAP,
@@ -298,35 +308,36 @@
/**
* Returns the Android resource id of a drawable to be used for the small icon of the
- * notification. This is called by the provider as it is constructing the notification, so a
+ * notification. This is called by the provider as it is constructing the notification so a
* complete notification can be passed to the client.
*
- * Default behaviour looks for meta-data with the name {@link #SMALL_ICON_META_DATA_NAME} in
+ * Default behaviour looks for meta-data with the name {@link #META_DATA_NAME_SMALL_ICON} in
* service section of the manifest.
- * @return A resource id for the small icon, or {@link #NO_ID} if not found.
+ * @return A resource id for the small icon, or {@link #SMALL_ICON_NOT_SET} if not found.
*/
- protected int getSmallIconId() {
+ public int getSmallIconId() {
try {
ServiceInfo info = getPackageManager().getServiceInfo(
new ComponentName(this, getClass()), PackageManager.GET_META_DATA);
- if (info.metaData == null) return NO_ID;
+ if (info.metaData == null) return SMALL_ICON_NOT_SET;
- return info.metaData.getInt(SMALL_ICON_META_DATA_NAME, NO_ID);
+ return info.metaData.getInt(META_DATA_NAME_SMALL_ICON, SMALL_ICON_NOT_SET);
} catch (PackageManager.NameNotFoundException e) {
// Will only happen if the package provided (the one we are running in) is not
// installed - so should never happen.
- return NO_ID;
+ return SMALL_ICON_NOT_SET;
}
}
@Override
- public final IBinder onBind(Intent intent) {
+ @Nullable
+ public final IBinder onBind(@Nullable Intent intent) {
return mBinder;
}
@Override
- public final boolean onUnbind(Intent intent) {
+ public final boolean onUnbind(@Nullable Intent intent) {
mVerifiedUid = -1;
return super.onUnbind(intent);
@@ -335,6 +346,7 @@
/**
* Should *not* be called on UI Thread, as accessing Preferences may hit disk.
*/
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
static SharedPreferences getPreferences(Context context) {
return context.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE);
}
@@ -343,10 +355,9 @@
* Sets (asynchronously) the package that this service will accept connections from.
* @param context A context to be used to access SharedPreferences.
* @param provider The package of the provider to accept connections from or null to clear.
- * @hide
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY)
- public static final void setVerifiedProvider(final Context context, @Nullable String provider) {
+ public static final void setVerifiedProvider(final @NonNull Context context,
+ @Nullable String provider) {
final String providerEmptyChecked =
(provider == null || provider.isEmpty()) ? null : provider;
diff --git a/browser/src/main/java/androidx/browser/trusted/TrustedWebActivityServiceConnectionManager.java b/browser/src/main/java/androidx/browser/trusted/TrustedWebActivityServiceConnectionManager.java
index d13c9dc..6dbcebe 100644
--- a/browser/src/main/java/androidx/browser/trusted/TrustedWebActivityServiceConnectionManager.java
+++ b/browser/src/main/java/androidx/browser/trusted/TrustedWebActivityServiceConnectionManager.java
@@ -33,8 +33,9 @@
import android.support.customtabs.trusted.ITrustedWebActivityService;
import android.util.Log;
+import androidx.annotation.MainThread;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
import java.util.Collections;
import java.util.HashMap;
@@ -49,38 +50,31 @@
* A TrustedWebActivityServiceConnectionManager will be used by a Trusted Web Activity provider and
* takes care of connecting to and communicating with {@link TrustedWebActivityService}s.
* <p>
- * Trusted Web Activity client apps are registered with the {@link #registerClient}, associating a
+ * Trusted Web Activity client apps are registered with {@link #registerClient}, associating a
* package with an origin. There may be multiple packages associated with a single origin.
* Note, the origins are essentially keys to a map of origin to package name - while they
* semantically are web origins, they aren't used that way.
* <p>
* To interact with a {@link TrustedWebActivityService}, call {@link #execute}.
- *
- * @hide
*/
-@RestrictTo(RestrictTo.Scope.LIBRARY)
public class TrustedWebActivityServiceConnectionManager {
private static final String TAG = "TWAConnectionManager";
private static final String PREFS_FILE = "TrustedWebActivityVerifiedPackages";
/**
* A callback to be executed once a connection to a {@link TrustedWebActivityService} is open.
- *
- * @hide
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY)
public interface ExecutionCallback {
/**
- * Is run when a connection is open.
- * See {@link #execute} for more information.
+ * Is run when a connection is open. See {@link #execute} for more information.
* @param service A {@link TrustedWebActivityServiceWrapper} wrapping the connected
* {@link TrustedWebActivityService}.
* It may be null if the connection failed.
* @throws RemoteException May be thrown by {@link TrustedWebActivityServiceWrapper}'s
- * methods.
- * If the user does not want to catch them, they will be caught
- * gracefully by {@link #execute}.
+ * methods. If the developer does not want to catch them, they will
+ * be caught gracefully by {@link #execute}.
*/
+ @SuppressLint("RethrowRemoteException") // We're accepting RemoteExceptions not throwing.
void onConnected(@Nullable TrustedWebActivityServiceWrapper service) throws RemoteException;
}
@@ -143,16 +137,15 @@
private static AtomicReference<SharedPreferences> sSharedPreferences = new AtomicReference<>();
/**
- * Gets the verified packages for the given origin. |origin| may be null, in which case this
- * method call will just trigger caching the Preferences.
- *
- * This is safe to be called on any thread, however it may hit disk.
+ * Gets the verified packages for the given origin. This is safe to be called on any thread,
+ * however it may hit disk the first time it is called.
*
* @param context A Context to be used for accessing SharedPreferences.
* @param origin The origin that was previously used with {@link #registerClient}.
* @return A set of package names. This set is safe to be modified.
*/
- public static Set<String> getVerifiedPackages(Context context, String origin) {
+ public static @NonNull Set<String> getVerifiedPackages(@NonNull Context context,
+ @NonNull String origin) {
// Loading preferences is on the critical path for this class - we need to synchronously
// inform the client whether or not an notification can be handled by a TWA.
// I considered loading the preferences into a cache on a background thread when this class
@@ -164,31 +157,35 @@
StrictMode.ThreadPolicy policy = StrictMode.allowThreadDiskReads();
try {
- if (sSharedPreferences.get() == null) {
- sSharedPreferences.compareAndSet(null,
- context.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE));
- }
+ ensurePreferencesOpened(context);
- return origin == null ? null :
- new HashSet<>(sSharedPreferences.get().getStringSet(origin,
- Collections.<String>emptySet()));
+ return new HashSet<>(
+ sSharedPreferences.get().getStringSet(origin, Collections.<String>emptySet()));
} finally {
StrictMode.setThreadPolicy(policy);
}
}
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ static void ensurePreferencesOpened(@NonNull Context context) {
+ if (sSharedPreferences.get() == null) {
+ sSharedPreferences.compareAndSet(null,
+ context.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE));
+ }
+ }
+
/**
* Creates a TrustedWebActivityServiceConnectionManager.
* @param context A Context used for accessing SharedPreferences.
*/
- public TrustedWebActivityServiceConnectionManager(Context context) {
+ public TrustedWebActivityServiceConnectionManager(@NonNull Context context) {
mContext = context.getApplicationContext();
// Asynchronously try to load (and therefore cache) the preferences.
AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
@Override
public void run() {
- getVerifiedPackages(mContext, null);
+ ensurePreferencesOpened(context);
}
});
}
@@ -222,9 +219,10 @@
* <p>
* To find a Service to connect to, this method attempts to resolve an
* {@link Intent#ACTION_VIEW} Intent with the {@code scope} as data. The first of the resolved
- * packages that registered (through {@link #registerClient}) to {@code origin} will be chosen.
- * Finally, an Intent with the action {@link TrustedWebActivityService#INTENT_ACTION} will be
- * used to find the Service.
+ * packages that is registered (through {@link #registerClient}) to {@code origin} will be
+ * chosen. Finally, an Intent with the action
+ * {@link TrustedWebActivityService#ACTION_TRUSTED_WEB_ACTIVITY_SERVICE} will be used to find
+ * the Service.
* <p>
* This method should be called on the UI thread.
*
@@ -243,7 +241,9 @@
* @return Whether a {@link TrustedWebActivityService} was found.
*/
@SuppressLint("StaticFieldLeak")
- public boolean execute(final Uri scope, String origin, final ExecutionCallback callback) {
+ @MainThread
+ public boolean execute(@NonNull final Uri scope, @NonNull String origin,
+ @NonNull final ExecutionCallback callback) {
final WrappedCallback wrappedCallback = wrapCallback(callback);
// If we have an existing connection, use it.
@@ -306,7 +306,8 @@
* to.
* @return Whether a {@link TrustedWebActivityService} was found.
*/
- public boolean serviceExistsForScope(Uri scope, String origin) {
+ @MainThread
+ public boolean serviceExistsForScope(@NonNull Uri scope, @NonNull String origin) {
// If we have an existing connection, we can deal with the scope.
if (mConnections.get(scope) != null) return true;
@@ -364,7 +365,8 @@
// Find the TrustedWebActivityService within that package.
Intent serviceResolutionIntent = new Intent();
serviceResolutionIntent.setPackage(resolvedPackage);
- serviceResolutionIntent.setAction(TrustedWebActivityService.INTENT_ACTION);
+ serviceResolutionIntent.setAction(
+ TrustedWebActivityService.ACTION_TRUSTED_WEB_ACTIVITY_SERVICE);
ResolveInfo info = appContext.getPackageManager().resolveService(serviceResolutionIntent,
PackageManager.MATCH_ALL);
@@ -389,7 +391,8 @@
* @param origin The origin for which the package is relevant.
* @param clientPackage The packages to register.
*/
- public static void registerClient(Context context, String origin, String clientPackage) {
+ public static void registerClient(@NonNull Context context, @NonNull String origin,
+ @NonNull String clientPackage) {
Set<String> possiblePackages = getVerifiedPackages(context, origin);
possiblePackages.add(clientPackage);
@@ -398,6 +401,4 @@
editor.putStringSet(origin, possiblePackages);
editor.apply();
}
-
- // TODO(peconn): Do we want to be able to unregister a client? To wipe all clients?
}
diff --git a/browser/src/main/java/androidx/browser/trusted/TrustedWebActivityServiceWrapper.java b/browser/src/main/java/androidx/browser/trusted/TrustedWebActivityServiceWrapper.java
index 57cb37c..8e50a7a 100644
--- a/browser/src/main/java/androidx/browser/trusted/TrustedWebActivityServiceWrapper.java
+++ b/browser/src/main/java/androidx/browser/trusted/TrustedWebActivityServiceWrapper.java
@@ -25,12 +25,13 @@
import android.os.RemoteException;
import android.support.customtabs.trusted.ITrustedWebActivityService;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
/**
- * TrustedWebActivityServiceWrapper is used by a Trusted Web Activity provider app to wrap calls to
+ * TrustedWebActivityServiceWrapper is used by a Trusted Web Activity provider to wrap calls to
* the {@link TrustedWebActivityService} in the client app.
* All of these calls except {@link #getComponentName()} forward over IPC
* to corresponding calls on {@link TrustedWebActivityService}, eg {@link #getSmallIconId()}
@@ -38,10 +39,7 @@
* <p>
* These IPC calls are synchronous, though the {@link TrustedWebActivityService} method may hit the
* disk. Therefore it is recommended to call them on a background thread (without StrictMode).
- *
- * @hide
*/
-@RestrictTo(RestrictTo.Scope.LIBRARY)
public class TrustedWebActivityServiceWrapper {
// Inputs.
private static final String KEY_PLATFORM_TAG =
@@ -59,11 +57,16 @@
private static final String KEY_NOTIFICATION_SUCCESS =
"android.support.customtabs.trusted.NOTIFICATION_SUCCESS";
+ private static final String REMOTE_EXCEPTION_MESSAGE = "RemoteException while trying to "
+ + "communicate with the TrustedWebActivityService, this is probably because the "
+ + "service died while attempting to respond. Check to see if the service crashed for "
+ + "some reason.";
+
private final ITrustedWebActivityService mService;
private final ComponentName mComponentName;
- TrustedWebActivityServiceWrapper(ITrustedWebActivityService service,
- ComponentName componentName) {
+ TrustedWebActivityServiceWrapper(@NonNull ITrustedWebActivityService service,
+ @NonNull ComponentName componentName) {
mService = service;
mComponentName = componentName;
}
@@ -72,11 +75,14 @@
* Checks whether notifications are enabled.
* @param channelName The name of the channel to check enabled status. Only used on Android O+.
* @return Whether notifications or the notification channel is blocked for the client app.
- * @throws RemoteException If the Service dies while responding to the request.
*/
- public boolean areNotificationsEnabled(String channelName) throws RemoteException {
- Bundle args = new NotificationsEnabledArgs(channelName).toBundle();
- return ResultArgs.fromBundle(mService.areNotificationsEnabled(args)).success;
+ public boolean areNotificationsEnabled(@NonNull String channelName) {
+ try {
+ Bundle args = new NotificationsEnabledArgs(channelName).toBundle();
+ return ResultArgs.fromBundle(mService.areNotificationsEnabled(args)).success;
+ } catch (RemoteException e) {
+ throw new RuntimeException("Failure when connecting to TrustedWebActivityService", e);
+ }
}
/**
@@ -87,27 +93,32 @@
* @param channel The name of the channel in the Trusted Web Activity client app to display the
* notification on.
* @return Whether notifications or the notification channel are blocked for the client app.
- * @throws RemoteException If the Service dies while responding to the request.
* @throws SecurityException If verification with the TrustedWebActivityService fails.
*/
- public boolean notify(String platformTag, int platformId, Notification notification,
- String channel) throws RemoteException, SecurityException {
- Bundle args = new NotifyNotificationArgs(platformTag, platformId, notification, channel)
- .toBundle();
- return ResultArgs.fromBundle(mService.notifyNotificationWithChannel(args)).success;
+ public boolean notify(@NonNull String platformTag, int platformId,
+ @NonNull Notification notification, @NonNull String channel) {
+ try {
+ Bundle args = new NotifyNotificationArgs(platformTag, platformId, notification, channel)
+ .toBundle();
+ return ResultArgs.fromBundle(mService.notifyNotificationWithChannel(args)).success;
+ } catch (RemoteException e) {
+ throw new RuntimeException(REMOTE_EXCEPTION_MESSAGE, e);
+ }
}
/**
* Requests a notification be cancelled.
* @param platformTag The tag to identify the notification.
* @param platformId The id to identify the notification.
- * @throws RemoteException If the Service dies while responding to the request.
* @throws SecurityException If verification with the TrustedWebActivityService fails.
*/
- public void cancel(String platformTag, int platformId)
- throws RemoteException, SecurityException {
- Bundle args = new CancelNotificationArgs(platformTag, platformId).toBundle();
- mService.cancelNotification(args);
+ public void cancel(@NonNull String platformTag, int platformId) {
+ try {
+ Bundle args = new CancelNotificationArgs(platformTag, platformId).toBundle();
+ mService.cancelNotification(args);
+ } catch (RemoteException e) {
+ throw new RuntimeException(REMOTE_EXCEPTION_MESSAGE, e);
+ }
}
/**
@@ -115,47 +126,57 @@
* Android M and above.
* @return An StatusBarNotification[] as a Parcelable[]. This is so this code can compile for
* Jellybean (even if it must not be called for pre-Marshmallow).
- * @throws RemoteException If the Service dies while responding to the request.
* @throws SecurityException If verification with the TrustedWebActivityService fails.
* @throws IllegalStateException If called on Android pre-M.
*
- * TODO(peconn): Figure out want to handle the return type before making this public.
+ * @hide
*/
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
@RequiresApi(Build.VERSION_CODES.M)
- public Parcelable[] getActiveNotifications()
- throws RemoteException, SecurityException, IllegalStateException {
- Bundle notifications = mService.getActiveNotifications();
- return ActiveNotificationsArgs.fromBundle(notifications).notifications;
+ public Parcelable[] getActiveNotifications() {
+ try {
+ Bundle notifications = mService.getActiveNotifications();
+ return ActiveNotificationsArgs.fromBundle(notifications).notifications;
+ } catch (RemoteException e) {
+ throw new RuntimeException(REMOTE_EXCEPTION_MESSAGE, e);
+ }
}
/**
* Requests an Android resource id to be used for the notification small icon.
* @return An Android resource id for the notification small icon. -1 if non found.
- * @throws RemoteException If the Service dies while responding to the request.
* @throws SecurityException If verification with the TrustedWebActivityService fails.
*/
- public int getSmallIconId() throws RemoteException, SecurityException {
- return mService.getSmallIconId();
+ public int getSmallIconId() {
+ try {
+ return mService.getSmallIconId();
+ } catch (RemoteException e) {
+ throw new RuntimeException(REMOTE_EXCEPTION_MESSAGE, e);
+ }
}
/**
* Requests a bitmap of a small icon to be used for the notification
* small icon. The bitmap is decoded on the side of Trusted Web Activity client using
* the resource id from {@link TrustedWebActivityService#getSmallIconId}.
- * @return {@link SmallIconData} with both an id and a bitmap
- * @throws RemoteException If the Service dies while responding to the request.
+ * @return A {@link Bitmap} to be used for the small icon.
* @throws SecurityException If verification with the TrustedWebActivityService fails.
*/
@Nullable
- public Bitmap getSmallIconBitmap() throws RemoteException, SecurityException {
- return mService.getSmallIconBitmap()
- .getParcelable(TrustedWebActivityService.KEY_SMALL_ICON_BITMAP);
+ public Bitmap getSmallIconBitmap() {
+ try {
+ return mService.getSmallIconBitmap()
+ .getParcelable(TrustedWebActivityService.KEY_SMALL_ICON_BITMAP);
+ } catch (RemoteException e) {
+ throw new RuntimeException(REMOTE_EXCEPTION_MESSAGE, e);
+ }
}
/**
* Gets the {@link ComponentName} of the connected Trusted Web Activity client app.
* @return The Trusted Web Activity client app component name.
*/
+ @NonNull
public ComponentName getComponentName() {
return mComponentName;
}
diff --git a/buildSrc-tests/build.gradle b/buildSrc-tests/build.gradle
new file mode 100644
index 0000000..f2a49f8
--- /dev/null
+++ b/buildSrc-tests/build.gradle
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+
+// This project contains tests for code contained in buildSrc
+// This project is stored outside of buildSrc/ so that waiting for these tests to complete doesn't delay the rest of the build
+
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.BuildServerConfigurationKt
+
+plugins {
+ id("AndroidXPlugin")
+ id("kotlin")
+}
+
+dependencies {
+ implementation gradleApi()
+ testImplementation JUNIT
+ implementation(project.files(new File(BuildServerConfigurationKt.getRootOutDirectory(project), "buildSrc/build/libs/buildSrc.jar")))
+}
+
+androidx {
+ toolingProject = true
+}
diff --git a/buildSrc-tests/lint-checks/build.gradle b/buildSrc-tests/lint-checks/build.gradle
new file mode 100644
index 0000000..9e9f531
--- /dev/null
+++ b/buildSrc-tests/lint-checks/build.gradle
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+
+import androidx.build.BuildServerConfigurationKt
+
+import java.io.File
+
+plugins {
+ id("AndroidXPlugin")
+ id("kotlin")
+}
+
+dependencies {
+ implementation build_libs.lint.core
+ implementation build_libs.lint.api
+ implementation build_libs.kotlin.stdlib
+ testImplementation build_libs.lint.tests
+ api("androidx.annotation:annotation:1.0.0")
+ implementation project.files(new File(BuildServerConfigurationKt.getRootOutDirectory(project), "buildSrc/lint-checks/build/libs/lint-checks.jar"))
+}
+
+androidx {
+ toolingProject = true
+}
diff --git a/buildSrc/lint-checks/src/test/java/androidx/build/lint/ObsoleteBuildCompatUsageDetectorTest.kt b/buildSrc-tests/lint-checks/src/test/java/androidx/build/lint/ObsoleteBuildCompatUsageDetectorTest.kt
similarity index 99%
rename from buildSrc/lint-checks/src/test/java/androidx/build/lint/ObsoleteBuildCompatUsageDetectorTest.kt
rename to buildSrc-tests/lint-checks/src/test/java/androidx/build/lint/ObsoleteBuildCompatUsageDetectorTest.kt
index 30bc8ee..a5f30df 100644
--- a/buildSrc/lint-checks/src/test/java/androidx/build/lint/ObsoleteBuildCompatUsageDetectorTest.kt
+++ b/buildSrc-tests/lint-checks/src/test/java/androidx/build/lint/ObsoleteBuildCompatUsageDetectorTest.kt
@@ -19,6 +19,7 @@
import com.android.tools.lint.checks.infrastructure.TestFiles.java
import com.android.tools.lint.checks.infrastructure.TestLintResult
import com.android.tools.lint.checks.infrastructure.TestLintTask.lint
+
import org.junit.Ignore
import org.junit.Test
diff --git a/buildSrc/lint-checks/src/test/java/androidx/build/lint/SampledAnnotationEnforcerTest.kt b/buildSrc-tests/lint-checks/src/test/java/androidx/build/lint/SampledAnnotationEnforcerTest.kt
similarity index 100%
rename from buildSrc/lint-checks/src/test/java/androidx/build/lint/SampledAnnotationEnforcerTest.kt
rename to buildSrc-tests/lint-checks/src/test/java/androidx/build/lint/SampledAnnotationEnforcerTest.kt
diff --git a/buildSrc/src/test/kotlin/androidx/build/VersionTest.kt b/buildSrc-tests/src/test/kotlin/androidx/build/VersionTest.kt
similarity index 100%
rename from buildSrc/src/test/kotlin/androidx/build/VersionTest.kt
rename to buildSrc-tests/src/test/kotlin/androidx/build/VersionTest.kt
diff --git a/buildSrc/src/test/kotlin/androidx/build/dependencyTracker/AffectedModuleDetectorImplTest.kt b/buildSrc-tests/src/test/kotlin/androidx/build/dependencyTracker/AffectedModuleDetectorImplTest.kt
similarity index 100%
rename from buildSrc/src/test/kotlin/androidx/build/dependencyTracker/AffectedModuleDetectorImplTest.kt
rename to buildSrc-tests/src/test/kotlin/androidx/build/dependencyTracker/AffectedModuleDetectorImplTest.kt
diff --git a/buildSrc/src/test/kotlin/androidx/build/dependencyTracker/AttachLogsTestRule.kt b/buildSrc-tests/src/test/kotlin/androidx/build/dependencyTracker/AttachLogsTestRule.kt
similarity index 100%
rename from buildSrc/src/test/kotlin/androidx/build/dependencyTracker/AttachLogsTestRule.kt
rename to buildSrc-tests/src/test/kotlin/androidx/build/dependencyTracker/AttachLogsTestRule.kt
diff --git a/buildSrc/src/test/kotlin/androidx/build/dependencyTracker/BuildPropParserTest.kt b/buildSrc-tests/src/test/kotlin/androidx/build/dependencyTracker/BuildPropParserTest.kt
similarity index 100%
rename from buildSrc/src/test/kotlin/androidx/build/dependencyTracker/BuildPropParserTest.kt
rename to buildSrc-tests/src/test/kotlin/androidx/build/dependencyTracker/BuildPropParserTest.kt
diff --git a/buildSrc/src/test/kotlin/androidx/build/dependencyTracker/GitClientImplTest.kt b/buildSrc-tests/src/test/kotlin/androidx/build/dependencyTracker/GitClientImplTest.kt
similarity index 100%
rename from buildSrc/src/test/kotlin/androidx/build/dependencyTracker/GitClientImplTest.kt
rename to buildSrc-tests/src/test/kotlin/androidx/build/dependencyTracker/GitClientImplTest.kt
diff --git a/buildSrc/src/test/kotlin/androidx/build/dependencyTracker/ProjectGraphTest.kt b/buildSrc-tests/src/test/kotlin/androidx/build/dependencyTracker/ProjectGraphTest.kt
similarity index 100%
rename from buildSrc/src/test/kotlin/androidx/build/dependencyTracker/ProjectGraphTest.kt
rename to buildSrc-tests/src/test/kotlin/androidx/build/dependencyTracker/ProjectGraphTest.kt
diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle
index 59996ce..5b08e3b 100644
--- a/buildSrc/build.gradle
+++ b/buildSrc/build.gradle
@@ -44,13 +44,6 @@
allprojects {
repos.addMavenRepositories(repositories)
- tasks.withType(Test) {
- testLogging {
- events = ["failed"]
- exceptionFormat "full"
- }
- }
-
tasks.withType(KotlinCompile).configureEach {
kotlinOptions {
freeCompilerArgs += ["-Werror"]
@@ -58,11 +51,6 @@
}
}
-sourceSets {
- main.java.srcDirs += "${supportRootFolder}/benchmark/gradle-plugin/src/main/kotlin"
- main.resources.srcDirs += "${supportRootFolder}/benchmark/gradle-plugin/src/main/resources"
-}
-
dependencies {
implementation build_libs.gradle
implementation build_libs.error_prone_gradle
@@ -70,6 +58,20 @@
implementation build_libs.kotlin.gradle_plugin
implementation gradleApi()
implementation project("jetpad-integration")
- testImplementation "junit:junit:4.12"
}
+apply plugin: "java-gradle-plugin"
+
+sourceSets {
+ main.java.srcDirs += "${supportRootFolder}/benchmark/gradle-plugin/src/main/kotlin"
+ main.resources.srcDirs += "${supportRootFolder}/benchmark/gradle-plugin/src/main/resources"
+}
+
+gradlePlugin {
+ plugins {
+ benchmark {
+ id = 'androidx.benchmark'
+ implementationClass = 'androidx.benchmark.gradle.BenchmarkPlugin'
+ }
+ }
+}
\ No newline at end of file
diff --git a/buildSrc/lint-checks/build.gradle b/buildSrc/lint-checks/build.gradle
index 6176fc3..1e34235 100644
--- a/buildSrc/lint-checks/build.gradle
+++ b/buildSrc/lint-checks/build.gradle
@@ -20,7 +20,6 @@
implementation build_libs.lint.core
implementation build_libs.lint.api
implementation build_libs.kotlin.stdlib
- testImplementation build_libs.lint.tests
api("androidx.annotation:annotation:1.0.0")
}
jar {
diff --git a/buildSrc/out.gradle b/buildSrc/out.gradle
index 7ad4cbf..4fe58ca 100644
--- a/buildSrc/out.gradle
+++ b/buildSrc/out.gradle
@@ -24,7 +24,10 @@
def outDir = System.env.OUT_DIR
if (outDir == null) {
outDir = new File("${buildscript.getSourceFile().parent}/../../../out${subdir}")
+ } else {
+ outDir = new File(outDir)
}
+ project.ext.outDir = outDir
buildDir = new File(outDir, "$project.name/build")
.getCanonicalFile()
subprojects {
diff --git a/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt b/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt
index 69970ea..d77740c 100644
--- a/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt
@@ -16,9 +16,7 @@
package androidx.build
-import androidx.benchmark.gradle.LockClocksTask
-import androidx.benchmark.gradle.UnlockClocksTask
-import androidx.build.SupportConfig.BENCHMARK_INSTRUMENTATION_RUNNER
+import androidx.benchmark.gradle.BenchmarkPlugin
import androidx.build.SupportConfig.BUILD_TOOLS_VERSION
import androidx.build.SupportConfig.COMPILE_SDK_VERSION
import androidx.build.SupportConfig.DEFAULT_MIN_SDK_VERSION
@@ -277,6 +275,7 @@
"verifyDependencyVersions" == task.name ||
"reportLibraryMetrics" == task.name ||
CREATE_STUB_API_JAR_TASK == task.name ||
+ BUILD_ON_SERVER_TASK == task.name ||
("lintDebug" == task.name &&
!project.rootProject.hasProperty(USE_MAX_DEP_VERSIONS))) {
buildOnServerTask.dependsOn(task)
@@ -315,14 +314,6 @@
CheckSameVersionLibraryGroupsTask::class.java)
buildOnServerTask.dependsOn(checkSameVersionLibraryGroupsTask)
- val adbPath = "${getSdkPath(SupportConfig.getSupportRoot(project)).path}/platform-tools/adb"
- tasks.register("lockClocks", LockClocksTask::class.java).configure {
- it.adbPath.set(adbPath)
- }
- tasks.register("unlockClocks", UnlockClocksTask::class.java).configure {
- it.adbPath.set(adbPath)
- }
-
AffectedModuleDetector.configure(gradle, this)
// If useMaxDepVersions is set, iterate through all the project and substitute any androidx
@@ -368,8 +359,13 @@
compileSdkVersion(COMPILE_SDK_VERSION)
buildToolsVersion = BUILD_TOOLS_VERSION
defaultConfig.targetSdkVersion(TARGET_SDK_VERSION)
- defaultConfig.testInstrumentationRunner =
- if (project.isBenchmark()) BENCHMARK_INSTRUMENTATION_RUNNER else INSTRUMENTATION_RUNNER
+
+ defaultConfig.testInstrumentationRunner = INSTRUMENTATION_RUNNER
+
+ // Enable code coverage for debug builds only if we are not running inside the IDE, since
+ // enabling coverage reports breaks the method parameter resolution in the IDE debugger.
+ buildTypes.getByName("debug").isTestCoverageEnabled =
+ !project.hasProperty("android.injected.invoked.from.ide")
// Pass the --no-window-animation flag with a hack (b/138120842)
// NOTE - We're exploiting the fact that anything after a space in the value of a
@@ -425,12 +421,6 @@
project.configureErrorProneForAndroid(variants)
- // Enable code coverage for debug builds only if we are not running inside the IDE, since
- // enabling coverage reports breaks the method parameter resolution in the IDE debugger.
- buildTypes.getByName("debug").isTestCoverageEnabled =
- !project.hasProperty("android.injected.invoked.from.ide") &&
- !project.isBenchmark()
-
// Set the officially published version to be the debug version with minimum dependency
// versions.
defaultPublishConfig(Release.DEFAULT_PUBLISH_CONFIG)
@@ -485,10 +475,9 @@
// Exclude media-compat-test-* and media2-test-* modules from
// existing support library presubmit tests.
fileName.replace("-debug-androidTest", "")
- } else if (fileName.contains("-benchmark-debug-androidTest")) {
- // Exclude '-benchmark' modules from correctness tests, and
- // remove '-debug' from the APK name, since it's incorrect
- fileName.replace("-debug-androidTest", "-androidBenchmark")
+ } else if (project.plugins.hasPlugin(BenchmarkPlugin::class.java)) {
+ // Exclude '-benchmark' modules from correctness tests
+ fileName.replace("-androidTest", "-androidBenchmark")
} else {
// multiple modules may have the same name so prefix the name with
// the module's path to ensure it is unique.
@@ -638,12 +627,6 @@
}
}
-fun Project.isBenchmark(): Boolean {
- // benchmark convention is to end name with "-benchmark"
- // Note: also match benchmark/src/androidTest, so it gets the BENCHMARK_INSTRUMENTATION_RUNNER
- return name.endsWith("-benchmark") || name == "benchmark"
-}
-
fun Project.hideJavadocTask() {
// Most tasks named "javadoc" are unused
// So, few tasks named "javadoc" are interesting to developers
diff --git a/buildSrc/src/main/kotlin/androidx/build/BuildServerConfiguration.kt b/buildSrc/src/main/kotlin/androidx/build/BuildServerConfiguration.kt
index 4d065ed..aa1f726 100644
--- a/buildSrc/src/main/kotlin/androidx/build/BuildServerConfiguration.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/BuildServerConfiguration.kt
@@ -46,6 +46,13 @@
}
/**
+ * Returns the out directory (an ancestor of all files generated by the build)
+ */
+fun Project.getRootOutDirectory(): File {
+ return project.rootProject.extensions.extraProperties.get("outDir") as File
+}
+
+/**
* Directory to put build info files for release service dependency files.
*/
fun Project.getBuildInfoDirectory(): File =
diff --git a/buildSrc/src/main/kotlin/androidx/build/CreateLibraryBuildInfoFileTask.kt b/buildSrc/src/main/kotlin/androidx/build/CreateLibraryBuildInfoFileTask.kt
index f4b47a4..07f598a 100644
--- a/buildSrc/src/main/kotlin/androidx/build/CreateLibraryBuildInfoFileTask.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/CreateLibraryBuildInfoFileTask.kt
@@ -16,6 +16,7 @@
package androidx.build
+import androidx.build.gmaven.GMavenVersionChecker
import androidx.build.jetpad.LibraryBuildInfoFile
import org.gradle.api.DefaultTask
import org.gradle.api.artifacts.ProjectDependency
@@ -91,6 +92,7 @@
val checks = ArrayList<LibraryBuildInfoFile.Check>()
libraryBuildInfoFile.checks = checks
val publishedProjects = project.getProjectsMap()
+ val versionChecker = project.property("versionChecker") as GMavenVersionChecker
project.configurations.filter {
/* Ignore test configuration dependencies */
!it.name.contains("test", ignoreCase = true)
@@ -110,6 +112,15 @@
androidXPublishedDependency.groupId = dep.group.toString()
androidXPublishedDependency.version = dep.version.toString()
androidXPublishedDependency.isTipOfTree = dep is ProjectDependency
+ // Check GMaven to confirm that the pinned dependency is not an unreleased
+ // version
+ if (!androidXPublishedDependency.isTipOfTree &&
+ !versionChecker.isReleased(
+ androidXPublishedDependency.groupId,
+ androidXPublishedDependency.artifactId,
+ androidXPublishedDependency.version)) {
+ androidXPublishedDependency.isTipOfTree = true
+ }
addDependencyToListIfNotAlreadyAdded(
libraryDependencies,
androidXPublishedDependency
diff --git a/buildSrc/src/main/kotlin/androidx/build/Jetify.kt b/buildSrc/src/main/kotlin/androidx/build/Jetify.kt
index 25b8e8b..ec84188 100644
--- a/buildSrc/src/main/kotlin/androidx/build/Jetify.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/Jetify.kt
@@ -30,6 +30,7 @@
"m2repository/androidx/arch/**",
"m2repository/androidx/arch/core/**",
"m2repository/androidx/asynclayoutinflater/**",
+ "m2repository/androidx/benchmark/**",
"m2repository/androidx/biometric/**",
"m2repository/androidx/browser/**",
"m2repository/androidx/camera/**",
diff --git a/buildSrc/src/main/kotlin/androidx/build/LibraryGroups.kt b/buildSrc/src/main/kotlin/androidx/build/LibraryGroups.kt
index 3d62b43..e0ecf75 100644
--- a/buildSrc/src/main/kotlin/androidx/build/LibraryGroups.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/LibraryGroups.kt
@@ -23,7 +23,7 @@
val ACTIVITY = LibraryGroup("androidx.activity")
val ADS = LibraryGroup("androidx.ads", false)
val ANIMATION = LibraryGroup("androidx.animation", false)
- val ANNOTATION = LibraryGroup("androidx.annotation")
+ val ANNOTATION = LibraryGroup("androidx.annotation", false)
val APPCOMPAT = LibraryGroup("androidx.appcompat", false)
val ARCH_CORE = LibraryGroup("androidx.arch.core")
val ASYNCLAYOUTINFLATER = LibraryGroup("androidx.asynclayoutinflater")
diff --git a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
index 0b9350a..9dacda3 100644
--- a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
@@ -25,6 +25,7 @@
val ANIMATION = Version("1.0.0-alpha01")
val ANIMATION_TESTING = Version("1.1.0-alpha01")
val ANNOTATION = Version("1.2.0-alpha01")
+ val ANNOTATION_EXPERIMENTAL = Version("1.0.0-alpha01")
val APPCOMPAT = Version("1.2.0-alpha01")
val ARCH_CORE = Version("2.2.0-alpha01")
val ARCH_CORE_TESTING = ARCH_CORE
@@ -33,7 +34,7 @@
val AUTOFILL = Version("1.0.0-alpha02")
val BENCHMARK = Version("1.0.0-alpha04")
val BIOMETRIC = Version("1.0.0-beta01")
- val BROWSER = Version("1.2.0-alpha06")
+ val BROWSER = Version("1.2.0-alpha07")
val CAMERA = Version("1.0.0-alpha04")
val CAMERA_EXTENSIONS = Version("1.0.0-alpha01")
val CAMERA_VIEW = Version("1.0.0-alpha01")
@@ -57,7 +58,7 @@
val ENTERPRISE = Version("1.0.0-alpha03")
val EXIFINTERFACE = Version("1.1.0-beta02")
val FRAGMENT = Version("1.2.0-alpha02")
- val FUTURES = Version("1.0.0-beta02")
+ val FUTURES = Version("1.0.0-rc01")
val GRIDLAYOUT = Version("1.1.0-alpha01")
val HEIFWRITER = Version("1.1.0-alpha01")
val INSPECTION = Version("1.0.0-alpha01")
diff --git a/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt b/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
index 47f1722..810d38b 100644
--- a/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
@@ -34,8 +34,9 @@
prebuilts(LibraryGroups.ARCH_CORE, "2.1.0-rc01")
prebuilts(LibraryGroups.ASYNCLAYOUTINFLATER, "1.0.0")
prebuilts(LibraryGroups.AUTOFILL, "1.0.0-alpha01")
+ prebuilts(LibraryGroups.BENCHMARK, "benchmark", "1.0.0-alpha03")
+ ignore(LibraryGroups.BENCHMARK.group, "benchmark-common")
ignore(LibraryGroups.BENCHMARK.group, "benchmark-gradle-plugin")
- prebuilts(LibraryGroups.BENCHMARK, "1.0.0-alpha03")
prebuilts(LibraryGroups.BIOMETRIC, "biometric", "1.0.0-alpha04")
prebuilts(LibraryGroups.BROWSER, "1.0.0")
ignore(LibraryGroups.CAMERA.group, "camera-view")
@@ -50,7 +51,7 @@
.addStubs("car/stubs/android.car.jar")
prebuilts(LibraryGroups.CARDVIEW, "1.0.0")
prebuilts(LibraryGroups.COLLECTION, "1.1.0")
- prebuilts(LibraryGroups.CONCURRENT, "1.0.0-beta01")
+ prebuilts(LibraryGroups.CONCURRENT, "1.0.0-rc01")
prebuilts(LibraryGroups.CONTENTPAGER, "1.0.0")
prebuilts(LibraryGroups.COORDINATORLAYOUT, "1.1.0-beta01")
prebuilts(LibraryGroups.CORE, "core", "1.2.0-alpha02")
@@ -118,7 +119,7 @@
prebuilts(LibraryGroups.TVPROVIDER, "1.0.0")
prebuilts(LibraryGroups.VECTORDRAWABLE, "1.1.0-rc01")
prebuilts(LibraryGroups.VECTORDRAWABLE, "vectordrawable-animated", "1.1.0-rc01")
- prebuilts(LibraryGroups.VERSIONEDPARCELABLE, "1.1.0-rc01")
+ prebuilts(LibraryGroups.VERSIONEDPARCELABLE, "1.1.0")
prebuilts(LibraryGroups.VIEWPAGER, "1.0.0")
prebuilts(LibraryGroups.VIEWPAGER2, "1.0.0-beta02")
prebuilts(LibraryGroups.WEAR, "1.0.0")
@@ -126,7 +127,7 @@
prebuilts(LibraryGroups.WEBKIT, "1.1.0-alpha01")
ignore(LibraryGroups.WORK.group, "work-gcm")
ignore(LibraryGroups.WORK.group, "work-foreground")
- prebuilts(LibraryGroups.WORK, "2.2.0-beta02")
+ prebuilts(LibraryGroups.WORK, "2.2.0-rc01")
default(Ignore)
}
diff --git a/buildSrc/src/main/kotlin/androidx/build/SupportConfig.kt b/buildSrc/src/main/kotlin/androidx/build/SupportConfig.kt
index 34bd1bd..4994312 100644
--- a/buildSrc/src/main/kotlin/androidx/build/SupportConfig.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/SupportConfig.kt
@@ -23,7 +23,6 @@
object SupportConfig {
const val DEFAULT_MIN_SDK_VERSION = 14
const val INSTRUMENTATION_RUNNER = "androidx.test.runner.AndroidJUnitRunner"
- const val BENCHMARK_INSTRUMENTATION_RUNNER = "androidx.benchmark.AndroidBenchmarkRunner"
const val BUILD_TOOLS_VERSION = "28.0.3"
/**
diff --git a/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt b/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
index 2a3e0c8..8993082 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
@@ -16,9 +16,11 @@
package androidx.build.dependencies
+const val ANDROID_GRADLE_PLUGIN = "com.android.tools.build:gradle:3.4.2"
const val ANDROIDX_TEST_CORE = "androidx.test:core:1.1.0"
const val ANDROIDX_TEST_EXT_JUNIT = "androidx.test.ext:junit:1.1.0"
const val ANDROIDX_TEST_EXT_KTX = "androidx.test.ext:junit-ktx:1.1.0"
+const val ANDROIDX_TEST_MONITOR = "androidx.test:monitor:1.1.1"
const val ANDROIDX_TEST_RULES = "androidx.test:rules:1.1.0"
const val ANDROIDX_TEST_RUNNER = "androidx.test:runner:1.1.1"
const val ANDROIDX_TEST_UIAUTOMATOR = "androidx.test.uiautomator:uiautomator:2.2.0"
@@ -48,6 +50,7 @@
const val KOTLINPOET = "com.squareup:kotlinpoet:1.1.0"
private const val KOTLIN_VERSION = "1.3.41"
+const val KOTLIN_GRADLE_PLUGIN = "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.20"
const val KOTLIN_STDLIB = "org.jetbrains.kotlin:kotlin-stdlib:$KOTLIN_VERSION"
const val KOTLIN_TEST_COMMON = "org.jetbrains.kotlin:kotlin-test:$KOTLIN_VERSION"
const val COMPOSE_VERSION = "1.3.30-compose-20190520"
@@ -68,6 +71,8 @@
"org.jetbrains.kotlinx:kotlinx-coroutines-guava:$KOTLIN_COROUTINES_VERSION"
const val KOTLIN_COROUTINES_TEST =
"org.jetbrains.kotlinx:kotlinx-coroutines-test:$KOTLIN_COROUTINES_VERSION"
+const val KOTLIN_COROUTINES_PREVIEW =
+ "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0-RC"
const val LEAKCANARY_INSTRUMENTATION =
"com.squareup.leakcanary:leakcanary-android-instrumentation:1.6.2"
@@ -114,10 +119,6 @@
const val ARCH_CORE_RUNTIME = "androidx.arch.core:core-runtime:2.0.1"
const val ARCH_CORE_TESTING = "androidx.arch.core:core-testing:2.0.1"
-const val SAFE_ARGS_ANDROID_GRADLE_PLUGIN = "com.android.tools.build:gradle:3.4.0"
-const val SAFE_ARGS_KOTLIN_GRADLE_PLUGIN = "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.20"
-const val SAFE_ARGS_NAVIGATION_COMMON = "androidx.navigation:navigation-common:2.0.0-rc02"
-
const val ARCH_PAGING_COMMON = "androidx.paging:paging-common:2.0.0"
const val ARCH_PAGING_RUNTIME = "androidx.paging:paging-runtime:2.0.0"
const val ARCH_ROOM_RUNTIME = "androidx.room:room-runtime:2.0.0"
@@ -125,6 +126,8 @@
const val ARCH_ROOM_RXJAVA = "androidx.room:room-rxjava2:2.0.0"
const val ARCH_ROOM_TESTING = "androidx.room:room-testing:2.0.0"
+const val NAVIGATION_COMMON = "androidx.navigation:navigation-common:2.0.0-rc02"
+
const val WORK_ARCH_CORE_RUNTIME = "androidx.arch.core:core-runtime:2.0.0"
const val WORK_ARCH_CORE_TESTING = "androidx.arch.core:core-testing:2.0.0"
const val WORK_ARCH_ROOM_RUNTIME = "androidx.room:room-runtime:2.1.0"
diff --git a/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt b/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt
index c20ff94..d3df2ff 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt
@@ -48,7 +48,7 @@
* ALL_AFFECTED_PROJECTS -- The union of CHANGED_PROJECTS and DEPENDENT_PROJECTS,
* which encompasses all projects that could possibly break due to the changes.
*/
-internal enum class ProjectSubset { DEPENDENT_PROJECTS, CHANGED_PROJECTS, ALL_AFFECTED_PROJECTS }
+enum class ProjectSubset { DEPENDENT_PROJECTS, CHANGED_PROJECTS, ALL_AFFECTED_PROJECTS }
/**
* A utility class that can discover which files are changed based on git history.
@@ -183,7 +183,7 @@
*
* When a file in a module is changed, all modules that depend on it are considered as changed.
*/
-internal class AffectedModuleDetectorImpl constructor(
+class AffectedModuleDetectorImpl constructor(
private val rootProject: Project,
private val logger: Logger?,
// used for debugging purposes when we want to ignore non module files
diff --git a/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/BuildPropParser.kt b/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/BuildPropParser.kt
index c877e7a..8f48cd6 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/BuildPropParser.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/BuildPropParser.kt
@@ -24,7 +24,7 @@
*
* Currently, we don't use it since build system does not give us the right shas.
*/
-internal object BuildPropParser {
+object BuildPropParser {
/**
* Returns the sha which is the reference sha that we should use to find changed files.
*
@@ -90,4 +90,4 @@
val repoSha: String,
val buildSha: String
)
-}
\ No newline at end of file
+}
diff --git a/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/GitClient.kt b/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/GitClient.kt
index 7e57766..486df04 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/GitClient.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/GitClient.kt
@@ -20,7 +20,7 @@
import java.io.File
import java.util.concurrent.TimeUnit
-internal interface GitClient {
+interface GitClient {
fun findChangedFilesSince(
sha: String,
top: String = "HEAD",
@@ -42,7 +42,7 @@
* A simple git client that uses system process commands to communicate with the git setup in the
* given working directory.
*/
-internal class GitClientImpl(
+class GitClientImpl(
/**
* The root location for git
*/
@@ -109,7 +109,7 @@
}
companion object {
- internal const val PREV_MERGE_CMD = "git log -1 --merges --oneline"
- internal const val CHANGED_FILES_CMD_PREFIX = "git diff --name-only"
+ const val PREV_MERGE_CMD = "git log -1 --merges --oneline"
+ const val CHANGED_FILES_CMD_PREFIX = "git diff --name-only"
}
}
diff --git a/buildSrc/src/main/kotlin/androidx/build/dokka/Dokka.kt b/buildSrc/src/main/kotlin/androidx/build/dokka/Dokka.kt
index f43a945..fd330de 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dokka/Dokka.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dokka/Dokka.kt
@@ -25,6 +25,7 @@
import com.android.build.gradle.LibraryExtension
import org.gradle.api.Project
import org.gradle.api.plugins.JavaBasePlugin
+import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.bundling.Zip
import org.gradle.kotlin.dsl.apply
import org.jetbrains.dokka.gradle.DokkaAndroidPlugin
@@ -33,9 +34,11 @@
import java.io.File
object Dokka {
- fun generatorTaskNameForType(docsType: String): String {
- return "dokka${docsType}Docs"
+ fun generatorTaskNameForType(docsType: String, language: String = ""): String {
+ val formattedLangauage = language.toLowerCase().capitalize()
+ return "dokka${formattedLangauage}${docsType}Docs"
}
+
fun archiveTaskNameForType(docsType: String): String {
return "dist${docsType}DokkaDocs"
}
@@ -44,7 +47,6 @@
project: Project,
hiddenPackages: List<String>
) {
- val taskName = generatorTaskNameForType(docsType)
val archiveTaskName = archiveTaskNameForType(docsType)
project.apply<DokkaAndroidPlugin>()
// We don't use the `dokka` task, but it normally appears in `./gradlew tasks`
@@ -53,38 +55,72 @@
if (project.name != "support" && project.name != "docs-runner") {
throw Exception("Illegal project passed to createDocsTask: " + project.name)
}
- val docsTask = project.tasks.create(taskName, DokkaAndroidTask::class.java) { docsTask ->
- docsTask.moduleName = project.name
- docsTask.outputDirectory = File(project.buildDir, taskName).absolutePath
- docsTask.description = "Generates $docsType Kotlin documentation in the style of " +
- "d.android.com. Places docs in ${docsTask.outputDirectory}"
- docsTask.outputFormat = "dac"
- docsTask.outlineRoot = "androidx/"
- docsTask.dacRoot = "/reference/kotlin"
- docsTask.moduleName = ""
+
+ val kotlinDocsTask = createDokkaTask(project,
+ docsType,
+ hiddenPackages,
+ "Kotlin",
+ "dac",
+ "/reference/kotlin")
+ val javaDocsTask = createDokkaTask(project,
+ docsType,
+ hiddenPackages,
+ "Java",
+ "dac-as-java",
+ "/reference/")
+
+ project.tasks.register(archiveTaskName, Zip::class.java) { zipTask ->
+
+ zipTask.dependsOn(javaDocsTask)
+ zipTask.from(javaDocsTask.map { it.outputDirectory }) { copySpec ->
+ copySpec.into("reference/java")
+ }
+
+ zipTask.dependsOn(kotlinDocsTask)
+ zipTask.from(kotlinDocsTask.map { it.outputDirectory }) { copySpec ->
+ copySpec.into("reference/kotlin")
+ }
+
+ val buildId = getBuildId()
+ val archiveBaseName = generatorTaskNameForType(docsType)
+ zipTask.archiveBaseName.set(archiveBaseName)
+ zipTask.archiveVersion.set(buildId)
+ zipTask.destinationDirectory.set(project.getDistributionDirectory())
+ val filePath = "${project.getDistributionDirectory().canonicalPath}/"
+ val fileName = "$archiveBaseName-$buildId.zip"
+ zipTask.description = "Zips $docsType documentation (generated via " +
+ "Dokka in the style of d.android.com) into ${filePath + fileName}"
+ zipTask.group = JavaBasePlugin.DOCUMENTATION_GROUP
+ }
+ }
+
+ private fun createDokkaTask(
+ project: Project,
+ docsType: String,
+ hiddenPackages: List<String>,
+ language: String,
+ outputFormat: String,
+ dacRoot: String
+ ): TaskProvider<DokkaAndroidTask> {
+
+ val docTaskName = generatorTaskNameForType(docsType, language)
+
+ return project.tasks.register(docTaskName, DokkaAndroidTask::class.java) { task ->
+ task.moduleName = project.name
+ task.outputDirectory = File(project.buildDir, docTaskName).absolutePath
+ task.description = "Generates $docsType $language documentation in the style of " +
+ "d.android.com. Places docs in ${task.outputDirectory}"
+ task.outputFormat = outputFormat
+ task.outlineRoot = "androidx/"
+ task.dacRoot = dacRoot
+ task.moduleName = ""
for (hiddenPackage in hiddenPackages) {
val opts = PackageOptions()
opts.prefix = hiddenPackage
opts.suppress = true
- docsTask.perPackageOptions.add(opts)
+ task.perPackageOptions.add(opts)
}
}
-
- project.tasks.create(archiveTaskName, Zip::class.java) { zipTask ->
- zipTask.dependsOn(docsTask)
- zipTask.from(docsTask.outputDirectory) { copySpec ->
- copySpec.into("reference/kotlin")
- }
- val buildId = getBuildId()
- zipTask.archiveBaseName.set(taskName)
- zipTask.archiveVersion.set(buildId)
- zipTask.destinationDirectory.set(project.getDistributionDirectory())
- val filePath = "${project.getDistributionDirectory().canonicalPath}/"
- val fileName = "$taskName-$buildId.zip"
- zipTask.description = "Zips $docsType Kotlin documentation (generated via " +
- "Dokka in the style of d.android.com) into ${filePath + fileName}"
- zipTask.group = JavaBasePlugin.DOCUMENTATION_GROUP
- }
}
fun Project.configureAndroidProjectForDokka(
diff --git a/buildSrc/src/main/kotlin/androidx/build/dokka/DokkaPublicDocs.kt b/buildSrc/src/main/kotlin/androidx/build/dokka/DokkaPublicDocs.kt
index c313a71..c793a8c 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dokka/DokkaPublicDocs.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dokka/DokkaPublicDocs.kt
@@ -20,7 +20,6 @@
import java.io.File
import androidx.build.androidJarFile
-import androidx.build.java.JavaCompileInputs
import androidx.build.AndroidXExtension
import androidx.build.RELEASE_RULE
import androidx.build.Strategy.Ignore
@@ -30,13 +29,15 @@
import org.gradle.api.Project
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.Copy
+import org.gradle.api.tasks.TaskCollection
import org.gradle.api.tasks.TaskContainer
+import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.util.PatternFilterable
import org.jetbrains.dokka.gradle.DokkaTask
object DokkaPublicDocs {
- val ARCHIVE_TASK_NAME: String = Dokka.archiveTaskNameForType("Public")
- private val RUNNER_TASK_NAME = Dokka.generatorTaskNameForType("Public")
+ private const val DOCS_TYPE = "Public"
+ val ARCHIVE_TASK_NAME: String = Dokka.archiveTaskNameForType(DOCS_TYPE)
private const val UNZIP_DEPS_TASK_NAME = "unzipDokkaPublicDocsDeps"
val hiddenPackages = listOf(
@@ -56,6 +57,9 @@
"androidx.work.impl.utils.futures",
"androidx.work.impl.utils.taskexecutor")
+ private var docsTasks: TaskCollection<DokkaTask>? = null
+ private var unzipTask: LocateJarsTask? = null
+
fun tryGetRunnerProject(project: Project): Project? {
return project.rootProject.findProject(":docs-runner")
}
@@ -64,34 +68,56 @@
return tryGetRunnerProject(project)!!
}
- fun getDocsTask(project: Project): DokkaTask {
+ fun getDocsTasks(project: Project): TaskCollection<DokkaTask>? {
+ docsTasks?.let {
+ return it
+ }
val runnerProject = getRunnerProject(project)
- return runnerProject.tasks.getOrCreateDocsTask(runnerProject)
+ docsTasks = runnerProject.tasks.getOrCreateDocsTask(runnerProject)
+ return docsTasks
}
fun getUnzipDepsTask(project: Project): LocateJarsTask {
+ unzipTask?.let {
+ return it
+ }
val runnerProject = getRunnerProject(project)
- return runnerProject.tasks.getByName(DokkaPublicDocs.UNZIP_DEPS_TASK_NAME) as LocateJarsTask
+ unzipTask = runnerProject.tasks.getByName(UNZIP_DEPS_TASK_NAME) as LocateJarsTask
+ return unzipTask as LocateJarsTask
}
- @Synchronized fun TaskContainer.getOrCreateDocsTask(runnerProject: Project): DokkaTask {
+ @Synchronized fun TaskContainer.getOrCreateDocsTask(runnerProject: Project):
+ TaskCollection<DokkaTask> {
val tasks = this
- if (tasks.findByName(RUNNER_TASK_NAME) == null) {
- Dokka.createDocsTask("Public",
+ var dokkaTasks = runnerProject.tasks.withType(DokkaTask::class.java)
+ .matching { it.name.contains(DOCS_TYPE) }
+ if (dokkaTasks.isEmpty()) {
+ Dokka.createDocsTask(
+ DOCS_TYPE,
runnerProject,
hiddenPackages)
- val docsTask = runnerProject.tasks.getByName(RUNNER_TASK_NAME) as DokkaTask
+
+ dokkaTasks = runnerProject.tasks.withType(DokkaTask::class.java)
+ .matching { it.name.contains(DOCS_TYPE) }
+
tasks.create(UNZIP_DEPS_TASK_NAME, LocateJarsTask::class.java) { unzipTask ->
unzipTask.doLast {
for (jar in unzipTask.outputJars) {
- docsTask.classpath = docsTask.classpath.plus(runnerProject.file(jar))
+ dokkaTasks.forEach {
+ it.classpath += runnerProject.file(jar)
+ }
}
- docsTask.classpath += androidJarFile(runnerProject)
+ dokkaTasks.forEach {
+ it.classpath += androidJarFile(runnerProject)
+ }
}
- docsTask.dependsOn(unzipTask)
+
+ dokkaTasks.forEach {
+ it.dependsOn(unzipTask)
+ }
}
}
- return runnerProject.tasks.getByName(DokkaPublicDocs.RUNNER_TASK_NAME) as DokkaTask
+ return dokkaTasks
}
// specifies that <project> exists and might need us to generate documentation for it
@@ -119,14 +145,21 @@
}
// specifies that <dependency> describes an artifact containing sources that we want to include in our generated documentation
- private fun registerPrebuilt(dependency: String, runnerProject: Project): Copy {
- val docsTask = getDocsTask(runnerProject)
+ private fun registerPrebuilt(dependency: String, runnerProject: Project): TaskProvider<Copy> {
+ val dokkaTasks = getDocsTasks(runnerProject)
// unzip the sources jar
val unzipTask = getPrebuiltSources(runnerProject, "$dependency:sources")
- val sourceDir = unzipTask.destinationDir
- docsTask.dependsOn(unzipTask)
- docsTask.sourceDirs += sourceDir
+ val sourceDir = unzipTask.map { it.destinationDir }
+
+ // Avoid depending on or modifying a task that has already been executed.
+ // Because registerProject is called in an afterEvaluate, we can end up in a case where the dokka tasks are
+ // cached and have been executed in a prior run
+ dokkaTasks?.filter { it.state.isConfigurable }?.forEach {
+ val sourceDirVal = sourceDir.get()
+ it.dependsOn(unzipTask)
+ it.sourceDirs += sourceDirVal
+ }
// also make a note to unzip any dependencies too
getUnzipDepsTask(runnerProject).inputDependencies.add(dependency)
@@ -138,7 +171,7 @@
private fun getPrebuiltSources(
runnerProject: Project,
mavenId: String
- ): Copy {
+ ): TaskProvider<Copy> {
val configuration = runnerProject.configurations.detachedConfiguration(
runnerProject.dependencies.create(mavenId)
)
@@ -156,7 +189,7 @@
val sanitizedMavenId = mavenId.replace(":", "-")
val buildDir = runnerProject.buildDir
val destDir = runnerProject.file("$buildDir/sources-unzipped/$sanitizedMavenId")
- return runnerProject.tasks.create("unzip$sanitizedMavenId", Copy::class.java) {
+ return runnerProject.tasks.register("unzip$sanitizedMavenId", Copy::class.java) {
it.from(runnerProject.zipTree(configuration.singleFile)
.matching {
it.exclude("**/*.MF")
@@ -172,14 +205,6 @@
}
}
}
-
- private fun registerInputs(inputs: JavaCompileInputs, project: Project) {
- val docsTask = getDocsTask(project)
- docsTask.sourceDirs += inputs.sourcePaths
- docsTask.classpath = docsTask.classpath.plus(inputs.dependencyClasspath)
- .plus(inputs.bootClasspath)
- docsTask.dependsOn(inputs.dependencyClasspath)
- }
}
open class LocateJarsTask : DefaultTask() {
diff --git a/buildSrc/src/main/kotlin/androidx/build/dokka/DokkaSourceDocs.kt b/buildSrc/src/main/kotlin/androidx/build/dokka/DokkaSourceDocs.kt
index 09d95e6..80826b3 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dokka/DokkaSourceDocs.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dokka/DokkaSourceDocs.kt
@@ -18,23 +18,25 @@
// TODO: after DiffAndDocs and Doclava are fully obsoleted and removed, rename this from DokkaSourceDocs to just SourceDocs
package androidx.build.dokka
-import androidx.build.java.JavaCompileInputs
import androidx.build.AndroidXExtension
import androidx.build.defaultPublishVariant
+import androidx.build.java.JavaCompileInputs
import com.android.build.gradle.LibraryExtension
import org.gradle.api.Project
import org.gradle.api.plugins.JavaPluginConvention
+import org.gradle.api.tasks.TaskCollection
import org.gradle.api.tasks.TaskContainer
import org.gradle.kotlin.dsl.getPlugin
import org.jetbrains.dokka.gradle.DokkaTask
object DokkaSourceDocs {
- private val RUNNER_TASK_NAME = Dokka.generatorTaskNameForType("TipOfTree")
- public val ARCHIVE_TASK_NAME: String = Dokka.archiveTaskNameForType("TipOfTree")
+ private const val DOCS_TYPE = "TipOfTree"
+ public val ARCHIVE_TASK_NAME: String = Dokka.archiveTaskNameForType(DOCS_TYPE)
// TODO(b/72330103) make "generateDocs" be the only archive task once Doclava is fully removed
private val ALTERNATE_ARCHIVE_TASK_NAME: String = "generateDocs"
private val hiddenPackages = DokkaPublicDocs.hiddenPackages
+ private var docsTasks: TaskCollection<DokkaTask>? = null
fun tryGetRunnerProject(project: Project): Project? {
return project.rootProject.findProject(":docs-runner")
@@ -44,22 +46,33 @@
return tryGetRunnerProject(project)!!
}
- fun getDocsTask(project: Project): DokkaTask {
- var runnerProject = getRunnerProject(project)
- return runnerProject.tasks.getOrCreateDocsTask(runnerProject)
+ fun getDocsTasks(project: Project): TaskCollection<DokkaTask>? {
+ docsTasks?.let {
+ return it
+ }
+ val runnerProject = getRunnerProject(project)
+ docsTasks = runnerProject.tasks.getOrCreateDocsTask(runnerProject)
+ return docsTasks
}
- @Synchronized fun TaskContainer.getOrCreateDocsTask(runnerProject: Project): DokkaTask {
+ @Synchronized fun TaskContainer.getOrCreateDocsTask(runnerProject: Project):
+ TaskCollection<DokkaTask> {
val tasks = this
- if (tasks.findByName(DokkaSourceDocs.RUNNER_TASK_NAME) == null) {
- Dokka.createDocsTask("TipOfTree", runnerProject, hiddenPackages)
- if (tasks.findByName(DokkaSourceDocs.ALTERNATE_ARCHIVE_TASK_NAME) == null) {
- tasks.create(ALTERNATE_ARCHIVE_TASK_NAME)
+ var dokkaTasks = runnerProject.tasks.withType(DokkaTask::class.java)
+ .matching { it.name.contains(DOCS_TYPE) }
+
+ if (dokkaTasks.isEmpty()) {
+ Dokka.createDocsTask(DOCS_TYPE, runnerProject, hiddenPackages)
+ dokkaTasks = runnerProject.tasks.withType(DokkaTask::class.java)
+ .matching { it.name.contains(DOCS_TYPE) }
+
+ if (tasks.findByName(ALTERNATE_ARCHIVE_TASK_NAME) == null) {
+ tasks.register(ALTERNATE_ARCHIVE_TASK_NAME) {
+ it.dependsOn(tasks.named(ARCHIVE_TASK_NAME))
+ }
}
- tasks.getByName(ALTERNATE_ARCHIVE_TASK_NAME)
- .dependsOn(tasks.getByName(ARCHIVE_TASK_NAME))
}
- return tasks.getByName(DokkaSourceDocs.RUNNER_TASK_NAME) as DokkaTask
+ return dokkaTasks
}
fun registerAndroidProject(
@@ -95,17 +108,24 @@
}
val javaPluginConvention = project.convention.getPlugin<JavaPluginConvention>()
val mainSourceSet = javaPluginConvention.sourceSets.getByName("main")
- project.afterEvaluate({
+ project.afterEvaluate {
val inputs = JavaCompileInputs.fromSourceSet(mainSourceSet, project)
registerInputs(inputs, project)
- })
+ }
}
fun registerInputs(inputs: JavaCompileInputs, project: Project) {
- val docsTask = getDocsTask(project)
- docsTask.sourceDirs += inputs.sourcePaths
- docsTask.classpath = docsTask.classpath.plus(inputs.dependencyClasspath)
- .plus(inputs.bootClasspath)
- docsTask.dependsOn(inputs.dependencyClasspath)
+ val dokkaTasks = getDocsTasks(project)
+
+ // Avoid depending on or modifying a task that has already been executed.
+ // Because registerInputs is called in an afterEvaluate, we can end up in a case where the dokka tasks are
+ // cached and have been executed in a prior run
+ dokkaTasks?.filter { it.state.isConfigurable }?.forEach {
+ it.sourceDirs += inputs.sourcePaths
+
+ it.classpath = it.classpath.plus(inputs.dependencyClasspath)
+ .plus(inputs.bootClasspath)
+ it.dependsOn(inputs.dependencyClasspath)
+ }
}
}
diff --git a/buildSrc/src/main/kotlin/androidx/build/metalava/MetalavaRunner.kt b/buildSrc/src/main/kotlin/androidx/build/metalava/MetalavaRunner.kt
index c3c8438..ab64ddd 100644
--- a/buildSrc/src/main/kotlin/androidx/build/metalava/MetalavaRunner.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/metalava/MetalavaRunner.kt
@@ -55,6 +55,7 @@
"ProtectedMember", // We allow using protected members in androidx
"ManagerLookup", // Managers in androidx are not the same as platfrom services
"ManagerConstructor",
+ "RethrowRemoteException", // This check is for calls into system_server
// List of checks that have bugs, but should be enabled once fixed.
"GetterSetterNames", // b/135498039
@@ -182,6 +183,12 @@
}
}
+ // Never track @Experimental APIs.
+ args += listOf(
+ "--hide-annotation", "androidx.annotation.experimental.Experimental",
+ "--hide-meta-annotation", "androidx.annotation.experimental.Experimental"
+ )
+
val metalavaConfiguration = getMetalavaConfiguration()
runMetalavaWithArgs(metalavaConfiguration, args)
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/ImageAnalysisTest.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/ImageAnalysisTest.java
index b552085..67292a2 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/ImageAnalysisTest.java
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/ImageAnalysisTest.java
@@ -84,8 +84,9 @@
@Rule
public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(
Manifest.permission.CAMERA);
+
@Before
- public void setUp() {
+ public void setUp() {
assumeTrue(CameraUtil.deviceHasCamera());
mAnalysisResults = new HashSet<>();
mAnalysisResultsSemaphore = new Semaphore(/*permits=*/ 0);
@@ -168,11 +169,23 @@
}
@Test
- public void analyzerAnalyzesImages_whenCameraIsOpen()
+ public void analyzesImages_withAcquireLatest_whenCameraIsOpen()
+ throws InterruptedException, CameraInfoUnavailableException {
+ analyzerAnalyzesImagesWithMode(ImageReaderMode.ACQUIRE_LATEST_IMAGE);
+ }
+
+ @Test
+ public void analyzesImages_withAcquireNext_whenCameraIsOpen()
+ throws InterruptedException, CameraInfoUnavailableException {
+ analyzerAnalyzesImagesWithMode(ImageReaderMode.ACQUIRE_NEXT_IMAGE);
+ }
+
+ private void analyzerAnalyzesImagesWithMode(ImageReaderMode imageReaderMode)
throws InterruptedException, CameraInfoUnavailableException {
final int imageFormat = ImageFormat.YUV_420_888;
ImageAnalysisConfig config =
- new ImageAnalysisConfig.Builder().setCallbackHandler(mHandler).build();
+ new ImageAnalysisConfig.Builder().setImageReaderMode(
+ imageReaderMode).setCallbackHandler(mHandler).build();
ImageAnalysis useCase = new ImageAnalysis(config);
Map<String, Size> suggestedResolutionMap = new HashMap<>();
suggestedResolutionMap.put(mCameraId, DEFAULT_RESOLUTION);
@@ -180,6 +193,8 @@
CameraUtil.openCameraWithUseCase(mCameraId, mCamera, useCase);
useCase.setAnalyzer(mAnalyzer);
+ mAnalysisResultsSemaphore.tryAcquire(5, TimeUnit.SECONDS);
+
int sensorRotation = CameraX.getCameraInfo(mCameraId).getSensorRotationDegrees();
// The frames should have properties which match the configuration.
for (ImageProperties properties : mAnalysisResults) {
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/ImageReaderProxysTest.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/ImageReaderProxysTest.java
index 2fe9758..3f31ab0 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/ImageReaderProxysTest.java
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/ImageReaderProxysTest.java
@@ -34,12 +34,13 @@
import androidx.camera.core.ImageReaderProxys;
import androidx.camera.core.ImmediateSurface;
import androidx.camera.core.SessionConfig;
+import androidx.camera.core.impl.utils.executor.CameraXExecutors;
import androidx.camera.testing.CameraUtil;
import androidx.camera.testing.fakes.FakeUseCase;
import androidx.camera.testing.fakes.FakeUseCaseConfig;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.MediumTest;
+import androidx.test.filters.LargeTest;
import org.junit.After;
import org.junit.Before;
@@ -52,14 +53,19 @@
import java.util.Map;
import java.util.concurrent.Semaphore;
-@MediumTest
+/**
+ * Instrument test for {@link ImageReaderProxy}.
+ */
+@LargeTest
@RunWith(AndroidJUnit4.class)
public final class ImageReaderProxysTest {
private static final String CAMERA_ID = "0";
+ private static final int TEST_TIMEOUT_MILLIS = 3000;
private BaseCamera mCamera;
private HandlerThread mHandlerThread;
private Handler mHandler;
+ private List<ImageReaderProxy> mReaders = new ArrayList<>();
private static ImageReaderProxy.OnImageAvailableListener createSemaphoreReleasingListener(
final Semaphore semaphore) {
@@ -76,7 +82,7 @@
}
@Before
- public void setUp() {
+ public void setUp() {
assumeTrue(CameraUtil.deviceHasCamera());
Context context = ApplicationProvider.getApplicationContext();
AppConfig appConfig = Camera2AppConfig.create(context);
@@ -86,34 +92,37 @@
mHandlerThread = new HandlerThread("Background");
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
+ mReaders = new ArrayList<>();
}
@After
public void tearDown() {
- if (mCamera != null && mHandlerThread != null) {
+ for (ImageReaderProxy reader : mReaders) {
+ reader.close();
+ }
+ if (mCamera != null && mHandlerThread != null) {
mCamera.release();
mHandlerThread.quitSafely();
}
}
- @MediumTest
@Test
public void sharedReadersGetFramesFromCamera() throws InterruptedException {
- List<ImageReaderProxy> readers = new ArrayList<>();
List<Semaphore> semaphores = new ArrayList<>();
for (int i = 0; i < 2; ++i) {
ImageReaderProxy reader =
ImageReaderProxys.createSharedReader(
- CAMERA_ID, 640, 480, ImageFormat.YUV_420_888, 2, mHandler);
+ CAMERA_ID, 640, 480, ImageFormat.YUV_420_888, 2,
+ CameraXExecutors.newHandlerExecutor(mHandler));
Semaphore semaphore = new Semaphore(/*permits=*/ 0);
reader.setOnImageAvailableListener(
createSemaphoreReleasingListener(semaphore), mHandler);
- readers.add(reader);
+ mReaders.add(reader);
semaphores.add(semaphore);
}
FakeUseCaseConfig config = new FakeUseCaseConfig.Builder().setTargetName("UseCase").build();
- UseCase useCase = new UseCase(config, readers);
+ UseCase useCase = new UseCase(config, mReaders);
CameraUtil.openCameraWithUseCase(CAMERA_ID, mCamera, useCase);
// Wait for a few frames to be observed.
@@ -122,24 +131,22 @@
}
}
- @MediumTest
@Test
public void isolatedReadersGetFramesFromCamera() throws InterruptedException {
- List<ImageReaderProxy> readers = new ArrayList<>();
List<Semaphore> semaphores = new ArrayList<>();
for (int i = 0; i < 2; ++i) {
ImageReaderProxy reader =
ImageReaderProxys.createIsolatedReader(
- 640, 480, ImageFormat.YUV_420_888, 2, mHandler);
+ 640, 480, ImageFormat.YUV_420_888, 2);
Semaphore semaphore = new Semaphore(/*permits=*/ 0);
reader.setOnImageAvailableListener(
createSemaphoreReleasingListener(semaphore), mHandler);
- readers.add(reader);
+ mReaders.add(reader);
semaphores.add(semaphore);
}
FakeUseCaseConfig config = new FakeUseCaseConfig.Builder().setTargetName("UseCase").build();
- UseCase useCase = new UseCase(config, readers);
+ UseCase useCase = new UseCase(config, mReaders);
CameraUtil.openCameraWithUseCase(CAMERA_ID, mCamera, useCase);
// Wait for a few frames to be observed.
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/UseCaseCombinationTest.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/UseCaseCombinationTest.java
index e7ab1c68..2a0f809 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/UseCaseCombinationTest.java
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/UseCaseCombinationTest.java
@@ -22,10 +22,12 @@
import android.Manifest;
import android.content.Context;
+import android.hardware.camera2.CameraDevice;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
+import androidx.annotation.NonNull;
import androidx.camera.core.AppConfig;
import androidx.camera.core.CameraX;
import androidx.camera.core.CameraX.LensFacing;
@@ -51,7 +53,9 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
/**
* Contains tests for {@link androidx.camera.core.CameraX} which varies use case combinations to
@@ -73,6 +77,8 @@
private ImageAnalysis mImageAnalysis;
private Preview mPreview;
private ImageAnalysis.Analyzer mImageAnalyzer;
+ private CountDownLatch mLatchForDeviceClose;
+ private CameraDevice.StateCallback mDeviceStateCallback;
private Observer<Long> createCountIncrementingObserver() {
return new Observer<Long>() {
@@ -87,6 +93,26 @@
public void setUp() {
assumeTrue(CameraUtil.deviceHasCamera());
+ mLatchForDeviceClose = new CountDownLatch(1);
+ mDeviceStateCallback = new CameraDevice.StateCallback() {
+ @Override
+ public void onOpened(@NonNull CameraDevice camera) {
+ }
+
+ @Override
+ public void onClosed(@NonNull CameraDevice camera) {
+ mLatchForDeviceClose.countDown();
+ }
+
+ @Override
+ public void onDisconnected(@NonNull CameraDevice camera) {
+ }
+
+ @Override
+ public void onError(@NonNull CameraDevice camera, int error) {
+ }
+ };
+
Context context = ApplicationProvider.getApplicationContext();
AppConfig config = Camera2AppConfig.create(context);
@@ -106,10 +132,10 @@
CameraX.unbindAll();
mHandlerThread.quitSafely();
- // Wait some time for the cameras to close.
- // We need the cameras to close to bring CameraX
- // back to the initial state.
- Thread.sleep(3000);
+ // Wait for camera to be closed.
+ if (mLatchForDeviceClose != null) {
+ mLatchForDeviceClose.await(2, TimeUnit.SECONDS);
+ }
}
}
@@ -209,12 +235,11 @@
}
private void initPreview() {
- PreviewConfig previewConfig =
+ PreviewConfig.Builder configBuilder =
new PreviewConfig.Builder()
- .setLensFacing(DEFAULT_LENS_FACING)
.setTargetName("Preview")
- .build();
-
- mPreview = new Preview(previewConfig);
+ .setLensFacing(DEFAULT_LENS_FACING);
+ new Camera2Config.Extender(configBuilder).setDeviceStateCallback(mDeviceStateCallback);
+ mPreview = new Preview(configBuilder.build());
}
}
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/impl/Camera2ImplCameraRepositoryTest.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/impl/Camera2ImplCameraRepositoryTest.java
index 87933c9..481c25f 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/impl/Camera2ImplCameraRepositoryTest.java
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/impl/Camera2ImplCameraRepositoryTest.java
@@ -24,13 +24,17 @@
import android.util.Size;
import android.view.Surface;
+import androidx.annotation.NonNull;
+import androidx.camera.camera2.Camera2Config;
import androidx.camera.camera2.impl.util.SemaphoreReleasingCamera2Callbacks.DeviceStateCallback;
import androidx.camera.camera2.impl.util.SemaphoreReleasingCamera2Callbacks.SessionStateCallback;
import androidx.camera.core.CameraFactory;
import androidx.camera.core.CameraRepository;
+import androidx.camera.core.CameraX;
import androidx.camera.core.CameraX.LensFacing;
import androidx.camera.core.ImmediateSurface;
import androidx.camera.core.SessionConfig;
+import androidx.camera.core.UseCaseConfig;
import androidx.camera.core.UseCaseGroup;
import androidx.camera.testing.CameraUtil;
import androidx.camera.testing.fakes.FakeUseCase;
@@ -47,6 +51,8 @@
import org.junit.runner.RunWith;
import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
/**
* Contains tests for {@link androidx.camera.core.CameraRepository} which require an actual
@@ -55,11 +61,16 @@
@LargeTest
@RunWith(AndroidJUnit4.class)
public final class Camera2ImplCameraRepositoryTest {
+ @Rule
+ public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(
+ Manifest.permission.CAMERA);
private CameraRepository mCameraRepository;
private UseCaseGroup mUseCaseGroup;
private FakeUseCaseConfig mConfig;
private CallbackAttachingFakeUseCase mUseCase;
private CameraFactory mCameraFactory;
+ private CountDownLatch mLatchForDeviceClose;
+ private CameraDevice.StateCallback mDeviceStateCallback;
private String getCameraIdForLensFacingUnchecked(LensFacing lensFacing) {
try {
@@ -70,18 +81,40 @@
}
}
- @Rule
- public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(
- Manifest.permission.CAMERA);
-
@Before
- public void setUp() {
+ public void setUp() {
assumeTrue(CameraUtil.deviceHasCamera());
+
+ mLatchForDeviceClose = new CountDownLatch(1);
+ mDeviceStateCallback = new CameraDevice.StateCallback() {
+ @Override
+ public void onOpened(@NonNull CameraDevice camera) {
+ }
+
+ @Override
+ public void onClosed(@NonNull CameraDevice camera) {
+ mLatchForDeviceClose.countDown();
+ }
+
+ @Override
+ public void onDisconnected(@NonNull CameraDevice camera) {
+ }
+
+ @Override
+ public void onError(@NonNull CameraDevice camera, int error) {
+ }
+ };
+
mCameraRepository = new CameraRepository();
mCameraFactory = new Camera2CameraFactory(ApplicationProvider.getApplicationContext());
mCameraRepository.init(mCameraFactory);
mUseCaseGroup = new UseCaseGroup();
- mConfig = new FakeUseCaseConfig.Builder().setLensFacing(LensFacing.BACK).build();
+
+ FakeUseCaseConfig.Builder configBuilder =
+ new FakeUseCaseConfig.Builder()
+ .setLensFacing(LensFacing.BACK);
+ new Camera2Config.Extender(configBuilder).setDeviceStateCallback(mDeviceStateCallback);
+ mConfig = configBuilder.build();
String mCameraId = getCameraIdForLensFacingUnchecked(mConfig.getLensFacing());
mUseCase = new CallbackAttachingFakeUseCase(mConfig, mCameraId);
mUseCaseGroup.addUseCase(mUseCase);
@@ -92,10 +125,10 @@
if (CameraUtil.deviceHasCamera()) {
mCameraRepository.onGroupInactive(mUseCaseGroup);
- // Wait some time for the cameras to close.
- // We need the cameras to close to bring CameraX
- // back to the initial state.
- Thread.sleep(3000);
+ // Wait camera to be closed.
+ if (mLatchForDeviceClose != null) {
+ mLatchForDeviceClose.await(2, TimeUnit.SECONDS);
+ }
}
}
@@ -155,6 +188,14 @@
attachToCamera(cameraId, builder.build());
}
+ // we need to set Camera2OptionUnpacker to the Config to enable the camera2 callback hookup.
+ @Override
+ protected UseCaseConfig.Builder<?, ?, ?> getDefaultBuilder(CameraX.LensFacing lensFacing) {
+ return new FakeUseCaseConfig.Builder()
+ .setLensFacing(lensFacing)
+ .setSessionOptionUnpacker(new Camera2SessionOptionUnpacker());
+ }
+
@Override
protected Map<String, Size> onSuggestedResolutionUpdated(
Map<String, Size> suggestedResolutionMap) {
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/impl/Camera2ImplCameraXTest.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/impl/Camera2ImplCameraXTest.java
index 85febf0..17736c4 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/impl/Camera2ImplCameraXTest.java
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/impl/Camera2ImplCameraXTest.java
@@ -21,6 +21,7 @@
import static org.junit.Assume.assumeTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
@@ -32,6 +33,7 @@
import android.os.HandlerThread;
import android.os.Looper;
+import androidx.annotation.NonNull;
import androidx.camera.camera2.Camera2AppConfig;
import androidx.camera.camera2.Camera2Config;
import androidx.camera.camera2.impl.util.SemaphoreReleasingCamera2Callbacks.DeviceStateCallback;
@@ -60,6 +62,8 @@
import org.junit.runner.RunWith;
import org.mockito.Mockito;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
/**
@@ -72,6 +76,7 @@
public final class Camera2ImplCameraXTest {
private static final LensFacing DEFAULT_LENS_FACING = LensFacing.BACK;
private final MutableLiveData<Long> mAnalysisResult = new MutableLiveData<>();
+ private final MutableLiveData<Long> mAnalysisResult2 = new MutableLiveData<>();
private final ImageAnalysis.Analyzer mImageAnalyzer =
new ImageAnalysis.Analyzer() {
@Override
@@ -79,12 +84,22 @@
mAnalysisResult.postValue(image.getTimestamp());
}
};
+ private final ImageAnalysis.Analyzer mImageAnalyzer2 =
+ new ImageAnalysis.Analyzer() {
+ @Override
+ public void analyze(ImageProxy image, int rotationDegrees) {
+ mAnalysisResult2.postValue(image.getTimestamp());
+ }
+ };
+ @Rule
+ public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(
+ Manifest.permission.CAMERA);
+ private CountDownLatch mLatchForDeviceClose;
+ private CameraDevice.StateCallback mDeviceStateCallback;
private FakeLifecycleOwner mLifecycle;
private HandlerThread mHandlerThread;
private Handler mMainThreadHandler;
- private CameraDevice.StateCallback mMockStateCallback;
-
private static Observer<Long> createCountIncrementingObserver(final AtomicLong counter) {
return new Observer<Long>() {
@Override
@@ -94,12 +109,8 @@
};
}
- @Rule
- public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(
- Manifest.permission.CAMERA);
-
@Before
- public void setUp() {
+ public void setUp() {
assumeTrue(CameraUtil.deviceHasCamera());
Context context = ApplicationProvider.getApplicationContext();
CameraX.init(context, Camera2AppConfig.create(context));
@@ -107,7 +118,9 @@
mHandlerThread = new HandlerThread("ErrorHandlerThread");
mHandlerThread.start();
mMainThreadHandler = new Handler(Looper.getMainLooper());
- mMockStateCallback = Mockito.mock(CameraDevice.StateCallback.class);
+
+ mLatchForDeviceClose = new CountDownLatch(1);
+ mDeviceStateCallback = spy(new DeviceStateCallbackImpl());
}
@After
@@ -115,56 +128,59 @@
if (mHandlerThread != null) {
CameraX.unbindAll();
mHandlerThread.quitSafely();
-
- // Wait some time for the cameras to close.
- // We need the cameras to close to bring CameraX
- // back to the initial state.
- Thread.sleep(3000);
+ }
+ // Wait camera to be closed.
+ if (mLatchForDeviceClose != null) {
+ mLatchForDeviceClose.await(2, TimeUnit.SECONDS);
}
}
@Test
- public void lifecycleResume_opensCameraAndStreamsFrames() throws InterruptedException {
- final AtomicLong observedCount = new AtomicLong(0);
+ public void lifecycleResume_opensCameraAndStreamsFrames() {
+ Observer<Long> mockObserver = Mockito.mock(Observer.class);
mMainThreadHandler.post(new Runnable() {
@Override
public void run() {
- ImageAnalysisConfig config =
- new ImageAnalysisConfig.Builder()
- .setLensFacing(DEFAULT_LENS_FACING)
- .build();
- ImageAnalysis useCase = new ImageAnalysis(config);
- CameraX.bindToLifecycle(mLifecycle, useCase);
+ ImageAnalysisConfig.Builder builder =
+ new ImageAnalysisConfig.Builder().setLensFacing(DEFAULT_LENS_FACING);
+ new Camera2Config.Extender(builder).setDeviceStateCallback(mDeviceStateCallback);
+ ImageAnalysis useCase = new ImageAnalysis(builder.build());
+ CameraX.bindToLifecycle(mLifecycle, useCase);
useCase.setAnalyzer(mImageAnalyzer);
- mAnalysisResult.observe(mLifecycle, createCountIncrementingObserver(observedCount));
+ mAnalysisResult.observe(mLifecycle, mockObserver);
mLifecycle.startAndResume();
}
});
-
- // Wait a little bit for the camera to open and stream frames.
- Thread.sleep(5000);
-
- // Some frames should have been observed.
- assertThat(observedCount.get()).isAtLeast(10L);
+ verify(mockObserver, timeout(5000).times(10)).onChanged(any());
}
@Test
public void removedUseCase_doesNotStreamWhenLifecycleResumes() throws InterruptedException {
- final AtomicLong observedCount = new AtomicLong(0);
+ Observer<Long> mockObserver = Mockito.mock(Observer.class);
+ Observer<Long> mockObserver2 = Mockito.mock(Observer.class);
+
mMainThreadHandler.post(new Runnable() {
@Override
public void run() {
- ImageAnalysisConfig config =
+ ImageAnalysisConfig.Builder builder =
+ new ImageAnalysisConfig.Builder().setLensFacing(DEFAULT_LENS_FACING);
+ new Camera2Config.Extender(builder).setDeviceStateCallback(mDeviceStateCallback);
+ ImageAnalysis useCase = new ImageAnalysis(builder.build());
+
+ ImageAnalysisConfig config2 =
new ImageAnalysisConfig.Builder()
.setLensFacing(DEFAULT_LENS_FACING)
.build();
- ImageAnalysis useCase = new ImageAnalysis(config);
- CameraX.bindToLifecycle(mLifecycle, useCase);
+ ImageAnalysis useCase2 = new ImageAnalysis(config2);
+
+ CameraX.bindToLifecycle(mLifecycle, useCase, useCase2);
+
useCase.setAnalyzer(mImageAnalyzer);
- mAnalysisResult.observe(mLifecycle, createCountIncrementingObserver(observedCount));
- assertThat(observedCount.get()).isEqualTo(0);
+ useCase2.setAnalyzer(mImageAnalyzer2);
+ mAnalysisResult.observe(mLifecycle, mockObserver);
+ mAnalysisResult2.observe(mLifecycle, mockObserver2);
CameraX.unbind(useCase);
@@ -172,11 +188,10 @@
}
});
- // Wait a little bit for the camera to open and stream frames.
- Thread.sleep(5000);
-
- // No frames should have been observed.
- assertThat(observedCount.get()).isEqualTo(0);
+ // Let second ImageAnalysis get some images. This shows that the first ImageAnalysis has
+ // not observed any images, even though the camera has started to stream.
+ verify(mockObserver2, timeout(3000).times(3)).onChanged(any());
+ verify(mockObserver, never()).onChanged(any());
}
@Test
@@ -229,27 +244,27 @@
public void bind_opensCamera() {
ImageAnalysisConfig.Builder builder =
new ImageAnalysisConfig.Builder().setLensFacing(DEFAULT_LENS_FACING);
- new Camera2Config.Extender(builder).setDeviceStateCallback(mMockStateCallback);
+ new Camera2Config.Extender(builder).setDeviceStateCallback(mDeviceStateCallback);
ImageAnalysisConfig config = builder.build();
ImageAnalysis useCase = new ImageAnalysis(config);
CameraX.bindToLifecycle(mLifecycle, useCase);
useCase.setAnalyzer(mImageAnalyzer);
mLifecycle.startAndResume();
- verify(mMockStateCallback, timeout(3000)).onOpened(any(CameraDevice.class));
+ verify(mDeviceStateCallback, timeout(3000)).onOpened(any(CameraDevice.class));
}
@Test
public void bind_opensCamera_withOutAnalyzer() {
ImageAnalysisConfig.Builder builder =
new ImageAnalysisConfig.Builder().setLensFacing(DEFAULT_LENS_FACING);
- new Camera2Config.Extender(builder).setDeviceStateCallback(mMockStateCallback);
+ new Camera2Config.Extender(builder).setDeviceStateCallback(mDeviceStateCallback);
ImageAnalysisConfig config = builder.build();
ImageAnalysis useCase = new ImageAnalysis(config);
CameraX.bindToLifecycle(mLifecycle, useCase);
mLifecycle.startAndResume();
- verify(mMockStateCallback, timeout(3000)).onOpened(any(CameraDevice.class));
+ verify(mDeviceStateCallback, timeout(3000)).onOpened(any(CameraDevice.class));
}
@Test
@@ -257,10 +272,9 @@
CameraCaptureSession.StateCallback mockSessionStateCallback = Mockito.mock(
CameraCaptureSession.StateCallback.class);
-
ImageAnalysisConfig.Builder builder =
new ImageAnalysisConfig.Builder().setLensFacing(DEFAULT_LENS_FACING);
- new Camera2Config.Extender(builder).setDeviceStateCallback(mMockStateCallback)
+ new Camera2Config.Extender(builder).setDeviceStateCallback(mDeviceStateCallback)
.setSessionStateCallback(mockSessionStateCallback);
ImageAnalysisConfig config = builder.build();
@@ -322,41 +336,41 @@
public void unbindAll_closesAllCameras() {
ImageAnalysisConfig.Builder builder =
new ImageAnalysisConfig.Builder().setLensFacing(DEFAULT_LENS_FACING);
- new Camera2Config.Extender(builder).setDeviceStateCallback(mMockStateCallback);
+ new Camera2Config.Extender(builder).setDeviceStateCallback(mDeviceStateCallback);
ImageAnalysisConfig config = builder.build();
ImageAnalysis useCase = new ImageAnalysis(config);
CameraX.bindToLifecycle(mLifecycle, useCase);
mLifecycle.startAndResume();
- verify(mMockStateCallback, timeout(3000)).onOpened(any(CameraDevice.class));
+ verify(mDeviceStateCallback, timeout(3000)).onOpened(any(CameraDevice.class));
CameraX.unbindAll();
- verify(mMockStateCallback, timeout(3000)).onClosed(any(CameraDevice.class));
+ verify(mDeviceStateCallback, timeout(3000)).onClosed(any(CameraDevice.class));
}
@Test
public void unbindAllAssociatedUseCase_closesCamera() {
ImageAnalysisConfig.Builder builder =
new ImageAnalysisConfig.Builder().setLensFacing(DEFAULT_LENS_FACING);
- new Camera2Config.Extender(builder).setDeviceStateCallback(mMockStateCallback);
+ new Camera2Config.Extender(builder).setDeviceStateCallback(mDeviceStateCallback);
ImageAnalysisConfig config = builder.build();
ImageAnalysis useCase = new ImageAnalysis(config);
CameraX.bindToLifecycle(mLifecycle, useCase);
mLifecycle.startAndResume();
- verify(mMockStateCallback, timeout(3000)).onOpened(any(CameraDevice.class));
+ verify(mDeviceStateCallback, timeout(3000)).onOpened(any(CameraDevice.class));
CameraX.unbind(useCase);
- verify(mMockStateCallback, timeout(3000)).onClosed(any(CameraDevice.class));
+ verify(mDeviceStateCallback, timeout(3000)).onClosed(any(CameraDevice.class));
}
@Test
public void unbindPartialAssociatedUseCase_doesNotCloseCamera() throws InterruptedException {
ImageAnalysisConfig.Builder builder =
new ImageAnalysisConfig.Builder().setLensFacing(DEFAULT_LENS_FACING);
- new Camera2Config.Extender(builder).setDeviceStateCallback(mMockStateCallback);
+ new Camera2Config.Extender(builder).setDeviceStateCallback(mDeviceStateCallback);
ImageAnalysisConfig config0 = builder.build();
ImageAnalysis useCase0 = new ImageAnalysis(config0);
@@ -369,20 +383,20 @@
CameraX.bindToLifecycle(mLifecycle, useCase0, useCase1);
mLifecycle.startAndResume();
- verify(mMockStateCallback, timeout(3000)).onOpened(any(CameraDevice.class));
+ verify(mDeviceStateCallback, timeout(3000)).onOpened(any(CameraDevice.class));
CameraX.unbind(useCase1);
Thread.sleep(3000);
- verify(mMockStateCallback, never()).onClosed(any(CameraDevice.class));
+ verify(mDeviceStateCallback, never()).onClosed(any(CameraDevice.class));
}
@Test
public void unbindAllAssociatedUseCaseInParts_ClosesCamera() {
ImageAnalysisConfig.Builder builder =
new ImageAnalysisConfig.Builder().setLensFacing(DEFAULT_LENS_FACING);
- new Camera2Config.Extender(builder).setDeviceStateCallback(mMockStateCallback);
+ new Camera2Config.Extender(builder).setDeviceStateCallback(mDeviceStateCallback);
ImageAnalysisConfig config0 = builder.build();
ImageAnalysis useCase0 = new ImageAnalysis(config0);
@@ -396,11 +410,30 @@
mLifecycle.startAndResume();
- verify(mMockStateCallback, timeout(3000)).onOpened(any(CameraDevice.class));
+ verify(mDeviceStateCallback, timeout(3000)).onOpened(any(CameraDevice.class));
CameraX.unbind(useCase0);
CameraX.unbind(useCase1);
- verify(mMockStateCallback, timeout(3000).times(1)).onClosed(any(CameraDevice.class));
+ verify(mDeviceStateCallback, timeout(3000).times(1)).onClosed(any(CameraDevice.class));
+ }
+
+ public class DeviceStateCallbackImpl extends CameraDevice.StateCallback {
+ @Override
+ public void onOpened(@NonNull CameraDevice camera) {
+ }
+
+ @Override
+ public void onClosed(@NonNull CameraDevice camera) {
+ mLatchForDeviceClose.countDown();
+ }
+
+ @Override
+ public void onDisconnected(@NonNull CameraDevice camera) {
+ }
+
+ @Override
+ public void onError(@NonNull CameraDevice camera, int error) {
+ }
}
}
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/impl/CameraTest.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/impl/CameraTest.java
index 9b4723f..d07c1d5 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/impl/CameraTest.java
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/impl/CameraTest.java
@@ -30,13 +30,17 @@
import android.os.HandlerThread;
import android.util.Size;
+import androidx.annotation.NonNull;
+import androidx.camera.camera2.Camera2Config;
import androidx.camera.core.BaseCamera;
import androidx.camera.core.CameraDeviceConfig;
import androidx.camera.core.CameraFactory;
+import androidx.camera.core.CameraX;
import androidx.camera.core.CameraX.LensFacing;
import androidx.camera.core.ImmediateSurface;
import androidx.camera.core.SessionConfig;
import androidx.camera.core.UseCase;
+import androidx.camera.core.UseCaseConfig;
import androidx.camera.testing.CameraUtil;
import androidx.camera.testing.fakes.FakeUseCase;
import androidx.camera.testing.fakes.FakeUseCaseConfig;
@@ -54,6 +58,8 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
@LargeTest
@RunWith(AndroidJUnit4.class)
@@ -67,6 +73,9 @@
OnImageAvailableListener mMockOnImageAvailableListener;
String mCameraId;
+ private CountDownLatch mLatchForDeviceClose;
+ private CameraDevice.StateCallback mDeviceStateCallback;
+
private static String getCameraIdForLensFacingUnchecked(LensFacing lensFacing) {
try {
return sCameraFactory.cameraIdForLensFacing(lensFacing);
@@ -82,21 +91,39 @@
}
@Before
- public void setup() {
+ public void setup() {
assumeTrue(CameraUtil.deviceHasCamera());
mMockOnImageAvailableListener = Mockito.mock(ImageReader.OnImageAvailableListener.class);
- FakeUseCaseConfig config =
+
+ mLatchForDeviceClose = new CountDownLatch(1);
+ mDeviceStateCallback = new CameraDevice.StateCallback() {
+ @Override
+ public void onOpened(@NonNull CameraDevice camera) {
+ }
+
+ @Override
+ public void onClosed(@NonNull CameraDevice camera) {
+ mLatchForDeviceClose.countDown();
+ }
+
+ @Override
+ public void onDisconnected(@NonNull CameraDevice camera) {
+ }
+
+ @Override
+ public void onError(@NonNull CameraDevice camera, int error) {
+ }
+ };
+
+ mCameraId = getCameraIdForLensFacingUnchecked(DEFAULT_LENS_FACING);
+ mCamera = sCameraFactory.getCamera(mCameraId);
+
+ FakeUseCaseConfig.Builder configBuilder =
new FakeUseCaseConfig.Builder()
.setTargetName("UseCase")
- .setLensFacing(DEFAULT_LENS_FACING)
- .build();
- mCameraId = getCameraIdForLensFacingUnchecked(DEFAULT_LENS_FACING);
- mFakeUseCase = new TestUseCase(config, mMockOnImageAvailableListener);
- Map<String, Size> suggestedResolutionMap = new HashMap<>();
- suggestedResolutionMap.put(mCameraId, new Size(640, 480));
- mFakeUseCase.updateSuggestedResolution(suggestedResolutionMap);
-
- mCamera = sCameraFactory.getCamera(mCameraId);
+ .setLensFacing(DEFAULT_LENS_FACING);
+ new Camera2Config.Extender(configBuilder).setDeviceStateCallback(mDeviceStateCallback);
+ mFakeUseCase = new TestUseCase(configBuilder.build(), mMockOnImageAvailableListener);
}
@After
@@ -104,21 +131,21 @@
// Need to release the camera no matter what is done, otherwise the CameraDevice is not
// closed.
// When the CameraDevice is not closed, then it can cause problems with interferes with
- // other
- // test cases.
+ // other test cases.
if (mCamera != null) {
mCamera.release();
mCamera = null;
}
- // Wait a little bit for the camera device to close.
- // TODO(b/111991758): Listen for the close signal when it becomes available.
- Thread.sleep(2000);
-
+ // Wait camera to be closed.
if (mFakeUseCase != null) {
mFakeUseCase.close();
mFakeUseCase = null;
}
+
+ if (mLatchForDeviceClose != null) {
+ mLatchForDeviceClose.await(2, TimeUnit.SECONDS);
+ }
}
@Test
@@ -135,7 +162,6 @@
@Test
public void activeUseCase() {
mCamera.open();
-
mCamera.onUseCaseActive(mFakeUseCase);
verify(mMockOnImageAvailableListener, never()).onImageAvailable(any(ImageReader.class));
@@ -144,9 +170,7 @@
}
@Test
- public void onlineAndActiveUseCase() throws InterruptedException {
- mCamera.open();
-
+ public void onlineAndActiveUseCase() {
mCamera.addOnlineUseCase(Collections.<UseCase>singletonList(mFakeUseCase));
mCamera.onUseCaseActive(mFakeUseCase);
@@ -156,9 +180,8 @@
@Test
public void removeOnlineUseCase() {
- mCamera.open();
-
mCamera.addOnlineUseCase(Collections.<UseCase>singletonList(mFakeUseCase));
+
mCamera.removeOnlineUseCase(Collections.<UseCase>singletonList(mFakeUseCase));
mCamera.onUseCaseActive(mFakeUseCase);
@@ -175,9 +198,6 @@
@Test
public void closedCamera() {
- mCamera.open();
-
- mCamera.close();
mCamera.addOnlineUseCase(Collections.<UseCase>singletonList(mFakeUseCase));
mCamera.removeOnlineUseCase(Collections.<UseCase>singletonList(mFakeUseCase));
@@ -186,6 +206,9 @@
@Test
public void releaseUnopenedCamera() {
+ // bypass camera close checking
+ mLatchForDeviceClose = null;
+ // Checks that if a camera has been released then calling open() will no longer open it.
mCamera.release();
mCamera.open();
@@ -197,8 +220,10 @@
@Test
public void releasedOpenedCamera() {
- mCamera.release();
+ // bypass camera close checking
+ mLatchForDeviceClose = null;
mCamera.open();
+ mCamera.release();
mCamera.addOnlineUseCase(Collections.<UseCase>singletonList(mFakeUseCase));
mCamera.onUseCaseActive(mFakeUseCase);
@@ -211,11 +236,15 @@
HandlerThread mHandlerThread = new HandlerThread("HandlerThread");
Handler mHandler;
ImageReader mImageReader;
+ FakeUseCaseConfig mConfig;
TestUseCase(
FakeUseCaseConfig config,
ImageReader.OnImageAvailableListener listener) {
super(config);
+ // Ensure we're using the combined configuration (user config + defaults)
+ mConfig = (FakeUseCaseConfig) getUseCaseConfig();
+
mImageAvailableListener = listener;
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
@@ -225,6 +254,14 @@
updateSuggestedResolution(suggestedResolutionMap);
}
+ // we need to set Camera2OptionUnpacker to the Config to enable the camera2 callback hookup.
+ @Override
+ protected UseCaseConfig.Builder<?, ?, ?> getDefaultBuilder(CameraX.LensFacing lensFacing) {
+ return new FakeUseCaseConfig.Builder()
+ .setLensFacing(lensFacing)
+ .setSessionOptionUnpacker(new Camera2SessionOptionUnpacker());
+ }
+
void close() {
mHandler.removeCallbacksAndMessages(null);
mHandlerThread.quitSafely();
@@ -239,7 +276,8 @@
LensFacing lensFacing = ((CameraDeviceConfig) getUseCaseConfig()).getLensFacing();
String cameraId = getCameraIdForLensFacingUnchecked(lensFacing);
Size resolution = suggestedResolutionMap.get(cameraId);
- SessionConfig.Builder builder = new SessionConfig.Builder();
+ SessionConfig.Builder builder = SessionConfig.Builder.createFrom(mConfig);
+
builder.setTemplateType(CameraDevice.TEMPLATE_PREVIEW);
mImageReader =
ImageReader.newInstance(
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/SupportedSurfaceCombination.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/SupportedSurfaceCombination.java
index 044c963..a226491 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/SupportedSurfaceCombination.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/SupportedSurfaceCombination.java
@@ -43,6 +43,7 @@
import androidx.camera.core.SurfaceSizeDefinition;
import androidx.camera.core.UseCase;
import androidx.camera.core.UseCaseConfig;
+import androidx.core.util.Preconditions;
import java.util.ArrayList;
import java.util.Arrays;
@@ -69,6 +70,7 @@
private static final Size QUALITY_1080P_SIZE = new Size(1920, 1080);
private static final Size QUALITY_720P_SIZE = new Size(1280, 720);
private static final Size QUALITY_480P_SIZE = new Size(720, 480);
+ private static final int ALIGN16 = 16;
private final List<SurfaceCombination> mSurfaceCombinations = new ArrayList<>();
private final Map<Integer, Size> mMaxSizeCache = new HashMap<>();
private String mCameraId;
@@ -342,37 +344,6 @@
+ imageFormat);
}
- // Check whether the desired default resolution is included in the original supported list
- boolean isDefaultResolutionSupported = outputSizeCandidates.contains(DEFAULT_SIZE);
-
- // If the target resolution is set, use it to find the minimum one from big enough items
- Size targetSize = config.getTargetResolution(ZERO_SIZE);
-
- if (!targetSize.equals(ZERO_SIZE)) {
- int indexBigEnough = 0;
-
- // Get the index of the item that is big enough for the view size
- for (int i = 0; i < outputSizeCandidates.size(); i++) {
- Size outputSize = outputSizeCandidates.get(i);
- if (outputSize.getWidth() * outputSize.getHeight()
- >= targetSize.getWidth() * targetSize.getHeight()) {
- indexBigEnough = i;
- } else {
- break;
- }
- }
-
- // Remove the additional items that is larger than the big enough item
- outputSizeCandidates.subList(0, indexBigEnough).clear();
- }
-
- if (outputSizeCandidates.isEmpty() && !isDefaultResolutionSupported) {
- throw new IllegalArgumentException(
- "Can not get supported output size for the desired output size quality for "
- + "the format: "
- + imageFormat);
- }
-
// Rearrange the supported size to put the ones with the same aspect ratio in the front
// of the list and put others in the end from large to small. Some low end devices may
// not able to get an supported resolution that match the preferred aspect ratio.
@@ -385,22 +356,45 @@
for (Size outputSize : outputSizeCandidates) {
// If target aspect ratio is set, moves the matched results to the front of the list.
- if (aspectRatio != null && aspectRatio.equals(
- new Rational(outputSize.getWidth(), outputSize.getHeight()))) {
+ if (hasMatchingAspectRatio(outputSize, aspectRatio)) {
sizesMatchAspectRatio.add(outputSize);
} else {
sizesNotMatchAspectRatio.add(outputSize);
}
}
+ // Sort not matched results by how close they are to the target aspect ratio.
+ if (aspectRatio != null) {
+ Collections.sort(sizesNotMatchAspectRatio,
+ new CompareSizesByDistanceToTargetRatio(aspectRatio.floatValue()));
+ }
+
+ // Check whether the desired default resolution is included in the original supported list
+ boolean isDefaultResolutionSupported = outputSizeCandidates.contains(DEFAULT_SIZE);
+
+ // If the target resolution is set, use it to find the minimum one from big enough items
+ Size targetSize = config.getTargetResolution(ZERO_SIZE);
+
+ if (!targetSize.equals(ZERO_SIZE)) {
+ removeSupportedSizesByTargetSize(sizesMatchAspectRatio, targetSize);
+ removeSupportedSizesByTargetSize(sizesNotMatchAspectRatio, targetSize);
+ }
+
+ if (sizesMatchAspectRatio.isEmpty() && sizesNotMatchAspectRatio.isEmpty()
+ && !isDefaultResolutionSupported) {
+ throw new IllegalArgumentException(
+ "Can not get supported output size for the desired output size quality for "
+ + "the format: "
+ + imageFormat);
+ }
+
List<Size> supportedResolutions = new ArrayList<>();
// No need to sort again since the source list has been sorted previously
supportedResolutions.addAll(sizesMatchAspectRatio);
supportedResolutions.addAll(sizesNotMatchAspectRatio);
// If there is no available size for the conditions and default resolution is in the
- // supported
- // list, return the default resolution.
+ // supported list, return the default resolution.
if (supportedResolutions.isEmpty() && !isDefaultResolutionSupported) {
supportedResolutions.add(DEFAULT_SIZE);
}
@@ -427,6 +421,73 @@
return outputRatio;
}
+ private boolean hasMatchingAspectRatio(Size resolution, Rational aspectRatio) {
+ boolean isMatch;
+ if (aspectRatio == null) {
+ isMatch = false;
+ } else if (aspectRatio.equals(
+ new Rational(resolution.getWidth(), resolution.getHeight()))) {
+ isMatch = true;
+ } else {
+ isMatch = isPossibleMod16FromAspectRatio(resolution, aspectRatio);
+ }
+ return isMatch;
+ }
+
+ private boolean isPossibleMod16FromAspectRatio(Size resolution, Rational aspectRatio) {
+ int width = resolution.getWidth();
+ int height = resolution.getHeight();
+ Rational invAspectRatio = new Rational(aspectRatio.getDenominator(),
+ aspectRatio.getNumerator());
+ if (width % 16 == 0 && height % 16 == 0) {
+ return ratioIntersectsMod16Segment(Math.max(0, height - ALIGN16), width, aspectRatio)
+ || ratioIntersectsMod16Segment(Math.max(0, width - ALIGN16), height,
+ invAspectRatio);
+ } else if (width % 16 == 0) {
+ return ratioIntersectsMod16Segment(height, width, aspectRatio);
+ } else if (height % 16 == 0) {
+ return ratioIntersectsMod16Segment(width, height, invAspectRatio);
+ }
+ return false;
+ }
+
+ private boolean ratioIntersectsMod16Segment(int height, int mod16Width, Rational aspectRatio) {
+ Preconditions.checkArgument(mod16Width % 16 == 0);
+ double aspectRatioWidth =
+ height * aspectRatio.getNumerator() / (double) aspectRatio.getDenominator();
+ return aspectRatioWidth > Math.max(0, mod16Width - ALIGN16) && aspectRatioWidth < (
+ mod16Width + ALIGN16);
+ }
+
+ private void removeSupportedSizesByTargetSize(List<Size> supportedSizesList,
+ Size targetSize) {
+ if (supportedSizesList != null && supportedSizesList.size() != 0) {
+ int indexBigEnough = 0;
+ // Get the index of the item that is big enough for the view size in matched list
+ for (int i = 0; i < supportedSizesList.size(); i++) {
+ Size outputSize = supportedSizesList.get(i);
+ if (outputSize.getWidth() * outputSize.getHeight()
+ >= targetSize.getWidth() * targetSize.getHeight()) {
+ indexBigEnough = i;
+ } else {
+ break;
+ }
+ }
+
+ Size bigEnoughSize = supportedSizesList.get(indexBigEnough);
+ List<Size> removeSizes = new ArrayList<>();
+ for (Size size : supportedSizesList) {
+ if (size.getWidth() * size.getHeight()
+ > bigEnoughSize.getWidth() * bigEnoughSize.getHeight()) {
+ removeSizes.add(size);
+ }
+ }
+
+ // Remove the additional items that is larger than the big enough item
+ supportedSizesList.removeAll(removeSizes);
+ }
+ }
+
private List<List<Size>> getAllPossibleSizeArrangements(
List<List<Size>> supportedOutputSizesList) {
int totalArrangementsCount = 1;
@@ -1019,4 +1080,26 @@
return result;
}
}
+
+ /** Comparator based on how close they are to the target aspect ratio. */
+ static final class CompareSizesByDistanceToTargetRatio implements Comparator<Size> {
+ private Float mTargetRatio;
+
+ CompareSizesByDistanceToTargetRatio(Float targetRatio) {
+ mTargetRatio = targetRatio;
+ }
+
+ @Override
+ public int compare(Size lhs, Size rhs) {
+
+ final Float lhsRatio = lhs.getWidth() * 1.0f / lhs.getHeight();
+ final Float rhsRatio = rhs.getWidth() * 1.0f / rhs.getHeight();
+
+ final Float lhsRatioDelta = Math.abs(lhsRatio - mTargetRatio);
+ final Float rhsRatioDelta = Math.abs(rhsRatio - mTargetRatio);
+
+ int result = (int) Math.signum(lhsRatioDelta - rhsRatioDelta);
+ return result;
+ }
+ }
}
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/impl/Camera2DeviceSurfaceManagerTest.java b/camera/camera-camera2/src/test/java/androidx/camera/camera2/impl/Camera2DeviceSurfaceManagerTest.java
index 05b98c0..63bd355 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/impl/Camera2DeviceSurfaceManagerTest.java
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/impl/Camera2DeviceSurfaceManagerTest.java
@@ -339,7 +339,7 @@
@Test
public void getSuggestedResolutionsForMixedUseCaseInLimitedDevice() {
- Rational aspectRatio = new Rational(16, 9);
+ Rational aspectRatio = new Rational(9, 16);
PreviewConfig.Builder previewConfigBuilder = new PreviewConfig.Builder();
VideoCaptureConfig.Builder videoCaptureConfigBuilder = new VideoCaptureConfig.Builder();
ImageCaptureConfig.Builder imageCaptureConfigBuilder = new ImageCaptureConfig.Builder();
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/impl/Camera2LensFacingCameraIdFilterTest.java b/camera/camera-camera2/src/test/java/androidx/camera/camera2/impl/Camera2LensFacingCameraIdFilterTest.java
index eb2398f..7054b98 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/impl/Camera2LensFacingCameraIdFilterTest.java
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/impl/Camera2LensFacingCameraIdFilterTest.java
@@ -21,6 +21,7 @@
import android.content.Context;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraManager;
+import android.os.Build;
import androidx.camera.core.CameraX;
import androidx.test.core.app.ApplicationProvider;
@@ -30,6 +31,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
import org.robolectric.annotation.internal.DoNotInstrument;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.shadows.ShadowCameraCharacteristics;
@@ -41,6 +43,7 @@
@SmallTest
@RunWith(RobolectricTestRunner.class)
@DoNotInstrument
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
public class Camera2LensFacingCameraIdFilterTest {
private static final String CAMERA0_ID = "0";
private static final int CAMERA0_LENS_FACING_INT = CameraCharacteristics.LENS_FACING_BACK;
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/impl/SupportedSurfaceCombinationTest.java b/camera/camera-camera2/src/test/java/androidx/camera/camera2/impl/SupportedSurfaceCombinationTest.java
index 2e10371..92cfd94 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/impl/SupportedSurfaceCombinationTest.java
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/impl/SupportedSurfaceCombinationTest.java
@@ -92,6 +92,7 @@
private final Size mRecordSize = new Size(3840, 2160);
private final Size mMaximumSize = new Size(4032, 3024);
private final Size mMaximumVideoSize = new Size(1920, 1080);
+ private final Size mMod16Size = new Size(960, 544);
private final CamcorderProfileHelper mMockCamcorderProfileHelper =
Mockito.mock(CamcorderProfileHelper.class);
@@ -124,6 +125,7 @@
new Size(1920, 1080),
new Size(1280, 720),
new Size(1280, 720), // duplicate the size since Nexus 5X emulator has the case.
+ new Size(960, 544), // a mod16 version of resolution with 16:9 aspect ratio.
new Size(640, 480),
new Size(320, 240),
new Size(320, 180)
@@ -583,7 +585,7 @@
new SupportedSurfaceCombination(
mContext, LIMITED_CAMERA_ID, mMockCamcorderProfileHelper);
- Rational aspectRatio = new Rational(16, 9);
+ Rational aspectRatio = new Rational(9, 16);
PreviewConfig.Builder previewConfigBuilder = new PreviewConfig.Builder();
VideoCaptureConfig.Builder videoCaptureConfigBuilder = new VideoCaptureConfig.Builder();
ImageCaptureConfig.Builder imageCaptureConfigBuilder = new ImageCaptureConfig.Builder();
@@ -644,7 +646,7 @@
Map<UseCase, Size> suggestedResolutionMap =
supportedSurfaceCombination.getSuggestedResolutions(null, useCases);
- assertThat(suggestedResolutionMap).containsEntry(imageCapture, mAnalysisSize);
+ assertThat(suggestedResolutionMap).containsEntry(imageCapture, mMaximumSize);
assertThat(suggestedResolutionMap).containsEntry(preview, mAnalysisSize);
assertThat(suggestedResolutionMap).containsEntry(imageAnalysis, mAnalysisSize);
}
@@ -805,6 +807,39 @@
assertEquals(mMaximumSize, maximumJPEGSize);
}
+ @Test
+ public void isAspectRatioMatchWithSupportedMod16Resolution() {
+ setupCamera(/* supportsRaw= */ false);
+ SupportedSurfaceCombination supportedSurfaceCombination =
+ new SupportedSurfaceCombination(
+ mContext, LEGACY_CAMERA_ID, mMockCamcorderProfileHelper);
+
+ Rational aspectRatio = new Rational(9, 16);
+ PreviewConfig.Builder previewConfigBuilder = new PreviewConfig.Builder();
+ ImageCaptureConfig.Builder imageCaptureConfigBuilder = new ImageCaptureConfig.Builder();
+
+ previewConfigBuilder.setLensFacing(LensFacing.BACK);
+ imageCaptureConfigBuilder.setLensFacing(LensFacing.BACK);
+
+ previewConfigBuilder.setTargetAspectRatio(aspectRatio);
+ imageCaptureConfigBuilder.setTargetAspectRatio(aspectRatio);
+
+ previewConfigBuilder.setTargetResolution(mMod16Size);
+ imageCaptureConfigBuilder.setTargetResolution(mMod16Size);
+
+ Preview preview = new Preview(previewConfigBuilder.build());
+ ImageCapture imageCapture = new ImageCapture(imageCaptureConfigBuilder.build());
+
+ List<UseCase> useCases = new ArrayList<>();
+ useCases.add(preview);
+ useCases.add(imageCapture);
+
+ Map<UseCase, Size> suggestedResolutionMap =
+ supportedSurfaceCombination.getSuggestedResolutions(null, useCases);
+ assertThat(suggestedResolutionMap).containsEntry(preview, mMod16Size);
+ assertThat(suggestedResolutionMap).containsEntry(imageCapture, mMod16Size);
+ }
+
private void setupCamera(boolean supportsRaw) {
addCamera(
LEGACY_CAMERA_ID, CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY, null,
diff --git a/camera/camera-core/src/androidTest/java/androidx/camera/core/AndroidImageReaderProxyTest.java b/camera/camera-core/src/androidTest/java/androidx/camera/core/AndroidImageReaderProxyTest.java
index b5efe40..fec0614 100644
--- a/camera/camera-core/src/androidTest/java/androidx/camera/core/AndroidImageReaderProxyTest.java
+++ b/camera/camera-core/src/androidTest/java/androidx/camera/core/AndroidImageReaderProxyTest.java
@@ -29,6 +29,7 @@
import android.os.Handler;
import android.view.Surface;
+import androidx.camera.core.impl.utils.executor.CameraXExecutors;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
@@ -112,7 +113,8 @@
ImageReaderProxy.OnImageAvailableListener listener =
mock(ImageReaderProxy.OnImageAvailableListener.class);
- mImageReaderProxy.setOnImageAvailableListener(listener, /*handler=*/ null);
+ mImageReaderProxy.setOnImageAvailableListener(listener,
+ CameraXExecutors.directExecutor());
ArgumentCaptor<ImageReader.OnImageAvailableListener> transformedListenerCaptor =
ArgumentCaptor.forClass(ImageReader.OnImageAvailableListener.class);
diff --git a/camera/camera-core/src/androidTest/java/androidx/camera/core/CameraXTest.java b/camera/camera-core/src/androidTest/java/androidx/camera/core/CameraXTest.java
index a124e1c..ab8c1f2 100644
--- a/camera/camera-core/src/androidTest/java/androidx/camera/core/CameraXTest.java
+++ b/camera/camera-core/src/androidTest/java/androidx/camera/core/CameraXTest.java
@@ -121,13 +121,9 @@
}
@After
- public void tearDown() throws InterruptedException {
+ public void tearDown() {
CameraX.unbindAll();
mHandlerThread.quitSafely();
-
- // Wait some time for the cameras to close. We need the cameras to close to bring CameraX
- // back to the initial state.
- Thread.sleep(3000);
}
@Test
@@ -335,7 +331,7 @@
SessionConfig.Builder builder = new SessionConfig.Builder();
- UseCaseConfig config = getUseCaseConfig();
+ UseCaseConfig<?> config = getUseCaseConfig();
String cameraId = getCameraIdUnchecked(config);
attachToCamera(cameraId, builder.build());
return suggestedResolutionMap;
diff --git a/camera/camera-core/src/androidTest/java/androidx/camera/core/ForwardingImageReaderListenerTest.java b/camera/camera-core/src/androidTest/java/androidx/camera/core/ForwardingImageReaderListenerTest.java
index cec4086..223548c 100644
--- a/camera/camera-core/src/androidTest/java/androidx/camera/core/ForwardingImageReaderListenerTest.java
+++ b/camera/camera-core/src/androidTest/java/androidx/camera/core/ForwardingImageReaderListenerTest.java
@@ -26,8 +26,6 @@
import static org.mockito.Mockito.when;
import android.graphics.ImageFormat;
-import android.media.Image;
-import android.media.ImageReader;
import android.os.Handler;
import android.os.HandlerThread;
import android.view.Surface;
@@ -52,15 +50,15 @@
private static final int IMAGE_FORMAT = ImageFormat.YUV_420_888;
private static final int MAX_IMAGES = 10;
- private final ImageReader mImageReader = mock(ImageReader.class);
+ private final ImageReaderProxy mImageReader = mock(ImageReaderProxy.class);
private final Surface mSurface = mock(Surface.class);
private HandlerThread mHandlerThread;
private Handler mHandler;
private List<QueuedImageReaderProxy> mImageReaderProxies;
private ForwardingImageReaderListener mForwardingListener;
- private static Image createMockImage() {
- Image image = mock(Image.class);
+ private static ImageProxy createMockImage() {
+ ImageProxy image = mock(ImageProxy.class);
when(image.getWidth()).thenReturn(IMAGE_WIDTH);
when(image.getHeight()).thenReturn(IMAGE_HEIGHT);
when(image.getFormat()).thenReturn(IMAGE_FORMAT);
@@ -107,7 +105,7 @@
@Test
public void newImageIsForwardedToAllListeners() {
- Image baseImage = createMockImage();
+ ImageProxy baseImage = createMockImage();
when(mImageReader.acquireNextImage()).thenReturn(baseImage);
List<ImageReaderProxy.OnImageAvailableListener> listeners = new ArrayList<>();
for (ImageReaderProxy imageReaderProxy : mImageReaderProxies) {
@@ -132,7 +130,7 @@
public void baseImageIsClosed_allQueuesAreCleared_whenAllForwardedCopiesAreClosed()
throws InterruptedException {
Semaphore onCloseSemaphore = new Semaphore(/*permits=*/ 0);
- Image baseImage = createMockImage();
+ ImageProxy baseImage = createMockImage();
when(mImageReader.acquireNextImage()).thenReturn(baseImage);
for (ImageReaderProxy imageReaderProxy : mImageReaderProxies) {
// Close the image for every listener.
@@ -158,7 +156,7 @@
public void baseImageIsNotClosed_someQueuesAreCleared_whenNotAllForwardedCopiesAreClosed()
throws InterruptedException {
Semaphore onCloseSemaphore = new Semaphore(/*permits=*/ 0);
- Image baseImage = createMockImage();
+ ImageProxy baseImage = createMockImage();
when(mImageReader.acquireNextImage()).thenReturn(baseImage);
// Don't close the image for the first listener.
mImageReaderProxies.get(0).setOnImageAvailableListener(createMockListener(), mHandler);
diff --git a/camera/camera-core/src/androidTest/java/androidx/camera/core/MetadataImageReaderTest.java b/camera/camera-core/src/androidTest/java/androidx/camera/core/MetadataImageReaderTest.java
index 99b82f9..ecd5238 100644
--- a/camera/camera-core/src/androidTest/java/androidx/camera/core/MetadataImageReaderTest.java
+++ b/camera/camera-core/src/androidTest/java/androidx/camera/core/MetadataImageReaderTest.java
@@ -20,6 +20,7 @@
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.Looper;
import androidx.camera.testing.HandlerUtil;
import androidx.camera.testing.fakes.FakeCameraCaptureResult;
@@ -49,6 +50,8 @@
private final Semaphore mSemaphore = new Semaphore(0);
private HandlerThread mBackgroundThread;
private Handler mBackgroundHandler;
+
+ private Handler mMainHandler;
private MetadataImageReader mMetadataImageReader;
@Before
@@ -57,6 +60,8 @@
mBackgroundThread.start();
mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
+ mMainHandler = new Handler(Looper.getMainLooper());
+
createMetadataImageReaderWithCapacity(8);
mCameraCaptureResult0.setTimestamp(TIMESTAMP_0);
mCameraCaptureResult1.setTimestamp(TIMESTAMP_1);
@@ -264,7 +269,7 @@
private void createMetadataImageReaderWithCapacity(int maxImages) {
mImageReader = new FakeImageReaderProxy(maxImages);
- mMetadataImageReader = new MetadataImageReader(mImageReader, null);
+ mMetadataImageReader = new MetadataImageReader(mImageReader, mMainHandler);
}
private void triggerImageAvailable(long timestamp) throws InterruptedException {
diff --git a/camera/camera-core/src/androidTest/java/androidx/camera/core/QueuedImageReaderProxyTest.java b/camera/camera-core/src/androidTest/java/androidx/camera/core/QueuedImageReaderProxyTest.java
index d7028b6..0310dd2 100644
--- a/camera/camera-core/src/androidTest/java/androidx/camera/core/QueuedImageReaderProxyTest.java
+++ b/camera/camera-core/src/androidTest/java/androidx/camera/core/QueuedImageReaderProxyTest.java
@@ -39,6 +39,8 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
@LargeTest
@@ -52,6 +54,7 @@
private final Surface mSurface = mock(Surface.class);
private HandlerThread mHandlerThread;
private Handler mHandler;
+ private Executor mExecutor;
private QueuedImageReaderProxy mImageReaderProxy;
private static ImageProxy createMockImageProxy() {
@@ -84,6 +87,7 @@
mHandlerThread = new HandlerThread("background");
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
+ mExecutor = Executors.newSingleThreadExecutor();
mImageReaderProxy =
new QueuedImageReaderProxy(
IMAGE_WIDTH, IMAGE_HEIGHT, IMAGE_FORMAT, MAX_IMAGES, mSurface);
@@ -168,11 +172,23 @@
}
@Test
- public void enqueueImage_invokesListenerCallback() {
+ public void enqueueImage_invokesListenerCallbackOnHandler() {
ImageReaderProxy.OnImageAvailableListener listener =
mock(ImageReaderProxy.OnImageAvailableListener.class);
mImageReaderProxy.setOnImageAvailableListener(listener, mHandler);
+ enqueueImage_invokesListenerCallback(listener);
+ }
+ @Test
+ public void enqueueImage_invokesListenerCallbackOnExecutor() {
+ ImageReaderProxy.OnImageAvailableListener listener =
+ mock(ImageReaderProxy.OnImageAvailableListener.class);
+ mImageReaderProxy.setOnImageAvailableListener(listener, mExecutor);
+ enqueueImage_invokesListenerCallback(listener);
+ }
+
+ private void enqueueImage_invokesListenerCallback(
+ ImageReaderProxy.OnImageAvailableListener listener) {
mImageReaderProxy.enqueueImage(createForwardingImageProxy());
mImageReaderProxy.enqueueImage(createForwardingImageProxy());
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/AndroidImageReaderProxy.java b/camera/camera-core/src/main/java/androidx/camera/core/AndroidImageReaderProxy.java
index 2a304e0..bef28dd 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/AndroidImageReaderProxy.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/AndroidImageReaderProxy.java
@@ -22,7 +22,12 @@
import android.view.Surface;
import androidx.annotation.GuardedBy;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.camera.core.impl.utils.MainThreadAsyncHandler;
+import androidx.camera.core.impl.utils.executor.CameraXExecutors;
+
+import java.util.concurrent.Executor;
/**
* An {@link ImageReaderProxy} which wraps around an {@link ImageReader}.
@@ -96,15 +101,35 @@
@Override
public synchronized void setOnImageAvailableListener(
- @Nullable final ImageReaderProxy.OnImageAvailableListener listener,
+ @NonNull final ImageReaderProxy.OnImageAvailableListener listener,
@Nullable Handler handler) {
+ // Unlike ImageReader.setOnImageAvailableListener(), if handler == null, the callback
+ // will not be triggered at all, instead of being triggered on main thread.
+ setOnImageAvailableListener(listener,
+ handler == null ? null : CameraXExecutors.newHandlerExecutor(handler));
+ }
+
+ @Override
+ public synchronized void setOnImageAvailableListener(
+ @NonNull OnImageAvailableListener listener,
+ @NonNull Executor executor) {
+ // ImageReader does not accept an executor. As a workaround, the callback is run on main
+ // handler then immediately posted to the executor.
ImageReader.OnImageAvailableListener transformedListener =
new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
- listener.onImageAvailable(AndroidImageReaderProxy.this);
+ executor.execute(new Runnable() {
+ @Override
+ public void run() {
+ listener.onImageAvailable(AndroidImageReaderProxy.this);
+ }
+ });
+
}
};
- mImageReader.setOnImageAvailableListener(transformedListener, handler);
+ mImageReader.setOnImageAvailableListener(transformedListener,
+ MainThreadAsyncHandler.getInstance());
}
+
}
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 0679266..fa8fec7 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
@@ -290,6 +290,8 @@
* @param lensFacing the lens facing
* @return true if the device has at least one camera with the specified lens facing,
* otherwise false.
+ * @throws CameraInfoUnavailableException if unable to access cameras, perhaps due to
+ * insufficient permissions
*/
public static boolean hasCameraWithLensFacing(LensFacing lensFacing)
throws CameraInfoUnavailableException {
@@ -325,10 +327,11 @@
* @return the cameraId if camera exists or {@code null} if no camera found with the config
* @throws CameraInfoUnavailableException if unable to access cameras, perhaps due to
* insufficient permissions.
+ * @throws IllegalArgumentException if there's no lens facing set in the config.
* @hide
*/
@RestrictTo(Scope.LIBRARY_GROUP)
- @NonNull
+ @Nullable
public static String getCameraWithCameraDeviceConfig(CameraDeviceConfig config)
throws CameraInfoUnavailableException {
Set<String> availableCameraIds = getCameraFactory().getAvailableCameraIds();
@@ -338,7 +341,10 @@
availableCameraIds =
LensFacingCameraIdFilter.createLensFacingCameraIdFilter(lensFacing)
.filter(availableCameraIds);
+ } else {
+ throw new IllegalArgumentException("Lens facing isn't set in the config.");
}
+
CameraIdFilter cameraIdFilter = config.getCameraIdFilter(null);
if (cameraIdFilter != null) {
// Filters camera ids with other filters.
@@ -348,7 +354,7 @@
if (!availableCameraIds.isEmpty()) {
return availableCameraIds.iterator().next();
} else {
- throw new CameraInfoUnavailableException("Unable to find available camera id.");
+ return null;
}
}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ForwardingImageProxy.java b/camera/camera-core/src/main/java/androidx/camera/core/ForwardingImageProxy.java
index 60ac352..76dd526 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ForwardingImageProxy.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ForwardingImageProxy.java
@@ -51,7 +51,7 @@
}
@Override
- public synchronized void close() {
+ public void close() {
mImage.close();
notifyOnImageCloseListeners();
}
@@ -116,8 +116,15 @@
}
/** Notifies the listeners that this image has been closed. */
- protected synchronized void notifyOnImageCloseListeners() {
- for (OnImageCloseListener listener : mOnImageCloseListeners) {
+ protected void notifyOnImageCloseListeners() {
+ Set<OnImageCloseListener> onImageCloseListeners;
+ synchronized (this) {
+ // Make a copy for thread safety. We want to synchronize the access for member variables
+ // but not the actual callbacks to avoid a deadlock between ForwardingImageProxy and
+ // QueuedImageReaderProxy. go/deadlock-in-sharedimagereaderproxy
+ onImageCloseListeners = new HashSet<>(mOnImageCloseListeners);
+ }
+ for (OnImageCloseListener listener : onImageCloseListeners) {
listener.onImageClose(this);
}
}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ForwardingImageReaderListener.java b/camera/camera-core/src/main/java/androidx/camera/core/ForwardingImageReaderListener.java
index 3b92666..566db37 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ForwardingImageReaderListener.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ForwardingImageReaderListener.java
@@ -16,19 +16,17 @@
package androidx.camera.core;
-import android.media.Image;
-import android.media.ImageReader;
-
import androidx.annotation.GuardedBy;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
- * An {@link ImageReader.OnImageAvailableListener} which forks and forwards newly available images
- * to multiple {@link ImageReaderProxy} instances.
+ * An {@link ImageReaderProxy.OnImageAvailableListener} which forks and forwards newly available
+ * images to multiple {@link ImageReaderProxy} instances.
*/
-final class ForwardingImageReaderListener implements ImageReader.OnImageAvailableListener {
+final class ForwardingImageReaderListener implements ImageReaderProxy.OnImageAvailableListener {
@GuardedBy("this")
private final List<QueuedImageReaderProxy> mImageReaders;
@@ -39,26 +37,29 @@
* @return new {@link ForwardingImageReaderListener} instance
*/
ForwardingImageReaderListener(List<QueuedImageReaderProxy> imageReaders) {
- mImageReaders = Collections.unmodifiableList(imageReaders);
+ // Make a copy of the incoming List to avoid ConcurrentAccessException.
+ mImageReaders = Collections.unmodifiableList(new ArrayList<>(imageReaders));
}
@Override
- public synchronized void onImageAvailable(ImageReader imageReader) {
- Image image = imageReader.acquireNextImage();
- ImageProxy imageProxy = new AndroidImageProxy(image);
+ public synchronized void onImageAvailable(ImageReaderProxy imageReaderProxy) {
+ ImageProxy imageProxy = imageReaderProxy.acquireNextImage();
+ if (imageProxy == null) {
+ return;
+ }
ReferenceCountedImageProxy referenceCountedImageProxy =
new ReferenceCountedImageProxy(imageProxy);
- for (QueuedImageReaderProxy imageReaderProxy : mImageReaders) {
- synchronized (imageReaderProxy) {
- if (!imageReaderProxy.isClosed()) {
+ for (QueuedImageReaderProxy queuedImageReaderProxy : mImageReaders) {
+ synchronized (queuedImageReaderProxy) {
+ if (!queuedImageReaderProxy.isClosed()) {
ImageProxy forkedImage = referenceCountedImageProxy.fork();
ForwardingImageProxy imageToEnqueue =
ImageProxyDownsampler.downsample(
forkedImage,
- imageReaderProxy.getWidth(),
- imageReaderProxy.getHeight(),
+ queuedImageReaderProxy.getWidth(),
+ queuedImageReaderProxy.getHeight(),
ImageProxyDownsampler.DownsamplingMethod.AVERAGING);
- imageReaderProxy.enqueueImage(imageToEnqueue);
+ queuedImageReaderProxy.enqueueImage(imageToEnqueue);
}
}
}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysis.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysis.java
index 96a1792..d8d48ac 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysis.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysis.java
@@ -33,6 +33,7 @@
import androidx.camera.core.impl.utils.executor.CameraXExecutors;
import java.util.Map;
+import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
@@ -45,7 +46,6 @@
*
* <p>After the analyzer function returns, the {@link ImageProxy} will be closed and the
* corresponding {@link android.media.Image} is released back to the {@link ImageReader}.
- *
*/
public final class ImageAnalysis extends UseCase {
/**
@@ -56,10 +56,16 @@
@RestrictTo(Scope.LIBRARY_GROUP)
public static final Defaults DEFAULT_CONFIG = new Defaults();
private static final String TAG = "ImageAnalysis";
+ // ImageReader depth for non-blocking mode.
+ private static final int NON_BLOCKING_IMAGE_DEPTH = 4;
+
final AtomicReference<Analyzer> mSubscribedAnalyzer;
final AtomicInteger mRelativeRotation = new AtomicInteger();
- private final Handler mHandler;
+ final Handler mHandler;
private final ImageAnalysisConfig.Builder mUseCaseConfigBuilder;
+ private final ImageAnalysisBlockingCallback mImageAnalysisBlockingCallback;
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ final ImageAnalysisNonBlockingCallback mImageAnalysisNonBlockingCallback;
@Nullable
ImageReaderProxy mImageReader;
@Nullable
@@ -82,6 +88,14 @@
throw new IllegalStateException("No default mHandler specified.");
}
setImageFormat(ImageReaderFormatRecommender.chooseCombo().imageAnalysisFormat());
+ // Init both instead of lazy loading to void synchronization.
+ mImageAnalysisBlockingCallback = new ImageAnalysisBlockingCallback(mSubscribedAnalyzer,
+ mRelativeRotation,
+ mHandler);
+ mImageAnalysisNonBlockingCallback = new ImageAnalysisNonBlockingCallback(
+ mSubscribedAnalyzer, mRelativeRotation,
+ mHandler, config.getBackgroundExecutor(
+ CameraXExecutors.highPriorityExecutor()));
}
/**
@@ -206,6 +220,7 @@
new DeferrableSurface.OnSurfaceDetachedListener() {
@Override
public void onSurfaceDetached() {
+ mImageAnalysisNonBlockingCallback.close();
if (mImageReader != null) {
mImageReader.close();
mImageReader = null;
@@ -257,39 +272,32 @@
mImageReader.close();
}
+ Executor backgroundExecutor = config.getBackgroundExecutor(
+ CameraXExecutors.highPriorityExecutor());
+
+ int imageQueueDepth = config.getImageReaderMode() == ImageReaderMode.ACQUIRE_NEXT_IMAGE
+ ? config.getImageQueueDepth() : NON_BLOCKING_IMAGE_DEPTH;
+
mImageReader =
ImageReaderProxys.createCompatibleReader(
cameraId,
resolution.getWidth(),
resolution.getHeight(),
getImageFormat(),
- config.getImageQueueDepth(),
- mHandler);
+ imageQueueDepth,
+ backgroundExecutor);
tryUpdateRelativeRotation(cameraId);
- mImageReader.setOnImageAvailableListener(
- new ImageReaderProxy.OnImageAvailableListener() {
- @Override
- public void onImageAvailable(ImageReaderProxy imageReader) {
- Analyzer analyzer = mSubscribedAnalyzer.get();
- try (ImageProxy image =
- config
- .getImageReaderMode(config.getImageReaderMode())
- .equals(ImageReaderMode.ACQUIRE_NEXT_IMAGE)
- ? imageReader.acquireNextImage()
- : imageReader.acquireLatestImage()) {
- // Do not analyze if unable to acquire an ImageProxy
- if (image == null) {
- return;
- }
- if (analyzer != null) {
- analyzer.analyze(image, mRelativeRotation.get());
- }
- }
- }
- },
- mHandler);
+ ImageReaderProxy.OnImageAvailableListener onImageAvailableListener;
+
+ if (config.getImageReaderMode() == ImageReaderMode.ACQUIRE_NEXT_IMAGE) {
+ onImageAvailableListener = mImageAnalysisBlockingCallback;
+ } else {
+ onImageAvailableListener = mImageAnalysisNonBlockingCallback;
+ mImageAnalysisNonBlockingCallback.open();
+ }
+ mImageReader.setOnImageAvailableListener(onImageAvailableListener, backgroundExecutor);
SessionConfig.Builder sessionConfigBuilder = SessionConfig.Builder.createFrom(config);
@@ -329,12 +337,12 @@
ACQUIRE_NEXT_IMAGE,
}
- /** Interface for analyzing images.
+ /**
+ * Interface for analyzing images.
*
* <p>Implement Analyzer and pass it to {@link ImageAnalysis#setAnalyzer(Analyzer)} to receive
* images and perform custom processing by implementing the
* {@link ImageAnalysis.Analyzer#analyze(ImageProxy, int)} function.
- *
*/
public interface Analyzer {
/**
@@ -359,7 +367,6 @@
* @param rotationDegrees The rotation which if applied to the image would make it match
* the current target rotation of {@link ImageAnalysis}, expressed in
* degrees in the range {@code [0..360)}.
- *
*/
void analyze(ImageProxy image, int rotationDegrees);
}
@@ -375,7 +382,7 @@
@RestrictTo(Scope.LIBRARY_GROUP)
public static final class Defaults implements ConfigProvider<ImageAnalysisConfig> {
private static final ImageReaderMode DEFAULT_IMAGE_READER_MODE =
- ImageReaderMode.ACQUIRE_NEXT_IMAGE;
+ ImageReaderMode.ACQUIRE_LATEST_IMAGE;
private static final Handler DEFAULT_HANDLER = new Handler(Looper.getMainLooper());
private static final int DEFAULT_IMAGE_QUEUE_DEPTH = 6;
private static final Size DEFAULT_TARGET_RESOLUTION = new Size(640, 480);
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysisBlockingCallback.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysisBlockingCallback.java
new file mode 100644
index 0000000..ce59f70
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysisBlockingCallback.java
@@ -0,0 +1,69 @@
+/*
+ * 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.camera.core;
+
+import android.os.Handler;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * OnImageAvailableListener with blocking behavior. It never drops image without analyzing it.
+ *
+ * <p> Used with {@link ImageAnalysis}.
+ */
+final class ImageAnalysisBlockingCallback implements ImageReaderProxy.OnImageAvailableListener {
+
+ private static final String TAG = "BlockingCallback";
+
+ final AtomicReference<ImageAnalysis.Analyzer> mSubscribedAnalyzer;
+ final AtomicInteger mRelativeRotation;
+
+ private final Handler mUserHandler;
+
+ ImageAnalysisBlockingCallback(AtomicReference<ImageAnalysis.Analyzer> subscribedAnalyzer,
+ AtomicInteger relativeRotation, Handler userHandler) {
+ mSubscribedAnalyzer = subscribedAnalyzer;
+ mRelativeRotation = relativeRotation;
+ mUserHandler = userHandler;
+ }
+
+ @Override
+ public void onImageAvailable(ImageReaderProxy imageReaderProxy) {
+ ImageProxy image = imageReaderProxy.acquireNextImage();
+ if (image == null) {
+ return;
+ }
+ try {
+ mUserHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ ImageAnalysis.Analyzer analyzer = mSubscribedAnalyzer.get();
+ if (analyzer != null) {
+ analyzer.analyze(image, mRelativeRotation.get());
+ }
+ } finally {
+ image.close();
+ }
+ }
+ });
+ } catch (RuntimeException e) {
+ image.close();
+ }
+ }
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysisConfig.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysisConfig.java
index d7c529b..b834cc7 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysisConfig.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysisConfig.java
@@ -573,7 +573,8 @@
}
/**
- * Sets the number of images available to the camera pipeline.
+ * Sets the number of images available to the camera pipeline for
+ * {@link ImageReaderMode#ACQUIRE_NEXT_IMAGE} mode.
*
* <p>The image queue depth is the number of images available to the camera to fill with
* data. This includes the image currently being analyzed by {@link
@@ -591,6 +592,10 @@
* single frame period for the current frame rate, on average, to avoid stalling the camera
* pipeline.
*
+ * <p> The value only applys to {@link ImageReaderMode#ACQUIRE_NEXT_IMAGE} mode.
+ * For {@link ImageReaderMode#ACQUIRE_LATEST_IMAGE} the value is overridden by default
+ * value.
+ *
* @param depth The total number of images available to the camera.
* @return The current Builder.
*/
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysisNonBlockingCallback.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysisNonBlockingCallback.java
new file mode 100644
index 0000000..447255a
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysisNonBlockingCallback.java
@@ -0,0 +1,177 @@
+/*
+ * 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.camera.core;
+
+import android.os.Handler;
+import android.util.Log;
+
+import androidx.annotation.GuardedBy;
+import androidx.annotation.NonNull;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * OnImageAvailableListener with non-blocking behavior. Analyzes images in a non-blocking way by
+ * dropping images when analyzer is busy.
+ *
+ * <p> Used with {@link ImageAnalysis}.
+ */
+final class ImageAnalysisNonBlockingCallback implements ImageReaderProxy.OnImageAvailableListener {
+
+ private static final String TAG = "NonBlockingCallback";
+
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ final AtomicReference<ImageAnalysis.Analyzer> mSubscribedAnalyzer;
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ final AtomicInteger mRelativeRotation;
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ final Executor mBackgroundExecutor;
+ private final Handler mUserHandler;
+
+ // The cached image when analyzer is busy. Image removed from cache must be closed by 1) closing
+ // it directly or 2) re-posting it to close it eventually.
+ @GuardedBy("this")
+ private ImageProxy mCachedImage;
+
+ private AtomicBoolean mIsClosed;
+
+ // Timestamp of the last image posted to user callback thread.
+ private final AtomicLong mPostedImageTimestamp;
+ // Timestamp of the last image finished being processed by user callback thread.
+ private final AtomicLong mFinishedImageTimestamp;
+
+ ImageAnalysisNonBlockingCallback(AtomicReference<ImageAnalysis.Analyzer> subscribedAnalyzer,
+ AtomicInteger relativeRotation, Handler userHandler, Executor executor) {
+ mSubscribedAnalyzer = subscribedAnalyzer;
+ mRelativeRotation = relativeRotation;
+ mUserHandler = userHandler;
+ mBackgroundExecutor = executor;
+ mPostedImageTimestamp = new AtomicLong();
+ mFinishedImageTimestamp = new AtomicLong();
+ mIsClosed = new AtomicBoolean();
+ open();
+ }
+
+ @Override
+ public void onImageAvailable(ImageReaderProxy imageReaderProxy) {
+ ImageProxy imageProxy = imageReaderProxy.acquireLatestImage();
+ if (imageProxy == null) {
+ return;
+ }
+ analyze(imageProxy);
+ }
+
+ /**
+ * Initialize the callback.
+ */
+ synchronized void open() {
+ mCachedImage = null;
+ mPostedImageTimestamp.set(-1);
+ mFinishedImageTimestamp.set(mPostedImageTimestamp.get());
+ mIsClosed.set(false);
+ }
+
+ /**
+ * Closes the callback so that it will stop posting to analyzer.
+ */
+ synchronized void close() {
+ mIsClosed.set(true);
+ if (mCachedImage != null) {
+ mCachedImage.close();
+ mCachedImage = null;
+ }
+ }
+
+ /**
+ * Removes cached image from cache and analyze it.
+ */
+ synchronized void analyzeCachedImage() {
+ if (mCachedImage != null) {
+ ImageProxy cachedImage = mCachedImage;
+ mCachedImage = null;
+ analyze(cachedImage);
+ }
+ }
+
+ /**
+ * This method guarantees closing the image by either 1) closing the image in the current
+ * thread, 2) caching it for later or 3) posting it to user Thread to close it.
+ *
+ * @param imageProxy the incoming image frame.
+ */
+ private synchronized void analyze(@NonNull ImageProxy imageProxy) {
+ if (mIsClosed.get()) {
+ return;
+ }
+ long postedImageTimestamp = mPostedImageTimestamp.get();
+ long finishedImageTimestamp = mFinishedImageTimestamp.get();
+
+ if (imageProxy.getTimestamp() <= postedImageTimestamp) {
+ // Discard image that is in wrong order. Reposted cached image can be in this state.
+ imageProxy.close();
+ return;
+ }
+
+ if (postedImageTimestamp > finishedImageTimestamp) {
+ // If analyzer is busy, cache the new image.
+ if (mCachedImage != null) {
+ mCachedImage.close();
+ }
+ mCachedImage = imageProxy;
+ return;
+ }
+
+ mPostedImageTimestamp.set(imageProxy.getTimestamp());
+ try {
+ mUserHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ ImageAnalysis.Analyzer analyzer = mSubscribedAnalyzer.get();
+ if (analyzer != null) {
+ analyzer.analyze(imageProxy, mRelativeRotation.get());
+ }
+ } finally {
+ finishImage(imageProxy);
+ mBackgroundExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ analyzeCachedImage();
+ }
+ });
+ }
+ }
+ });
+ } catch (RuntimeException e) {
+ // Unblock if fails to post to user thread.
+ Log.e(TAG, "Error calling user callback", e);
+ finishImage(imageProxy);
+ }
+ }
+
+ synchronized void finishImage(ImageProxy imageProxy) {
+ if (mIsClosed.get()) {
+ return;
+ }
+ mFinishedImageTimestamp.set(imageProxy.getTimestamp());
+ imageProxy.close();
+ }
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageReaderProxy.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageReaderProxy.java
index b98161e..0cff10d 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ImageReaderProxy.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageReaderProxy.java
@@ -20,10 +20,13 @@
import android.os.Handler;
import android.view.Surface;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.annotation.RestrictTo.Scope;
+import java.util.concurrent.Executor;
+
/**
* An image reader proxy which has an analogous interface as {@link ImageReader}.
*
@@ -98,10 +101,20 @@
* <p>@see {@link ImageReader#setOnImageAvailableListener}.
*/
void setOnImageAvailableListener(
- @Nullable ImageReaderProxy.OnImageAvailableListener listener,
+ @NonNull ImageReaderProxy.OnImageAvailableListener listener,
@Nullable Handler handler);
/**
+ * Sets the on-image-available listener.
+ *
+ * @param listener The listener that will be run.
+ * @param executor The executor on which the listener should be invoked.
+ */
+ void setOnImageAvailableListener(
+ @NonNull ImageReaderProxy.OnImageAvailableListener listener,
+ @NonNull Executor executor);
+
+ /**
* A listener for newly available images.
*
* @hide
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageReaderProxys.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageReaderProxys.java
index 6691a67..5d15561 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ImageReaderProxys.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageReaderProxys.java
@@ -18,7 +18,6 @@
import android.graphics.ImageFormat;
import android.media.ImageReader;
-import android.os.Handler;
import android.util.Log;
import android.util.Size;
@@ -29,6 +28,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.Executor;
/**
* Different implementations of {@link ImageReaderProxy}.
@@ -42,7 +42,7 @@
private static final int SHARED_MAX_IMAGES = 8;
static final List<QueuedImageReaderProxy> sSharedImageReaderProxys = new ArrayList<>();
private static Set<DeviceProperties> sSharedReaderWhitelist;
- private static ImageReader sSharedImageReader;
+ private static ImageReaderProxy sSharedImageReader;
private ImageReaderProxys() {
}
@@ -55,15 +55,16 @@
* @param height of the reader
* @param format of the reader
* @param maxImages of the reader
- * @param handler for on-image-available callbacks
+ * @param executor for on-image-available callbacks
* @return new {@link ImageReaderProxy} instance
*/
static ImageReaderProxy createCompatibleReader(
- String cameraId, int width, int height, int format, int maxImages, Handler handler) {
+ String cameraId, int width, int height, int format, int maxImages,
+ Executor executor) {
if (inSharedReaderWhitelist(DeviceProperties.create())) {
- return createSharedReader(cameraId, width, height, format, maxImages, handler);
+ return createSharedReader(cameraId, width, height, format, maxImages, executor);
} else {
- return createIsolatedReader(width, height, format, maxImages, handler);
+ return createIsolatedReader(width, height, format, maxImages);
}
}
@@ -74,11 +75,10 @@
* @param height of the reader
* @param format of the reader
* @param maxImages of the reader
- * @param handler for on-image-available callbacks
* @return new {@link ImageReaderProxy} instance
*/
public static ImageReaderProxy createIsolatedReader(
- int width, int height, int format, int maxImages, Handler handler) {
+ int width, int height, int format, int maxImages) {
ImageReader imageReader = ImageReader.newInstance(width, height, format, maxImages);
return new AndroidImageReaderProxy(imageReader);
}
@@ -91,21 +91,22 @@
* @param height of the reader
* @param format of the reader
* @param maxImages of the reader
- * @param handler for on-image-available callbacks
+ * @param executor for on-image-available callbacks
* @return new {@link ImageReaderProxy} instance
*/
public static ImageReaderProxy createSharedReader(
- String cameraId, int width, int height, int format, int maxImages, Handler handler) {
+ String cameraId, int width, int height, int format, int maxImages,
+ Executor executor) {
if (sSharedImageReader == null) {
Size resolution =
CameraX.getSurfaceManager().getMaxOutputSize(cameraId, SHARED_IMAGE_FORMAT);
Log.d(TAG, "Resolution of base ImageReader: " + resolution);
sSharedImageReader =
- ImageReader.newInstance(
+ new AndroidImageReaderProxy(ImageReader.newInstance(
resolution.getWidth(),
resolution.getHeight(),
SHARED_IMAGE_FORMAT,
- SHARED_MAX_IMAGES);
+ SHARED_MAX_IMAGES));
}
Log.d(TAG, "Resolution of forked ImageReader: " + new Size(width, height));
QueuedImageReaderProxy imageReaderProxy =
@@ -113,7 +114,7 @@
width, height, format, maxImages, sSharedImageReader.getSurface());
sSharedImageReaderProxys.add(imageReaderProxy);
sSharedImageReader.setOnImageAvailableListener(
- new ForwardingImageReaderListener(sSharedImageReaderProxys), handler);
+ new ForwardingImageReaderListener(sSharedImageReaderProxys), executor);
imageReaderProxy.addOnReaderCloseListener(
new QueuedImageReaderProxy.OnReaderCloseListener() {
@Override
@@ -151,7 +152,6 @@
static void clearSharedReaders() {
sSharedImageReaderProxys.clear();
- sSharedImageReader.setOnImageAvailableListener(null, null);
sSharedImageReader.close();
sSharedImageReader = null;
}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/MetadataImageReader.java b/camera/camera-core/src/main/java/androidx/camera/core/MetadataImageReader.java
index 184d08a9..4c59753 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/MetadataImageReader.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/MetadataImageReader.java
@@ -25,10 +25,12 @@
import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.camera.core.impl.utils.executor.CameraXExecutors;
import androidx.core.util.Preconditions;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.Executor;
/**
* An {@link ImageReaderProxy} which matches the incoming {@link android.media.Image} with its
@@ -76,7 +78,7 @@
@GuardedBy("mLock")
@Nullable
- private Handler mHandler;
+ private Executor mExecutor;
/** ImageInfos haven't been matched with Image. */
@GuardedBy("mLock")
@@ -111,7 +113,7 @@
mImageReaderProxy = new AndroidImageReaderProxy(
ImageReader.newInstance(width, height, format, maxImages));
- init(handler);
+ init(CameraXExecutors.newHandlerExecutor(handler));
}
/**
@@ -125,12 +127,12 @@
MetadataImageReader(ImageReaderProxy imageReaderProxy, @Nullable Handler handler) {
mImageReaderProxy = imageReaderProxy;
- init(handler);
+ init(CameraXExecutors.newHandlerExecutor(handler));
}
- private void init(Handler handler) {
- mHandler = handler;
- mImageReaderProxy.setOnImageAvailableListener(mTransformedListener, handler);
+ private void init(Executor executor) {
+ mExecutor = executor;
+ mImageReaderProxy.setOnImageAvailableListener(mTransformedListener, executor);
mImageProxiesIndex = 0;
mMatchedImageProxies = new ArrayList<>(getMaxImages());
}
@@ -241,12 +243,18 @@
@Override
public void setOnImageAvailableListener(
- @Nullable final ImageReaderProxy.OnImageAvailableListener listener,
+ @NonNull final ImageReaderProxy.OnImageAvailableListener listener,
@Nullable Handler handler) {
+ setOnImageAvailableListener(listener, CameraXExecutors.newHandlerExecutor(handler));
+ }
+
+ @Override
+ public void setOnImageAvailableListener(@NonNull OnImageAvailableListener listener,
+ @NonNull Executor executor) {
synchronized (mLock) {
mListener = listener;
- mHandler = handler;
- mImageReaderProxy.setOnImageAvailableListener(mTransformedListener, handler);
+ mExecutor = executor;
+ mImageReaderProxy.setOnImageAvailableListener(mTransformedListener, executor);
}
}
@@ -263,8 +271,8 @@
image.addOnImageCloseListener(this);
mMatchedImageProxies.add(image);
if (mListener != null) {
- if (mHandler != null) {
- mHandler.post(
+ if (mExecutor != null) {
+ mExecutor.execute(
new Runnable() {
@Override
public void run() {
@@ -295,11 +303,6 @@
}
}
- @Nullable
- Handler getHandler() {
- return mHandler;
- }
-
// Return the necessary CameraCaptureCallback, which needs to register to capture session.
CameraCaptureCallback getCameraCaptureCallback() {
return mCameraCaptureCallback;
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ProcessingImageReader.java b/camera/camera-core/src/main/java/androidx/camera/core/ProcessingImageReader.java
index c7e2935..966c0f3 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ProcessingImageReader.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ProcessingImageReader.java
@@ -16,7 +16,6 @@
package androidx.camera.core;
-import android.media.Image;
import android.media.ImageReader;
import android.os.Handler;
import android.util.Log;
@@ -34,6 +33,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.Executor;
/**
* An {@link ImageReaderProxy} which takes one or more {@link android.media.Image}, processes it,
@@ -59,13 +59,13 @@
};
// Callback when Image is ready from OutputImageReader.
- private ImageReader.OnImageAvailableListener mImageProcessedListener =
- new ImageReader.OnImageAvailableListener() {
+ private ImageReaderProxy.OnImageAvailableListener mImageProcessedListener =
+ new ImageReaderProxy.OnImageAvailableListener() {
@Override
- public void onImageAvailable(ImageReader reader) {
+ public void onImageAvailable(ImageReaderProxy reader) {
// Callback the output OnImageAvailableListener.
- if (mHandler != null) {
- mHandler.post(
+ if (mExecutor != null) {
+ mExecutor.execute(
new Runnable() {
@Override
public void run() {
@@ -102,7 +102,7 @@
private final ImageReaderProxy mInputImageReader;
@GuardedBy("mLock")
- private final ImageReader mOutputImageReader;
+ private final ImageReaderProxy mOutputImageReader;
@GuardedBy("mLock")
@Nullable
@@ -110,7 +110,7 @@
@GuardedBy("mLock")
@Nullable
- Handler mHandler;
+ Executor mExecutor;
@NonNull
CaptureProcessor mCaptureProcessor;
@@ -127,10 +127,10 @@
* @param height Height of the ImageReader
* @param format Image format
* @param maxImages Maximum Image number the ImageReader can hold. The capacity should
- * be greater than the captureBundle size in order to hold all the
+ * be greater than the captureBundle size in order to hold all the
* Images needed with this processing.
* @param handler Handler for executing
- * {@link ImageReaderProxy.OnImageAvailableListener}
+ * {@link ImageReaderProxy.OnImageAvailableListener}
* @param captureBundle The {@link CaptureBundle} includes the processing information
* @param captureProcessor The {@link CaptureProcessor} to be invoked when the Images are ready
*/
@@ -143,9 +143,10 @@
format,
maxImages,
handler);
- mOutputImageReader = ImageReader.newInstance(width, height, format, maxImages);
+ mOutputImageReader = new AndroidImageReaderProxy(
+ ImageReader.newInstance(width, height, format, maxImages));
- init(handler, captureBundle, captureProcessor);
+ init(CameraXExecutors.newHandlerExecutor(handler), captureBundle, captureProcessor);
}
ProcessingImageReader(ImageReaderProxy imageReader, @Nullable Handler handler,
@@ -156,17 +157,19 @@
"MetadataImageReader is smaller than CaptureBundle.");
}
mInputImageReader = imageReader;
- mOutputImageReader = ImageReader.newInstance(imageReader.getWidth(),
- imageReader.getHeight(), imageReader.getImageFormat(), imageReader.getMaxImages());
+ mOutputImageReader = new AndroidImageReaderProxy(
+ ImageReader.newInstance(imageReader.getWidth(),
+ imageReader.getHeight(), imageReader.getImageFormat(),
+ imageReader.getMaxImages()));
- init(handler, captureBundle, captureProcessor);
+ init(CameraXExecutors.newHandlerExecutor(handler), captureBundle, captureProcessor);
}
- private void init(@Nullable Handler handler, @NonNull CaptureBundle captureBundle,
+ private void init(@NonNull Executor executor, @NonNull CaptureBundle captureBundle,
@NonNull CaptureProcessor captureProcessor) {
- mHandler = handler;
- mInputImageReader.setOnImageAvailableListener(mTransformedListener, handler);
- mOutputImageReader.setOnImageAvailableListener(mImageProcessedListener, handler);
+ mExecutor = executor;
+ mInputImageReader.setOnImageAvailableListener(mTransformedListener, executor);
+ mOutputImageReader.setOnImageAvailableListener(mImageProcessedListener, executor);
mCaptureProcessor = captureProcessor;
mCaptureProcessor.onOutputSurface(mOutputImageReader.getSurface(), getImageFormat());
mCaptureProcessor.onResolutionUpdate(
@@ -179,12 +182,7 @@
@Nullable
public ImageProxy acquireLatestImage() {
synchronized (mLock) {
- Image image = mOutputImageReader.acquireLatestImage();
- if (image == null) {
- return null;
- }
-
- return new AndroidImageProxy(image);
+ return mOutputImageReader.acquireLatestImage();
}
}
@@ -192,12 +190,7 @@
@Nullable
public ImageProxy acquireNextImage() {
synchronized (mLock) {
- Image image = mOutputImageReader.acquireNextImage();
- if (image == null) {
- return null;
- }
-
- return new AndroidImageProxy(image);
+ return mOutputImageReader.acquireNextImage();
}
}
@@ -252,13 +245,20 @@
@Override
public void setOnImageAvailableListener(
- @Nullable final ImageReaderProxy.OnImageAvailableListener listener,
+ @NonNull final ImageReaderProxy.OnImageAvailableListener listener,
@Nullable Handler handler) {
+ setOnImageAvailableListener(listener, CameraXExecutors.newHandlerExecutor(handler));
+ }
+
+ @Override
+ public void setOnImageAvailableListener(@NonNull OnImageAvailableListener listener,
+ @NonNull Executor executor) {
+ // TODO(b/115747543) support callback on executor
synchronized (mLock) {
mListener = listener;
- mHandler = handler;
- mInputImageReader.setOnImageAvailableListener(mTransformedListener, handler);
- mOutputImageReader.setOnImageAvailableListener(mImageProcessedListener, handler);
+ mExecutor = executor;
+ mInputImageReader.setOnImageAvailableListener(mTransformedListener, executor);
+ mOutputImageReader.setOnImageAvailableListener(mImageProcessedListener, executor);
}
}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/QueuedImageReaderProxy.java b/camera/camera-core/src/main/java/androidx/camera/core/QueuedImageReaderProxy.java
index ac42f16..cbcc54d 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/QueuedImageReaderProxy.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/QueuedImageReaderProxy.java
@@ -20,12 +20,15 @@
import android.view.Surface;
import androidx.annotation.GuardedBy;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.camera.core.impl.utils.executor.CameraXExecutors;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.Executor;
/**
* An {@link ImageReaderProxy} which maintains a queue of recently available images.
@@ -63,7 +66,7 @@
private ImageReaderProxy.OnImageAvailableListener mOnImageAvailableListener;
@GuardedBy("this")
@Nullable
- private Handler mOnImageAvailableHandler;
+ private Executor mOnImageAvailableExecutor;
@GuardedBy("this")
private boolean mClosed;
@@ -151,9 +154,9 @@
if (mImages.size() < mMaxImages) {
mImages.add(image);
image.addOnImageCloseListener(this);
- if (mOnImageAvailableListener != null && mOnImageAvailableHandler != null) {
+ if (mOnImageAvailableListener != null && mOnImageAvailableExecutor != null) {
final OnImageAvailableListener listener = mOnImageAvailableListener;
- mOnImageAvailableHandler.post(
+ mOnImageAvailableExecutor.execute(
new Runnable() {
@Override
public void run() {
@@ -171,7 +174,8 @@
@Override
public synchronized void close() {
if (!mClosed) {
- setOnImageAvailableListener(null, null);
+ this.mOnImageAvailableExecutor = null;
+ this.mOnImageAvailableListener = null;
// We need to copy into a different list, because closing an image triggers the on-close
// listener which in turn modifies the original list.
List<ImageProxy> imagesToClose = new ArrayList<>(mImages);
@@ -216,11 +220,20 @@
@Override
public synchronized void setOnImageAvailableListener(
- @Nullable OnImageAvailableListener onImageAvailableListener,
+ @NonNull OnImageAvailableListener onImageAvailableListener,
@Nullable Handler onImageAvailableHandler) {
+ setOnImageAvailableListener(onImageAvailableListener,
+ onImageAvailableHandler == null ? null :
+ CameraXExecutors.newHandlerExecutor(onImageAvailableHandler));
+ }
+
+ @Override
+ public synchronized void setOnImageAvailableListener(
+ @NonNull OnImageAvailableListener onImageAvailableListener,
+ @NonNull Executor executor) {
throwExceptionIfClosed();
mOnImageAvailableListener = onImageAvailableListener;
- mOnImageAvailableHandler = onImageAvailableHandler;
+ mOnImageAvailableExecutor = executor;
}
@Override
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/CameraIdFilterSetTest.java b/camera/camera-core/src/test/java/androidx/camera/core/CameraIdFilterSetTest.java
index 5d799db..40ffaad 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/CameraIdFilterSetTest.java
+++ b/camera/camera-core/src/test/java/androidx/camera/core/CameraIdFilterSetTest.java
@@ -18,17 +18,21 @@
import static com.google.common.truth.Truth.assertThat;
+import android.os.Build;
+
import androidx.camera.testing.fakes.FakeCameraIdFilter;
import androidx.test.filters.SmallTest;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
import org.robolectric.annotation.internal.DoNotInstrument;
@SmallTest
@RunWith(RobolectricTestRunner.class)
@DoNotInstrument
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
public class CameraIdFilterSetTest {
private CameraIdFilterSet mCameraIdFilterSet = new CameraIdFilterSet();
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/ImageAnalysisTest.java b/camera/camera-core/src/test/java/androidx/camera/core/ImageAnalysisTest.java
new file mode 100644
index 0000000..2e22966
--- /dev/null
+++ b/camera/camera-core/src/test/java/androidx/camera/core/ImageAnalysisTest.java
@@ -0,0 +1,176 @@
+/*
+ * 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.camera.core;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.media.Image;
+import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.util.Size;
+
+import androidx.camera.core.impl.utils.executor.CameraXExecutors;
+import androidx.test.filters.MediumTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.annotation.internal.DoNotInstrument;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.ShadowLooper;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+/**
+ * Unit test for {@link ImageAnalysis}.
+ */
+@MediumTest
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP, shadows = {ShadowCameraX.class,
+ ShadowImageReader.class})
+public class ImageAnalysisTest {
+
+ private static final Size DEFAULT_RESOLUTION = new Size(640, 480);
+ private static final int QUEUE_DEPTH = 8;
+ private static final Image MOCK_IMAGE_1 = createMockImage(1);
+ private static final Image MOCK_IMAGE_2 = createMockImage(2);
+ private static final Image MOCK_IMAGE_3 = createMockImage(3);
+
+ private Handler mCallbackHandler;
+ private Handler mBackgroundHandler;
+ private Executor mBackgroundExecutor;
+ private List<Image> mImagesReceived;
+
+ @Before
+ public void setUp() {
+ HandlerThread callbackThread = new HandlerThread("Callback");
+ callbackThread.start();
+ mCallbackHandler = new Handler(callbackThread.getLooper());
+
+ HandlerThread backgroundThread = new HandlerThread("Background");
+ backgroundThread.start();
+ mBackgroundHandler = new Handler(backgroundThread.getLooper());
+ mBackgroundExecutor = CameraXExecutors.newHandlerExecutor(mBackgroundHandler);
+
+ mImagesReceived = new ArrayList<>();
+
+ ShadowImageReader.clear();
+ }
+
+ @Test
+ public void acquireLatestMode_doesNotBlock() {
+ // Arrange.
+ setUpImageAnalysisWithMode(ImageAnalysis.ImageReaderMode.ACQUIRE_LATEST_IMAGE);
+
+ // Act.
+ // Receive images from camera feed.
+ ShadowImageReader.triggerCallbackWithImage(MOCK_IMAGE_1);
+ flushHandler(mBackgroundHandler);
+ ShadowImageReader.triggerCallbackWithImage(MOCK_IMAGE_2);
+ flushHandler(mBackgroundHandler);
+ ShadowImageReader.triggerCallbackWithImage(MOCK_IMAGE_3);
+ flushHandler(mBackgroundHandler);
+
+ // Assert.
+ // No image is received because callback handler is blocked.
+ assertThat(mImagesReceived).isEmpty();
+
+ // Flush callback handler and image1 is received.
+ flushHandler(mCallbackHandler);
+ assertThat(mImagesReceived).containsExactly(MOCK_IMAGE_1);
+
+ // Flush both handlers and the previous cached image3 is received (image2 was dropped). The
+ // code alternates the 2 threads so they have to be both flushed to proceed.
+ flushHandler(mBackgroundHandler);
+ flushHandler(mCallbackHandler);
+ assertThat(mImagesReceived).containsExactly(MOCK_IMAGE_1, MOCK_IMAGE_3);
+
+ // Flush both handlers and no more frame.
+ flushHandler(mBackgroundHandler);
+ flushHandler(mCallbackHandler);
+ assertThat(mImagesReceived).containsExactly(MOCK_IMAGE_1, MOCK_IMAGE_3);
+ }
+
+ @Test
+ public void acquireNextMode_doesNotDropFrames() {
+ // Arrange.
+ setUpImageAnalysisWithMode(ImageAnalysis.ImageReaderMode.ACQUIRE_NEXT_IMAGE);
+
+ // Act.
+ // Receive images from camera feed.
+ ShadowImageReader.triggerCallbackWithImage(MOCK_IMAGE_1);
+ flushHandler(mBackgroundHandler);
+ ShadowImageReader.triggerCallbackWithImage(MOCK_IMAGE_2);
+ flushHandler(mBackgroundHandler);
+ ShadowImageReader.triggerCallbackWithImage(MOCK_IMAGE_3);
+ flushHandler(mBackgroundHandler);
+
+ // Assert.
+ // No image is received because callback handler is blocked.
+ assertThat(mImagesReceived).isEmpty();
+
+ // Flush callback handler and 3 frames received.
+ flushHandler(mCallbackHandler);
+ assertThat(mImagesReceived).containsExactly(MOCK_IMAGE_1, MOCK_IMAGE_2, MOCK_IMAGE_3);
+ }
+
+ private void setUpImageAnalysisWithMode(ImageAnalysis.ImageReaderMode imageReaderMode) {
+ ImageAnalysis imageAnalysis = new ImageAnalysis(new ImageAnalysisConfig.Builder()
+ .setCallbackHandler(mCallbackHandler)
+ .setBackgroundExecutor(mBackgroundExecutor)
+ .setImageQueueDepth(QUEUE_DEPTH)
+ .setImageReaderMode(imageReaderMode)
+ .build());
+
+ imageAnalysis.setAnalyzer(new ImageAnalysis.Analyzer() {
+ @Override
+ public void analyze(ImageProxy image, int rotationDegrees) {
+ mImagesReceived.add(image.getImage());
+ }
+ });
+
+ Map<String, Size> suggestedResolutionMap = new HashMap<>();
+ suggestedResolutionMap.put(ShadowCameraX.DEFAULT_CAMERA_ID, DEFAULT_RESOLUTION);
+ imageAnalysis.updateSuggestedResolution(suggestedResolutionMap);
+ }
+
+ /**
+ * Flushes a {@link Handler} to run all pending tasks.
+ *
+ * @param handler the {@link Handler} to flush.
+ */
+ private static void flushHandler(Handler handler) {
+ ((ShadowLooper) Shadow.extract(handler.getLooper())).idle();
+ }
+
+ private static Image createMockImage(long timestamp) {
+ Image mockImage = mock(Image.class);
+ when(mockImage.getTimestamp()).thenReturn(timestamp);
+ return mockImage;
+ }
+}
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/ShadowCameraX.java b/camera/camera-core/src/test/java/androidx/camera/core/ShadowCameraX.java
new file mode 100644
index 0000000..fd893b4
--- /dev/null
+++ b/camera/camera-core/src/test/java/androidx/camera/core/ShadowCameraX.java
@@ -0,0 +1,82 @@
+/*
+ * 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.camera.core;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+/**
+ * A Robolectric shadow of {@link CameraX}.
+ */
+@Implements(CameraX.class)
+public class ShadowCameraX {
+ public static final String DEFAULT_CAMERA_ID = "DEFAULT_CAMERA_ID";
+
+ private static final UseCaseConfig<ImageAnalysis> DEFAULT_IMAGE_ANALYSIS_CONFIG =
+ new ImageAnalysisConfig.Builder().setSessionOptionUnpacker(
+ new SessionConfig.OptionUnpacker() {
+ @Override
+ public void unpack(UseCaseConfig<?> config, SessionConfig.Builder builder) {
+ // no op.
+ }
+ }).build();
+
+ private static final CameraInfo DEFAULT_CAMERA_INFO = new CameraInfo() {
+
+ @Override
+ public CameraX.LensFacing getLensFacing() {
+ return CameraX.LensFacing.BACK;
+ }
+
+ @Override
+ public int getSensorRotationDegrees() {
+ return 0;
+ }
+
+ @Override
+ public int getSensorRotationDegrees(int relativeRotation) {
+ return 0;
+ }
+ };
+
+ /**
+ * Shadow of {@link ShadowCameraX#getCameraWithCameraDeviceConfig(CameraDeviceConfig)}.
+ */
+ @Implementation
+ public static String getCameraWithCameraDeviceConfig(CameraDeviceConfig config) {
+ return DEFAULT_CAMERA_ID;
+ }
+
+ /**
+ * Shadow of {@link CameraX#getCameraInfo(String)}.
+ */
+ @Implementation
+ public static CameraInfo getCameraInfo(String cameraId) throws CameraInfoUnavailableException {
+ return DEFAULT_CAMERA_INFO;
+ }
+
+ /**
+ * Shadow of {@link CameraX#getDefaultUseCaseConfig(Class, CameraX.LensFacing)}.
+ */
+ @SuppressWarnings("unchecked")
+ @Implementation
+ public static <C extends UseCaseConfig<?>> C getDefaultUseCaseConfig(
+ Class<C> configType, CameraX.LensFacing lensFacing) {
+ return (C) DEFAULT_IMAGE_ANALYSIS_CONFIG;
+ }
+
+}
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/ShadowImageReader.java b/camera/camera-core/src/test/java/androidx/camera/core/ShadowImageReader.java
new file mode 100644
index 0000000..40b067d
--- /dev/null
+++ b/camera/camera-core/src/test/java/androidx/camera/core/ShadowImageReader.java
@@ -0,0 +1,117 @@
+/*
+ * 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.camera.core;
+
+import android.media.Image;
+import android.media.ImageReader;
+import android.os.Build;
+import android.os.Handler;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.shadow.api.Shadow;
+
+/**
+ * A Robolectric shadow of {@link ImageReader}.
+ */
+@Implements(ImageReader.class)
+public class ShadowImageReader {
+
+ // Image to return when user call acquireLatestImage() or acquireNextImage().
+ private static Image sIncomingImage;
+
+ @Nullable
+ private volatile ImageReader.OnImageAvailableListener mListener;
+
+ private static ImageReader sImageReader;
+ private static ShadowImageReader sShadowImageReader;
+
+ /**
+ * Shadow of {@link ImageReader#newInstance(int, int, int, int)}.
+ */
+ @Implementation
+ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+ public static ImageReader newInstance(int width, int height, int format, int maxImages) {
+ sImageReader = Shadow.newInstanceOf(ImageReader.class);
+ sShadowImageReader = Shadow.extract(sImageReader);
+ return sImageReader;
+ }
+
+ /**
+ * Sets the incoming image and triggers
+ * {@link ImageReader.OnImageAvailableListener#onImageAvailable(ImageReader)}.
+ */
+ public static void triggerCallbackWithImage(Image mockImage) {
+ sIncomingImage = mockImage;
+ sShadowImageReader.getListener().onImageAvailable(sImageReader);
+ }
+
+ /**
+ * Clears incoming images.
+ */
+ public static void clear() {
+ sIncomingImage = null;
+ sImageReader = null;
+ sShadowImageReader = null;
+ }
+
+
+ /**
+ * Shadow of {@link ImageReader#setOnImageAvailableListener}.
+ */
+ @Implementation
+ public void setOnImageAvailableListener(ImageReader.OnImageAvailableListener listener,
+ Handler handler) {
+ this.mListener = listener;
+ }
+
+ /**
+ * Shadow of {@link ImageReader#acquireLatestImage()}.
+ */
+ @Implementation
+ public Image acquireLatestImage() {
+ return popIncomingImage();
+ }
+
+ /**
+ * Shadow of {@link ImageReader#acquireNextImage()}.
+ */
+ @Implementation
+ public Image acquireNextImage() {
+ return popIncomingImage();
+ }
+
+ private Image popIncomingImage() {
+ try {
+ return sIncomingImage;
+ } finally {
+ sIncomingImage = null;
+ }
+ }
+
+ /**
+ * Returns the last OnImageAvailableListener that was passed in call to
+ * setOnImageAvailableListener or null if never called.
+ */
+ @Nullable
+ public ImageReader.OnImageAvailableListener getListener() {
+ return mListener;
+ }
+}
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 e45ab82..1cc603b 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
@@ -24,6 +24,7 @@
import android.Manifest;
import android.content.Context;
+import android.os.Build;
import androidx.annotation.NonNull;
import androidx.camera.core.AppConfig;
@@ -96,6 +97,10 @@
@Before
public void setUp() {
+ // Ignore the tests if SDK is before M since extension implementation is only supported
+ // after M.
+ assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M);
+
Context context = ApplicationProvider.getApplicationContext();
CameraDeviceSurfaceManager surfaceManager = new FakeCameraDeviceSurfaceManager();
ExtendableUseCaseConfigFactory defaultConfigFactory = new ExtendableUseCaseConfigFactory();
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/CameraUtil.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/CameraUtil.java
index b85eb35..b0898c7 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/CameraUtil.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/CameraUtil.java
@@ -22,10 +22,14 @@
import android.hardware.camera2.CameraManager;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.camera.core.CameraDeviceConfig;
import androidx.camera.core.CameraInfoUnavailableException;
import androidx.camera.core.CameraX;
+import androidx.camera.core.LensFacingCameraIdFilter;
+
+import java.util.Set;
/**
* Utility functions for accessing camera related parameters
@@ -44,6 +48,17 @@
}
}
+ @NonNull
+ static Set<String> getCameraIdSetWithLensFacing(CameraX.LensFacing lensFacing)
+ throws CameraInfoUnavailableException {
+ Set<String> availableCameraIds = CameraX.getCameraFactory().getAvailableCameraIds();
+ LensFacingCameraIdFilter lensFacingCameraIdFilter =
+ LensFacingCameraIdFilter.createLensFacingCameraIdFilter(lensFacing);
+ availableCameraIds = lensFacingCameraIdFilter.filter(availableCameraIds);
+
+ return availableCameraIds;
+ }
+
static CameraCharacteristics getCameraCharacteristics(String cameraId) {
Context context = CameraX.getContext();
CameraManager cameraManager = (CameraManager) context.getSystemService(
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/ExtensionsManager.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/ExtensionsManager.java
index 4e1613a..64378b6 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/ExtensionsManager.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/ExtensionsManager.java
@@ -105,22 +105,23 @@
LensFacing lensFacing) {
ImageCaptureConfig.Builder builder = new ImageCaptureConfig.Builder();
builder.setLensFacing(lensFacing);
+ ImageCaptureExtender extender;
switch (effectMode) {
case BOKEH:
- BokehImageCaptureExtender.create(builder);
+ extender = BokehImageCaptureExtender.create(builder);
break;
case HDR:
- HdrImageCaptureExtender.create(builder);
+ extender = HdrImageCaptureExtender.create(builder);
break;
case NIGHT:
- NightImageCaptureExtender.create(builder);
+ extender = NightImageCaptureExtender.create(builder);
break;
case BEAUTY:
- BeautyImageCaptureExtender.create(builder);
+ extender = BeautyImageCaptureExtender.create(builder);
break;
case AUTO:
- AutoImageCaptureExtender.create(builder);
+ extender = AutoImageCaptureExtender.create(builder);
break;
case NORMAL:
return true;
@@ -128,9 +129,7 @@
return false;
}
- String cameraId = CameraUtil.getCameraId(builder.build());
-
- return cameraId != null;
+ return extender.isExtensionAvailable();
}
/**
@@ -163,22 +162,23 @@
LensFacing lensFacing) {
PreviewConfig.Builder builder = new PreviewConfig.Builder();
builder.setLensFacing(lensFacing);
+ PreviewExtender extender;
switch (effectMode) {
case BOKEH:
- BokehPreviewExtender.create(builder);
+ extender = BokehPreviewExtender.create(builder);
break;
case HDR:
- HdrPreviewExtender.create(builder);
+ extender = HdrPreviewExtender.create(builder);
break;
case NIGHT:
- NightPreviewExtender.create(builder);
+ extender = NightPreviewExtender.create(builder);
break;
case BEAUTY:
- BeautyPreviewExtender.create(builder);
+ extender = BeautyPreviewExtender.create(builder);
break;
case AUTO:
- AutoPreviewExtender.create(builder);
+ extender = AutoPreviewExtender.create(builder);
break;
case NORMAL:
return true;
@@ -186,9 +186,7 @@
return false;
}
- String cameraId = CameraUtil.getCameraId(builder.build());
-
- return cameraId != null;
+ return extender.isExtensionAvailable();
}
private ExtensionsManager() {
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/ImageCaptureExtender.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/ImageCaptureExtender.java
index e4d59ec..2d0e31e 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/ImageCaptureExtender.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/ImageCaptureExtender.java
@@ -27,6 +27,7 @@
import androidx.camera.camera2.impl.CameraEventCallbacks;
import androidx.camera.core.CameraIdFilter;
import androidx.camera.core.CameraIdFilterSet;
+import androidx.camera.core.CameraInfoUnavailableException;
import androidx.camera.core.CameraX;
import androidx.camera.core.CaptureBundle;
import androidx.camera.core.CaptureConfig;
@@ -43,6 +44,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
/**
@@ -61,17 +63,6 @@
mBuilder = builder;
mImpl = implementation;
mEffectMode = effectMode;
-
- ExtensionCameraIdFilter extensionCameraIdFilter = new ExtensionCameraIdFilter(mImpl);
- CameraIdFilter cameraIdFilter = mBuilder.build().getCameraIdFilter(null);
- if (cameraIdFilter == null) {
- mBuilder.setCameraIdFilter(extensionCameraIdFilter);
- } else {
- CameraIdFilterSet cameraIdFilterSet = new CameraIdFilterSet();
- cameraIdFilterSet.addCameraIdFilter(cameraIdFilter);
- cameraIdFilterSet.addCameraIdFilter(extensionCameraIdFilter);
- mBuilder.setCameraIdFilter(cameraIdFilterSet);
- }
}
/**
@@ -80,8 +71,18 @@
* @return True if the specific extension function is supported for the camera device.
*/
public boolean isExtensionAvailable() {
- String cameraId = CameraUtil.getCameraId(mBuilder.build());
- return cameraId != null;
+ CameraX.LensFacing lensFacing = mBuilder.build().getLensFacing();
+ Set<String> availableCameraIds = null;
+ try {
+ availableCameraIds = CameraUtil.getCameraIdSetWithLensFacing(lensFacing);
+ } catch (CameraInfoUnavailableException e) {
+ // Returns false if camera info is unavailable.
+ return false;
+ }
+ ExtensionCameraIdFilter extensionCameraIdFilter = new ExtensionCameraIdFilter(mImpl);
+ availableCameraIds = extensionCameraIdFilter.filter(availableCameraIds);
+
+ return !availableCameraIds.isEmpty();
}
/**
@@ -92,6 +93,18 @@
* enabled together.
*/
public void enableExtension() {
+ // Add extension camera id filter to config.
+ ExtensionCameraIdFilter extensionCameraIdFilter = new ExtensionCameraIdFilter(mImpl);
+ CameraIdFilter currentCameraIdFilter = mBuilder.build().getCameraIdFilter(null);
+ if (currentCameraIdFilter == null) {
+ mBuilder.setCameraIdFilter(extensionCameraIdFilter);
+ } else {
+ CameraIdFilterSet cameraIdFilterSet = new CameraIdFilterSet();
+ cameraIdFilterSet.addCameraIdFilter(currentCameraIdFilter);
+ cameraIdFilterSet.addCameraIdFilter(extensionCameraIdFilter);
+ mBuilder.setCameraIdFilter(cameraIdFilterSet);
+ }
+
String cameraId = CameraUtil.getCameraId(mBuilder.build());
if (cameraId == null) {
// If there's no available camera id for the extender to function, just return here
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/PreviewExtender.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/PreviewExtender.java
index 955cbd9..6f9beb4 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/PreviewExtender.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/PreviewExtender.java
@@ -32,6 +32,7 @@
import androidx.camera.core.CameraCaptureResults;
import androidx.camera.core.CameraIdFilter;
import androidx.camera.core.CameraIdFilterSet;
+import androidx.camera.core.CameraInfoUnavailableException;
import androidx.camera.core.CameraX;
import androidx.camera.core.CaptureConfig;
import androidx.camera.core.CaptureStage;
@@ -48,6 +49,7 @@
import androidx.camera.extensions.impl.RequestUpdateProcessorImpl;
import java.util.Collection;
+import java.util.Set;
/**
* Class for using an OEM provided extension on preview.
@@ -65,17 +67,6 @@
mBuilder = builder;
mImpl = implementation;
mEffectMode = effectMode;
-
- ExtensionCameraIdFilter extensionCameraIdFilter = new ExtensionCameraIdFilter(mImpl);
- CameraIdFilter cameraIdFilter = mBuilder.build().getCameraIdFilter(null);
- if (cameraIdFilter == null) {
- mBuilder.setCameraIdFilter(extensionCameraIdFilter);
- } else {
- CameraIdFilterSet cameraIdFilterSet = new CameraIdFilterSet();
- cameraIdFilterSet.addCameraIdFilter(cameraIdFilter);
- cameraIdFilterSet.addCameraIdFilter(extensionCameraIdFilter);
- mBuilder.setCameraIdFilter(cameraIdFilterSet);
- }
}
/**
@@ -85,8 +76,18 @@
* @return True if the specific extension function is supported for the camera device.
*/
public boolean isExtensionAvailable() {
- String cameraId = CameraUtil.getCameraId(mBuilder.build());
- return cameraId != null;
+ CameraX.LensFacing lensFacing = mBuilder.build().getLensFacing();
+ Set<String> availableCameraIds = null;
+ try {
+ availableCameraIds = CameraUtil.getCameraIdSetWithLensFacing(lensFacing);
+ } catch (CameraInfoUnavailableException e) {
+ // Returns false if camera info is unavailable.
+ return false;
+ }
+ ExtensionCameraIdFilter extensionCameraIdFilter = new ExtensionCameraIdFilter(mImpl);
+ availableCameraIds = extensionCameraIdFilter.filter(availableCameraIds);
+
+ return !availableCameraIds.isEmpty();
}
/**
@@ -97,6 +98,18 @@
* extension is not enabled together.
*/
public void enableExtension() {
+ // Add extension camera id filter to config.
+ ExtensionCameraIdFilter extensionCameraIdFilter = new ExtensionCameraIdFilter(mImpl);
+ CameraIdFilter currentCameraIdFilter = mBuilder.build().getCameraIdFilter(null);
+ if (currentCameraIdFilter == null) {
+ mBuilder.setCameraIdFilter(extensionCameraIdFilter);
+ } else {
+ CameraIdFilterSet cameraIdFilterSet = new CameraIdFilterSet();
+ cameraIdFilterSet.addCameraIdFilter(currentCameraIdFilter);
+ cameraIdFilterSet.addCameraIdFilter(extensionCameraIdFilter);
+ mBuilder.setCameraIdFilter(cameraIdFilterSet);
+ }
+
String cameraId = CameraUtil.getCameraId(mBuilder.build());
if (cameraId == null) {
// If there's no available camera id for the extender to function, just return here
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeImageReaderProxy.java b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeImageReaderProxy.java
index 8b6d560..212d783 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeImageReaderProxy.java
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeImageReaderProxy.java
@@ -30,6 +30,7 @@
import java.util.NoSuchElementException;
import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
@@ -43,7 +44,7 @@
private int mImageFormat = ImageFormat.JPEG;
private final int mMaxImages;
private Surface mSurface;
- private Handler mHandler;
+ private Executor mExecutor;
// Queue of all futures for ImageProxys which have not yet been closed.
@SuppressWarnings("WeakerAccess") /* synthetic accessor */
@@ -131,10 +132,16 @@
@Override
public void setOnImageAvailableListener(
- @Nullable final ImageReaderProxy.OnImageAvailableListener listener,
+ @NonNull final ImageReaderProxy.OnImageAvailableListener listener,
@Nullable Handler handler) {
+ setOnImageAvailableListener(mListener, CameraXExecutors.newHandlerExecutor(handler));
+ }
+
+ @Override
+ public void setOnImageAvailableListener(@NonNull OnImageAvailableListener listener,
+ @NonNull Executor executor) {
mListener = listener;
- mHandler = handler;
+ mExecutor = executor;
}
public void setSurface(Surface surface) {
@@ -223,14 +230,14 @@
private void triggerImageAvailableListener() {
if (mListener != null) {
- if (mHandler != null) {
- mHandler.post(
- new Runnable() {
- @Override
- public void run() {
- mListener.onImageAvailable(FakeImageReaderProxy.this);
- }
- });
+ Runnable listenerRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mListener.onImageAvailable(FakeImageReaderProxy.this);
+ }
+ };
+ if (mExecutor != null) {
+ mExecutor.execute(listenerRunnable);
} else {
mListener.onImageAvailable(FakeImageReaderProxy.this);
}
diff --git a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/ToggleButtonTest.java b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/ToggleButtonTest.java
index 1e65e19..8fe00cf 100644
--- a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/ToggleButtonTest.java
+++ b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/ToggleButtonTest.java
@@ -53,10 +53,11 @@
public final class ToggleButtonTest {
private static final int DISMISS_LOCK_SCREEN_CODE = 82;
+ private static final String BASIC_SAMPLE_PACKAGE = "androidx.camera.integration.extensions";
@Rule
public ActivityTestRule<CameraExtensionsActivity> mActivityRule =
- new ActivityTestRule<>(CameraExtensionsActivity.class);
+ new ActivityTestRule<>(CameraExtensionsActivity.class, true, false);
@Rule
public GrantPermissionRule mCameraPermissionRule =
@@ -85,6 +86,11 @@
// Close system dialogs first to avoid interrupt.
ApplicationProvider.getApplicationContext().sendBroadcast(
new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+
+ Intent intent = ApplicationProvider.getApplicationContext().getPackageManager()
+ .getLaunchIntentForPackage(BASIC_SAMPLE_PACKAGE);
+
+ mActivityRule.launchActivity(intent);
}
@After
diff --git a/camera/integration-tests/extensionstestapp/src/main/AndroidManifest.xml b/camera/integration-tests/extensionstestapp/src/main/AndroidManifest.xml
index f7ceba1..4916933 100644
--- a/camera/integration-tests/extensionstestapp/src/main/AndroidManifest.xml
+++ b/camera/integration-tests/extensionstestapp/src/main/AndroidManifest.xml
@@ -20,10 +20,6 @@
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
- <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
- <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
- <uses-permission android:name="android.permission.WRITE_MEDIA_IMAGES" />
- <uses-permission android:name="android.permission.WRITE_MEDIA_VIDEO" />
<uses-feature android:name="android.hardware.camera" />
diff --git a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/MainActivity.kt b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/MainActivity.kt
index 63105dd..6dc6ca7 100644
--- a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/MainActivity.kt
+++ b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/MainActivity.kt
@@ -117,7 +117,7 @@
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
- camViewModel = ViewModelProvider(this, ViewModelProvider.NewInstanceFactory())
+ camViewModel = ViewModelProvider(this)
.get(CamViewModel::class.java)
cameraParams = camViewModel.getCameraParams()
deviceInfo = DeviceInfo()
diff --git a/car/core/api/1.0.0-alpha8.txt b/car/core/api/1.0.0-alpha8.txt
index d3d8be7..bf6878c 100644
--- a/car/core/api/1.0.0-alpha8.txt
+++ b/car/core/api/1.0.0-alpha8.txt
@@ -295,7 +295,7 @@
method public androidx.car.widget.CarMenuItem.Builder setDisplayBehavior(androidx.car.widget.CarMenuItem.DisplayBehavior);
method public androidx.car.widget.CarMenuItem.Builder setEnabled(boolean);
method public androidx.car.widget.CarMenuItem.Builder setIcon(android.graphics.drawable.Drawable);
- method public androidx.car.widget.CarMenuItem.Builder setIcon(android.content.Context, @DrawableRes int);
+ method @Deprecated public androidx.car.widget.CarMenuItem.Builder setIcon(android.content.Context, @DrawableRes int);
method public androidx.car.widget.CarMenuItem.Builder setOnClickListener(androidx.car.widget.CarMenuItem.OnClickListener);
method public androidx.car.widget.CarMenuItem.Builder setStyle(@StyleRes int);
method public androidx.car.widget.CarMenuItem.Builder setTitle(CharSequence);
diff --git a/car/core/api/api_lint.ignore b/car/core/api/api_lint.ignore
index ce9444c..5a9daf1 100644
--- a/car/core/api/api_lint.ignore
+++ b/car/core/api/api_lint.ignore
@@ -1,5 +1,3 @@
// Baseline format: 1.0
KotlinOperator: androidx.car.widget.ListItemProvider#get(int):
Method can be invoked with an indexing operator from Kotlin: `get` (this is usually desirable; just make sure it makes sense for this type of object)
-KotlinOperator: androidx.car.widget.ListItemProvider.ListProvider#get(int):
- Method can be invoked with an indexing operator from Kotlin: `get` (this is usually desirable; just make sure it makes sense for this type of object)
diff --git a/car/core/api/current.txt b/car/core/api/current.txt
index d3d8be7..bf6878c 100644
--- a/car/core/api/current.txt
+++ b/car/core/api/current.txt
@@ -295,7 +295,7 @@
method public androidx.car.widget.CarMenuItem.Builder setDisplayBehavior(androidx.car.widget.CarMenuItem.DisplayBehavior);
method public androidx.car.widget.CarMenuItem.Builder setEnabled(boolean);
method public androidx.car.widget.CarMenuItem.Builder setIcon(android.graphics.drawable.Drawable);
- method public androidx.car.widget.CarMenuItem.Builder setIcon(android.content.Context, @DrawableRes int);
+ method @Deprecated public androidx.car.widget.CarMenuItem.Builder setIcon(android.content.Context, @DrawableRes int);
method public androidx.car.widget.CarMenuItem.Builder setOnClickListener(androidx.car.widget.CarMenuItem.OnClickListener);
method public androidx.car.widget.CarMenuItem.Builder setStyle(@StyleRes int);
method public androidx.car.widget.CarMenuItem.Builder setTitle(CharSequence);
diff --git a/car/core/api/restricted_1.0.0-alpha8.txt b/car/core/api/restricted_1.0.0-alpha8.txt
index c177433..b4d0539 100644
--- a/car/core/api/restricted_1.0.0-alpha8.txt
+++ b/car/core/api/restricted_1.0.0-alpha8.txt
@@ -318,7 +318,7 @@
method public androidx.car.widget.CarMenuItem.Builder setDisplayBehavior(androidx.car.widget.CarMenuItem.DisplayBehavior);
method public androidx.car.widget.CarMenuItem.Builder setEnabled(boolean);
method public androidx.car.widget.CarMenuItem.Builder setIcon(android.graphics.drawable.Drawable);
- method public androidx.car.widget.CarMenuItem.Builder setIcon(android.content.Context, @DrawableRes int);
+ method @Deprecated public androidx.car.widget.CarMenuItem.Builder setIcon(android.content.Context, @DrawableRes int);
method public androidx.car.widget.CarMenuItem.Builder setOnClickListener(androidx.car.widget.CarMenuItem.OnClickListener);
method public androidx.car.widget.CarMenuItem.Builder setStyle(@StyleRes int);
method public androidx.car.widget.CarMenuItem.Builder setTitle(CharSequence);
diff --git a/car/core/api/restricted_current.txt b/car/core/api/restricted_current.txt
index c177433..b4d0539 100644
--- a/car/core/api/restricted_current.txt
+++ b/car/core/api/restricted_current.txt
@@ -318,7 +318,7 @@
method public androidx.car.widget.CarMenuItem.Builder setDisplayBehavior(androidx.car.widget.CarMenuItem.DisplayBehavior);
method public androidx.car.widget.CarMenuItem.Builder setEnabled(boolean);
method public androidx.car.widget.CarMenuItem.Builder setIcon(android.graphics.drawable.Drawable);
- method public androidx.car.widget.CarMenuItem.Builder setIcon(android.content.Context, @DrawableRes int);
+ method @Deprecated public androidx.car.widget.CarMenuItem.Builder setIcon(android.content.Context, @DrawableRes int);
method public androidx.car.widget.CarMenuItem.Builder setOnClickListener(androidx.car.widget.CarMenuItem.OnClickListener);
method public androidx.car.widget.CarMenuItem.Builder setStyle(@StyleRes int);
method public androidx.car.widget.CarMenuItem.Builder setTitle(CharSequence);
diff --git a/car/core/src/androidTest/java/androidx/car/widget/CarToolbarTest.java b/car/core/src/androidTest/java/androidx/car/widget/CarToolbarTest.java
index 2ce3e70..81f524c 100644
--- a/car/core/src/androidTest/java/androidx/car/widget/CarToolbarTest.java
+++ b/car/core/src/androidTest/java/androidx/car/widget/CarToolbarTest.java
@@ -336,7 +336,7 @@
.Builder()
.setTitle(actionItemText)
.setDisplayBehavior(CarMenuItem.DisplayBehavior.ALWAYS) // Action item
- .setIcon(mActivity, android.R.drawable.sym_def_app_icon)
+ .setIcon(mActivity.getDrawable(android.R.drawable.sym_def_app_icon))
.build();
mActivityRule.runOnUiThread(() ->
diff --git a/car/core/src/main/java/androidx/car/widget/CarMenuItem.java b/car/core/src/main/java/androidx/car/widget/CarMenuItem.java
index 42d20b8..400a2e0 100644
--- a/car/core/src/main/java/androidx/car/widget/CarMenuItem.java
+++ b/car/core/src/main/java/androidx/car/widget/CarMenuItem.java
@@ -265,7 +265,10 @@
* @param context Context to load the drawable resource with.
* @param iconResId Resource id of icon of the {@code CarMenuItem}.
* @return This {@code Builder} object to allow call chaining.
+ *
+ * @deprecated Use {@link #setIcon(Drawable)} instead.
*/
+ @Deprecated
@NonNull
public Builder setIcon(@NonNull Context context, @DrawableRes int iconResId) {
mIconDrawable = context.getDrawable(iconResId);
diff --git a/collection/collection/api/api_lint.ignore b/collection/collection/api/api_lint.ignore
index 589b899..c01fd32 100644
--- a/collection/collection/api/api_lint.ignore
+++ b/collection/collection/api/api_lint.ignore
@@ -1,6 +1,4 @@
// Baseline format: 1.0
-KotlinOperator: androidx.collection.ArraySet#contains(Object):
- Method can be invoked as a "in" operator from Kotlin: `contains` (this is usually desirable; just make sure it makes sense for this type of object)
KotlinOperator: androidx.collection.CircularArray#get(int):
Method can be invoked with an indexing operator from Kotlin: `get` (this is usually desirable; just make sure it makes sense for this type of object)
KotlinOperator: androidx.collection.CircularIntArray#get(int):
diff --git a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/FcsCodegenTests.kt b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/FcsCodegenTests.kt
index 76f260b..adc4397 100644
--- a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/FcsCodegenTests.kt
+++ b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/FcsCodegenTests.kt
@@ -116,7 +116,7 @@
}
""",
{ emptyMap<String, String>() },
- "TestCall()", dumpClasses = true
+ "TestCall()"
).then { activity ->
val textView = activity.findViewById<TextView>(100)
assertEquals("12", textView.text)
@@ -1847,6 +1847,180 @@
)
}
+ @Test
+ fun testStableParameters_Various(): Unit = ensureSetup {
+ val output = ArrayList<String>()
+ compose("""
+ @Model
+ class M { var count = 0 }
+ val m = M()
+
+ @Immutable
+ data class ValueHolder(val value: Int)
+
+ var output = ArrayList<String>()
+
+ class NotStable { val value = 10 }
+
+ @Composable
+ fun MemoInt(a: Int) {
+ output.add("MemoInt a=${'$'}a")
+ Button(id=101, text="memo ${'$'}a", onClick={ m.count++ })
+ }
+
+ @Composable
+ fun MemoFloat(a: Float) {
+ output.add("MemoFloat")
+ Button(text="memo ${'$'}a")
+ }
+
+ @Composable
+ fun MemoDouble(a: Double) {
+ output.add("MemoDouble")
+ Button(text="memo ${'$'}a")
+ }
+
+ @Composable
+ fun MemoNotStable(a: NotStable) {
+ output.add("MemoNotStable")
+ Button(text="memo ${'$'}{a.value}")
+ }
+
+ @Composable
+ fun MemoModel(a: ValueHolder) {
+ output.add("MemoModelHolder")
+ Button(text="memo ${'$'}{a.value}")
+ }
+
+ @Composable
+ fun TestSkipping(
+ a: Int,
+ b: Float,
+ c: Double,
+ d: NotStable,
+ e: ValueHolder
+ ) {
+ val am = a + m.count
+ output.add("TestSkipping a=${'$'}a am=${'$'}am")
+ MemoInt(a=am)
+ MemoFloat(a=b)
+ MemoDouble(a=c)
+ MemoNotStable(a=d)
+ MemoModel(a=e)
+ }
+
+ @Composable
+ fun Main(v: ValueHolder) {
+ TestSkipping(a=1, b=1f, c=2.0, d=NotStable(), e=v)
+ }
+ """, {
+ mapOf(
+ "outerOutput: ArrayList<String>" to output
+ )
+ }, """
+ output = outerOutput
+ val v = ValueHolder(0)
+ Main(v)
+ """).then {
+ // Expect that all the methods are called in order
+ assertEquals(
+ "TestSkipping a=1 am=1, MemoInt a=1, MemoFloat, " +
+ "MemoDouble, MemoNotStable, MemoModelHolder",
+ output.joinToString()
+ )
+ output.clear()
+ }.then { activity ->
+ // Expect TestSkipping and MemoNotStable to be called because the test forces an extra compose.
+ assertEquals("TestSkipping a=1 am=1, MemoNotStable", output.joinToString())
+ output.clear()
+
+ // Change the model
+ val button = activity.findViewById(101) as Button
+ button.performClick()
+ }.then {
+ // Expect that only MemoInt (the parameter changed) and MemoNotStable (it has unstable parameters) were
+ // called then expect a second compose which should only MemoNotStable
+ assertEquals(
+ "TestSkipping a=1 am=2, MemoInt a=2, MemoNotStable, " +
+ "TestSkipping a=1 am=2, MemoNotStable",
+ output.joinToString()
+ )
+ }
+ }
+
+ @Test
+ fun testStableParameters_Lambdas(): Unit = ensureSetup {
+ val output = ArrayList<String>()
+ compose("""
+ @Model
+ class M { var count = 0 }
+ val m = M()
+
+ var output = ArrayList<String>()
+ val unchanged: () -> Unit = { }
+
+ fun log(msg: String) { output.add(msg) }
+
+ @Composable
+ fun Container(@Children children: () -> Unit) {
+ log("Container")
+ children()
+ }
+
+ @Composable
+ fun NormalLambda(index: Int, lambda: () -> Unit) {
+ log("NormalLambda(${'$'}index)")
+ Button(text="text")
+ }
+
+ @Composable
+ fun TestSkipping(unchanged: () -> Unit, changed: () -> Unit) {
+ log("TestSkipping")
+ Container {
+ NormalLambda(index = 1, lambda = unchanged)
+ NormalLambda(index = 2, lambda = unchanged)
+ NormalLambda(index = 3, lambda = unchanged)
+ NormalLambda(index = 4, lambda = changed)
+ }
+ }
+
+ @Composable
+ fun Main(unchanged: () -> Unit) {
+ Button(id=101, text="model ${'$'}{m.count}", onClick={ m.count++ })
+ TestSkipping(unchanged = unchanged, changed = { })
+ }
+ """, {
+ mapOf(
+ "outerOutput: ArrayList<String>" to output
+ )
+ }, """
+ output = outerOutput
+ Main(unchanged = unchanged)
+ """).then {
+ // Expect that all the methods are called in order
+ assertEquals(
+ "TestSkipping, Container, NormalLambda(1), " +
+ "NormalLambda(2), NormalLambda(3), NormalLambda(4)",
+ output.joinToString()
+ )
+ output.clear()
+ }.then { activity ->
+ // Expect nothing to occur with no changes
+ assertEquals("", output.joinToString())
+ output.clear()
+
+ // Change the model
+ val button = activity.findViewById(101) as Button
+ button.performClick()
+ }.then {
+ // Expect only NormalLambda(4) to be called
+ assertEquals(
+ "TestSkipping, Container, NormalLambda(4)",
+ output.joinToString()
+ )
+ }
+ }
+
override fun setUp() {
isSetup = true
super.setUp()
@@ -1914,7 +2088,9 @@
@Suppress("NO_REFLECTION_IN_CLASS_PATH")
val parameterList = candidateValues.map {
- "${it.key}: ${it.value::class.qualifiedName}"
+ if (it.key.contains(':')) {
+ it.key
+ } else "${it.key}: ${it.value::class.qualifiedName}"
}.joinToString()
val parameterTypes = candidateValues.map {
it.value::class.javaPrimitiveType ?: it.value::class.javaObjectType
diff --git a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/KtxModelCodeGenTests.kt b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/KtxModelCodeGenTests.kt
index a019068..f5aca24 100644
--- a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/KtxModelCodeGenTests.kt
+++ b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/KtxModelCodeGenTests.kt
@@ -27,6 +27,7 @@
import androidx.compose.composer
import androidx.compose.runWithCurrent
import org.junit.Before
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.Robolectric
@@ -64,6 +65,7 @@
}
@Test
+ @Ignore("TODO(b/138720405): Investigate synchronisation issues in tests")
fun testCGModelView_PersonModel(): Unit = ensureSetup {
val tvNameId = 384
val tvAgeId = 385
diff --git a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/KtxTypeResolutionTests.kt b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/KtxTypeResolutionTests.kt
index 1974f9d..e4bb666 100644
--- a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/KtxTypeResolutionTests.kt
+++ b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/KtxTypeResolutionTests.kt
@@ -160,10 +160,10 @@
@Composable fun test(
- @Children children: @Composable() () -> Unit,
+ children: @Composable() () -> Unit,
value: Int,
x: Int,
- @Children children2: @Composable() () -> Unit,
+ children2: @Composable() () -> Unit,
value2: Int
) {
<LinearLayout>
diff --git a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/analysis/ChildrenAnnotationTest.kt b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/analysis/ChildrenAnnotationTest.kt
new file mode 100644
index 0000000..dedcd26
--- /dev/null
+++ b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/analysis/ChildrenAnnotationTest.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.compose.plugins.kotlin.analysis
+
+import androidx.compose.plugins.kotlin.AbstractComposeDiagnosticsTest
+
+class ChildrenAnnotationTest : AbstractComposeDiagnosticsTest() {
+
+ fun testReportChildrenOnWrongParameter() {
+ doTest("""
+ import androidx.compose.*;
+
+ @Composable fun MyWidget(<!CHILDREN_MUST_BE_LAST!>@Children children: ()->Unit<!>, value: Int) {
+ System.out.println(""+children+value)
+ }
+ """)
+ }
+}
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ChildAnnotationChecker.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ChildAnnotationChecker.kt
new file mode 100644
index 0000000..c618a31
--- /dev/null
+++ b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ChildAnnotationChecker.kt
@@ -0,0 +1,57 @@
+/*
+ * 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.compose.plugins.kotlin
+
+import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
+import org.jetbrains.kotlin.descriptors.FunctionDescriptor
+import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi
+import org.jetbrains.kotlin.psi.KtDeclaration
+import org.jetbrains.kotlin.psi.KtElement
+import androidx.compose.plugins.kotlin.analysis.ComposeErrors
+import org.jetbrains.kotlin.container.StorageComponentContainer
+import org.jetbrains.kotlin.container.useInstance
+import org.jetbrains.kotlin.descriptors.ModuleDescriptor
+import org.jetbrains.kotlin.extensions.StorageComponentContainerContributor
+import org.jetbrains.kotlin.resolve.TargetPlatform
+import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker
+import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext
+import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
+
+open class ChildAnnotationChecker() : DeclarationChecker, StorageComponentContainerContributor {
+ override fun check(
+ declaration: KtDeclaration,
+ descriptor: DeclarationDescriptor,
+ context: DeclarationCheckerContext
+ ) {
+ if(descriptor is FunctionDescriptor) {
+ descriptor.valueParameters.forEachIndexed { index, param ->
+ if(param.hasChildrenAnnotation() && index != descriptor.valueParameters.lastIndex) {
+ context.trace.report(ComposeErrors.CHILDREN_MUST_BE_LAST.on(param.findPsi() as KtElement))
+ }
+ }
+ }
+ }
+
+ override fun registerModuleComponents(
+ container: StorageComponentContainer,
+ platform: TargetPlatform,
+ moduleDescriptor: ModuleDescriptor
+ ) {
+ if (platform != JvmPlatform) return
+ container.useInstance(this)
+ }
+}
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeFqNames.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeFqNames.kt
index bbea5f3..e00a888 100644
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeFqNames.kt
+++ b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeFqNames.kt
@@ -26,6 +26,7 @@
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.annotations.argumentValue
import org.jetbrains.kotlin.resolve.constants.ConstantValue
+import org.jetbrains.kotlin.resolve.descriptorUtil.annotationClass
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeUtils.NO_EXPECTED_TYPE
import org.jetbrains.kotlin.types.TypeUtils.UNIT_EXPECTED_TYPE
@@ -36,6 +37,7 @@
val Pivotal = ComposeUtils.composeFqName("Pivotal")
val Children = ComposeUtils.composeFqName("Children")
val Stateful = ComposeUtils.composeFqName("Stateful")
+ val StableMarker = ComposeUtils.composeFqName("StableMarker")
val Emittable = ComposeUtils.composeFqName("Emittable")
val HiddenAttribute = ComposeUtils.composeFqName("HiddenAttribute")
@@ -59,6 +61,10 @@
fun KotlinType.hasComposableAnnotation(): Boolean =
!isSpecialType && annotations.findAnnotation(ComposeFqNames.Composable) != null
+fun KotlinType.isMarkedStable(): Boolean =
+ !isSpecialType && (
+ annotations.hasStableMarker() ||
+ (constructor.declarationDescriptor?.annotations?.hasStableMarker() ?: false))
fun Annotated.hasComposableAnnotation(): Boolean =
annotations.findAnnotation(ComposeFqNames.Composable) != null
fun Annotated.hasPivotalAnnotation(): Boolean =
@@ -77,7 +83,7 @@
return childrenAnnotation.isComposableChildrenAnnotation
}
-private val KotlinType.isSpecialType: Boolean get() =
+internal val KotlinType.isSpecialType: Boolean get() =
this === NO_EXPECTED_TYPE || this === UNIT_EXPECTED_TYPE
val AnnotationDescriptor.isComposableAnnotation: Boolean get() = fqName == ComposeFqNames.Composable
@@ -87,4 +93,11 @@
if (fqName != ComposeFqNames.Children) return false
val composableValueArgument = argumentValue("composable")?.value
return composableValueArgument == null || composableValueArgument == true
- }
\ No newline at end of file
+ }
+
+fun Annotations.hasStableMarker(): Boolean = any(AnnotationDescriptor::isStableMarker)
+
+fun AnnotationDescriptor.isStableMarker(): Boolean {
+ val classDescriptor = annotationClass ?: return false
+ return classDescriptor.annotations.hasAnnotation(ComposeFqNames.StableMarker)
+}
\ No newline at end of file
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposePlugin.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposePlugin.kt
index e046b68c..bab14b9 100644
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposePlugin.kt
+++ b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposePlugin.kt
@@ -111,6 +111,10 @@
)
StorageComponentContainerContributor.registerExtension(
project,
+ ChildAnnotationChecker()
+ )
+ StorageComponentContainerContributor.registerExtension(
+ project,
UnionAnnotationCheckerProvider()
)
KtxParsingExtension.registerExtension(project,
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeSyntheticIrExtension.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeSyntheticIrExtension.kt
index 87d4766..29ba6a9 100644
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeSyntheticIrExtension.kt
+++ b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeSyntheticIrExtension.kt
@@ -358,9 +358,11 @@
memoize.validations.map { validation ->
statementGenerator.validationCall(
UNDEFINED_OFFSET, UNDEFINED_OFFSET,
- validation,
- updaterLambdaDescriptor,
- validation.assignmentLambda?.extensionReceiverParameter
+ memoizing = true,
+ validation = validation,
+ fnDescriptor = updaterLambdaDescriptor,
+ assignmentReceiver = validation.assignmentLambda
+ ?.extensionReceiverParameter
?: error("expected extension receiver")
) { name ->
getAttribute(name)
@@ -602,6 +604,8 @@
?: error("Expected callInvalidFnDescriptor to be non-null")
val validateParameterType =
getComposerCallParameterType(KtxNameConventions.CALL_INVALID_PARAMETER)
+ val memoizing = memoize.validations.all { it.attribute.isStable }
+ val validations = memoize.validations
val validateLambda =
lambdaExpression(
validateLambdaDescriptor,
@@ -609,30 +613,51 @@
) { statements ->
// all as one expression: a or b or c ... or z
- val validationCalls = memoize.validations
+ val validationCalls = validations
.map { validation ->
- statementGenerator.validationCall(
- UNDEFINED_OFFSET, UNDEFINED_OFFSET,
- validation,
- validateLambdaDescriptor,
- validateLambdaDescriptor.valueParameters.firstOrNull()
- ) { name ->
- getAttribute(name)
- }
+ if (validation.validationType == ValidationType.CHANGED &&
+ !memoizing)
+ IrConstImpl.constTrue(
+ UNDEFINED_OFFSET,
+ UNDEFINED_OFFSET,
+ irBuiltIns.booleanType
+ )
+ else
+ statementGenerator.validationCall(
+ UNDEFINED_OFFSET, UNDEFINED_OFFSET,
+ memoizing = memoizing,
+ validation = validation,
+ fnDescriptor = validateLambdaDescriptor,
+ assignmentReceiver = validateLambdaDescriptor
+ .valueParameters.firstOrNull()
+ ) { name ->
+ getAttribute(name)
+ }
}
when (validationCalls.size) {
- 0 -> Unit // TODO(lmr): return constant true here?
+ 0 -> if (!memoizing) {
+ // If we are not memoizing we should always return true
+ statements.add(IrConstImpl.constTrue(
+ UNDEFINED_OFFSET,
+ UNDEFINED_OFFSET,
+ irBuiltIns.booleanType
+ ))
+ }
1 -> statements.add(validationCalls.single())
else -> {
statements.add(
validationCalls.reduce { left, right ->
- statementGenerator.callMethod(
- UNDEFINED_OFFSET, UNDEFINED_OFFSET,
- resolvedKtxCall.infixOrCall
- ?: error("Invalid KTX Call"),
- left
- ).apply {
- putValueArgument(0, right)
+ when {
+ left is IrConstImpl<*> -> right
+ right is IrConstImpl<*> -> left
+ else -> statementGenerator.callMethod(
+ UNDEFINED_OFFSET, UNDEFINED_OFFSET,
+ resolvedKtxCall.infixOrCall
+ ?: error("Invalid KTX Call"),
+ left
+ ).apply {
+ putValueArgument(0, right)
+ }
}
}
)
@@ -697,15 +722,6 @@
}
}
-private fun <T> Collection<T>.append(collection: Collection<T>): Collection<T> {
- if (collection.isEmpty()) return this
- if (this.isEmpty()) return collection
- val result = arrayListOf<T>()
- result.addAll(this)
- result.addAll(collection)
- return result
-}
-
private fun StatementGenerator.getProperty(
startOffset: Int,
endOffset: Int,
@@ -758,13 +774,16 @@
private fun StatementGenerator.validationCall(
startOffset: Int,
endOffset: Int,
+ memoizing: Boolean,
validation: ValidatedAssignment,
fnDescriptor: FunctionDescriptor,
assignmentReceiver: ValueDescriptor?,
getAttribute: (String) -> IrExpression
): IrCall {
- val name = validation.attribute.name
+ val attribute = validation.attribute
+ val name = attribute.name
val attributeValue = getAttribute(name)
+
val validator = extensionReceiverOf(fnDescriptor)
?: error("expected an extension receiver to validator lambda")
@@ -773,10 +792,12 @@
// in emit, the element is passed through an extension parameter
// in call, the element is passed through a capture scope
-
+ val validationCall = (
+ if (memoizing) validation.validationCall
+ else validation.uncheckedValidationCall
+ ) ?: error("Expected validationCall to be non-null")
return callMethod(
- UNDEFINED_OFFSET, UNDEFINED_OFFSET,
- validation.validationCall ?: error("Expected validationCall to be non-null"),
+ UNDEFINED_OFFSET, UNDEFINED_OFFSET, validationCall,
validator
).apply {
putValueArgument(0, attributeValue)
@@ -787,7 +808,7 @@
val validationAssignment = lambdaExpression(
startOffset, endOffset,
assignmentLambdaDescriptor,
- validation.validationCall.resultingDescriptor.valueParameters[1].type
+ validationCall.resultingDescriptor.valueParameters[1].type
) { statements ->
val parameterDefinition = validation.assignmentLambda.valueParameters.first()
val parameterReference = context.symbolTable.referenceValueParameter(
@@ -872,23 +893,6 @@
}
}
-private fun StatementGenerator.callFunction(
- startOffset: Int,
- endOffset: Int,
- function: ResolvedCall<*>,
- extensionReceiver: IrExpression? = null
-): IrCall {
- val functionDescriptor = function.resultingDescriptor as FunctionDescriptor
-
- return buildCall(
- startOffset,
- endOffset,
- function,
- functionDescriptor,
- extensionReceiver = extensionReceiver
- )
-}
-
private fun StatementGenerator.callMethod(
startOffset: Int,
endOffset: Int,
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/KtxCallResolver.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/KtxCallResolver.kt
index 58e8157..69c6d5b 100644
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/KtxCallResolver.kt
+++ b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/KtxCallResolver.kt
@@ -78,6 +78,8 @@
import androidx.compose.plugins.kotlin.analysis.ComposeDefaultErrorMessages
import androidx.compose.plugins.kotlin.analysis.ComposeErrors
import androidx.compose.plugins.kotlin.analysis.ComposeWritableSlices
+import androidx.compose.plugins.kotlin.analysis.ComposeWritableSlices.STABLE_TYPE
+import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.builtins.isFunctionType
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.BindingTrace
@@ -140,6 +142,7 @@
import org.jetbrains.kotlin.types.expressions.ExpressionTypingFacade
import org.jetbrains.kotlin.types.expressions.KotlinTypeInfo
import org.jetbrains.kotlin.types.isError
+import org.jetbrains.kotlin.types.isNullable
import org.jetbrains.kotlin.types.typeUtil.asTypeProjection
import org.jetbrains.kotlin.types.typeUtil.equalTypesOrNulls
import org.jetbrains.kotlin.types.typeUtil.isAnyOrNullableAny
@@ -147,9 +150,11 @@
import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
import org.jetbrains.kotlin.types.typeUtil.isTypeParameter
import org.jetbrains.kotlin.types.typeUtil.isUnit
+import org.jetbrains.kotlin.types.typeUtil.makeNotNullable
import org.jetbrains.kotlin.types.typeUtil.supertypes
import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty
+import java.util.Locale
import java.util.concurrent.atomic.AtomicBoolean
/**
@@ -773,8 +778,16 @@
constantChecker
)
+ val stable = isStable(
+ node.type,
+ context
+ )
+
// update all of the nodes in the AST as "static"
- attributeNodes[node.name]?.forEach { it.isStatic = static }
+ attributeNodes[node.name]?.forEach {
+ it.isStatic = static
+ it.isStable = stable
+ }
// return a node for the root of the AST that codegen can use
AttributeNode(
@@ -782,7 +795,8 @@
descriptor = node.descriptor,
expression = node.expression,
type = node.type,
- isStatic = static
+ isStatic = static,
+ isStable = stable
)
}
@@ -1097,23 +1111,26 @@
ExplicitReceiverKind.DISPATCH_RECEIVER -> {
val receiver = resolvedCall.dispatchReceiver as? ExpressionReceiver
?: return emptyList()
+ val (validationCall, uncheckedValidationCall, _) = resolveValidationCall(
+ kind = kind,
+ validationType = ValidationType.CHANGED,
+ attrType = receiver.type,
+ expressionToReportErrorsOn = receiver.expression,
+ receiverScope = receiverScope,
+ assignmentReceiverScope = null,
+ context = context
+ )
return listOf(
ValidatedAssignment(
validationType = ValidationType.CHANGED,
- validationCall = resolveValidationCall(
- kind = kind,
- validationType = ValidationType.CHANGED,
- attrType = receiver.type,
- expressionToReportErrorsOn = receiver.expression,
- receiverScope = receiverScope,
- assignmentReceiverScope = null,
- context = context
- ).first,
+ validationCall = validationCall,
+ uncheckedValidationCall = uncheckedValidationCall,
assignment = null,
assignmentLambda = null,
attribute = AttributeNode(
name = TAG_KEY,
isStatic = false,
+ isStable = false,
type = receiver.type,
expression = receiver.expression,
descriptor = descriptor
@@ -1620,25 +1637,29 @@
} else emptyList()
val allValidations = if (!shouldMemoizeCtor) {
- (tagValidations + setterValidations).map {
- when (it.validationType) {
- ValidationType.CHANGED -> it
+ (tagValidations + setterValidations).map { validation ->
+ when (validation.validationType) {
+ ValidationType.CHANGED -> validation
ValidationType.UPDATE,
- ValidationType.SET -> ValidatedAssignment(
+ ValidationType.SET -> resolveValidationCall(
+ kind = ComposerCallKind.CALL,
validationType = ValidationType.CHANGED,
- validationCall = resolveValidationCall(
- kind = ComposerCallKind.CALL,
+ attrType = validation.attribute.type,
+ expressionToReportErrorsOn = expression,
+ receiverScope = invalidReceiverScope,
+ assignmentReceiverScope = null,
+ context = context
+ ).let {
+ val (validationCall, uncheckedValidationCall, _) = it
+ ValidatedAssignment(
validationType = ValidationType.CHANGED,
- attrType = it.attribute.type,
- expressionToReportErrorsOn = expression,
- receiverScope = invalidReceiverScope,
- assignmentReceiverScope = null,
- context = context
- ).first,
- assignment = null,
- attribute = it.attribute,
- assignmentLambda = null
- )
+ validationCall = validationCall,
+ uncheckedValidationCall = uncheckedValidationCall,
+ assignment = null,
+ attribute = validation.attribute,
+ assignmentLambda = null
+ )
+ }
}
}
} else tagValidations + setterValidations
@@ -1798,7 +1819,8 @@
descriptor = it.descriptor,
type = it.type,
expression = it.attribute.value,
- isStatic = false
+ isStatic = false,
+ isStable = false
)
}
}
@@ -1815,7 +1837,8 @@
descriptor = info.descriptor,
type = info.type,
expression = attribute.value,
- isStatic = false
+ isStatic = false,
+ isStable = false
)
)
}
@@ -1835,7 +1858,8 @@
descriptor = descriptor,
type = attribute.type,
expression = attribute.expression,
- isStatic = false
+ isStatic = false,
+ isStable = false
)
)
continue
@@ -1848,7 +1872,8 @@
descriptor = descriptor,
type = attribute.type,
expression = attribute.expression,
- isStatic = false
+ isStatic = false,
+ isStable = false
)
)
continue
@@ -1900,6 +1925,7 @@
AttributeNode(
name = attr.name,
isStatic = false,
+ isStable = false,
descriptor = param,
type = type,
expression = attr.value
@@ -1914,7 +1940,7 @@
receiverScope: KotlinType,
context: ExpressionTypingContext
): ValidatedAssignment {
- val validationCall = resolveValidationCall(
+ val (validationCall, uncheckedValidationCall, _) = resolveValidationCall(
kind = kind,
validationType = ValidationType.CHANGED,
attrType = type,
@@ -1922,11 +1948,12 @@
receiverScope = receiverScope,
assignmentReceiverScope = null,
context = context
- ).first
+ )
return ValidatedAssignment(
validationType = ValidationType.CHANGED,
validationCall = validationCall,
+ uncheckedValidationCall = uncheckedValidationCall,
attribute = this,
assignment = null,
assignmentLambda = null
@@ -2013,15 +2040,16 @@
else -> null
} ?: continue
- val (validationCall, lambdaDescriptor) = resolveValidationCall(
- kind = kind,
- expressionToReportErrorsOn = expressionToReportErrorsOn,
- receiverScope = receiverScope,
- assignmentReceiverScope = type,
- validationType = validationType,
- attrType = attrType,
- context = context.replaceTraceAndCache(tempForValidations)
- )
+ val (validationCall, uncheckedValidationCall, lambdaDescriptor) =
+ resolveValidationCall(
+ kind = kind,
+ expressionToReportErrorsOn = expressionToReportErrorsOn,
+ receiverScope = receiverScope,
+ assignmentReceiverScope = type,
+ validationType = validationType,
+ attrType = attrType,
+ context = context.replaceTraceAndCache(tempForValidations)
+ )
results.add(
ValidatedAssignment(
@@ -2033,9 +2061,11 @@
expression = attribute.value,
type = attrType,
descriptor = resolvedCall.resultingDescriptor,
- isStatic = false
+ isStatic = false,
+ isStable = false
),
- validationCall = validationCall
+ validationCall = validationCall,
+ uncheckedValidationCall = uncheckedValidationCall
)
)
consumedAttributes.add(name)
@@ -2114,15 +2144,16 @@
else -> error("Unknown callable type encountered")
}
- val (validationCall, lambdaDescriptor) = resolveValidationCall(
- kind = kind,
- expressionToReportErrorsOn = expressionToReportErrorsOn,
- receiverScope = receiverScope,
- assignmentReceiverScope = type,
- validationType = validationType,
- attrType = attrType,
- context = context.replaceTraceAndCache(tempForValidations)
- )
+ val (validationCall, uncheckedValidationCall, lambdaDescriptor) =
+ resolveValidationCall(
+ kind = kind,
+ expressionToReportErrorsOn = expressionToReportErrorsOn,
+ receiverScope = receiverScope,
+ assignmentReceiverScope = type,
+ validationType = validationType,
+ attrType = attrType,
+ context = context.replaceTraceAndCache(tempForValidations)
+ )
results.add(
ValidatedAssignment(
@@ -2137,9 +2168,11 @@
expression = children.value,
type = attrType,
descriptor = resolvedCall.resultingDescriptor,
- isStatic = false
+ isStatic = false,
+ isStable = false
),
- validationCall = validationCall
+ validationCall = validationCall,
+ uncheckedValidationCall = uncheckedValidationCall
)
)
consumedAttributes.add(CHILDREN_KEY)
@@ -2839,53 +2872,23 @@
)
}
- private fun resolveValidationCall(
- kind: ComposerCallKind,
+ private fun resolveSingleValidationCall(
expressionToReportErrorsOn: KtExpression,
receiverScope: KotlinType,
- assignmentReceiverScope: KotlinType?,
validationType: ValidationType,
+ checked: Boolean,
attrType: KotlinType,
+ lambdaArg: ValueArgument?,
context: ExpressionTypingContext
- ): Pair<ResolvedCall<*>?, FunctionDescriptor?> {
-
+ ): ResolvedCall<*>? {
val temporaryForVariable = TemporaryTraceAndCache.create(
context, "trace to resolve variable", expressionToReportErrorsOn
)
val contextToUse = context.replaceTraceAndCache(temporaryForVariable)
-
- val name = validationType.name.toLowerCase()
- val includeLambda = validationType != ValidationType.CHANGED
-
+ val name = validationType.name.toLowerCase(Locale.ROOT).let {
+ if (!checked) (it + "Unchecked") else it
+ }
val calleeExpression = psiFactory.createSimpleName(name)
-
- // for call:
- // ValidatorType.set(AttrType, (AttrType) -> Unit): Boolean
- // ValidatorType.update(AttrType, (AttrType) -> Unit): Boolean
- // ValidatorType.changed(AttrType): Boolean
-
- // for emit:
- // ValidatorType.set(AttrType, ElementType.(AttrType) -> Unit): Unit
- // ValidatorType.update(AttrType, ElementType.(AttrType) -> Unit): Unit
- // ValidatorType.changed(AttrType): Unit
-
- val lambdaType = when {
- includeLambda && kind == ComposerCallKind.EMIT -> functionType(
- parameterTypes = listOf(attrType),
- receiverType = assignmentReceiverScope
- )
- includeLambda && kind == ComposerCallKind.CALL -> functionType(
- parameterTypes = listOf(attrType)
- )
- else -> null
- }
- val lambdaArg = lambdaType?.let { makeValueArgument(it, contextToUse) }
- val lambdaDescriptor = lambdaType?.let {
- createFunctionDescriptor(
- it,
- contextToUse
- )
- }
val call = makeCall(
callElement = calleeExpression,
calleeExpression = calleeExpression,
@@ -2895,7 +2898,6 @@
),
receiver = TransientReceiver(receiverScope)
)
-
val results = callResolver.resolveCallWithGivenName(
BasicCallResolutionContext.create(
contextToUse,
@@ -2908,7 +2910,7 @@
Name.identifier(name)
)
- if (results.isSuccess) return results.resultingCall to lambdaDescriptor
+ if (results.isSuccess) return results.resultingCall
if (results.resultCode == OverloadResolutionResults.Code.INCOMPLETE_TYPE_INFERENCE) {
@@ -2967,12 +2969,79 @@
if (nextResults.isSuccess) {
nextTempTrace.commit()
- return nextResults.resultingCall to lambdaDescriptor
+ return nextResults.resultingCall
}
}
}
- return null to null
+ return null
+ }
+
+ private fun resolveValidationCall(
+ kind: ComposerCallKind,
+ expressionToReportErrorsOn: KtExpression,
+ receiverScope: KotlinType,
+ assignmentReceiverScope: KotlinType?,
+ validationType: ValidationType,
+ attrType: KotlinType,
+ context: ExpressionTypingContext
+ ): Triple<ResolvedCall<*>?, ResolvedCall<*>?, FunctionDescriptor?> {
+ val temporaryForVariable = TemporaryTraceAndCache.create(
+ context, "trace to resolve variable", expressionToReportErrorsOn
+ )
+ val contextToUse = context.replaceTraceAndCache(temporaryForVariable)
+
+ val includeLambda = validationType != ValidationType.CHANGED
+
+ // for call:
+ // ValidatorType.set(AttrType, (AttrType) -> Unit): Boolean
+ // ValidatorType.update(AttrType, (AttrType) -> Unit): Boolean
+ // ValidatorType.changed(AttrType): Boolean
+
+ // for emit:
+ // ValidatorType.set(AttrType, ElementType.(AttrType) -> Unit): Unit
+ // ValidatorType.update(AttrType, ElementType.(AttrType) -> Unit): Unit
+ // ValidatorType.changed(AttrType): Unit
+
+ val lambdaType = when {
+ includeLambda && kind == ComposerCallKind.EMIT -> functionType(
+ parameterTypes = listOf(attrType),
+ receiverType = assignmentReceiverScope
+ )
+ includeLambda && kind == ComposerCallKind.CALL -> functionType(
+ parameterTypes = listOf(attrType)
+ )
+ else -> null
+ }
+ val lambdaArg = lambdaType?.let { makeValueArgument(it, contextToUse) }
+ val lambdaDescriptor = lambdaType?.let {
+ createFunctionDescriptor(
+ it,
+ contextToUse
+ )
+ }
+
+ val validationCall = resolveSingleValidationCall(
+ expressionToReportErrorsOn = expressionToReportErrorsOn,
+ receiverScope = receiverScope,
+ validationType = validationType,
+ checked = true,
+ attrType = attrType,
+ lambdaArg = lambdaArg,
+ context = context
+ )
+
+ val uncheckedValidationCall = resolveSingleValidationCall(
+ expressionToReportErrorsOn = expressionToReportErrorsOn,
+ receiverScope = receiverScope,
+ validationType = validationType,
+ checked = false,
+ attrType = attrType,
+ lambdaArg = lambdaArg,
+ context = context
+ )
+
+ return Triple(validationCall, uncheckedValidationCall, lambdaDescriptor)
}
private fun resolveSubstitutableComposerMethod(
@@ -3719,6 +3788,22 @@
}
}
+private fun isStable(type: KotlinType?, context: ExpressionTypingContext): Boolean {
+ return type?.let {
+ val trace = context.trace
+ val calculated = trace.get(STABLE_TYPE, it)
+ if (calculated == null) {
+ val isStable = !it.isError && !it.isSpecialType && (
+ KotlinBuiltIns.isPrimitiveType(it) ||
+ it.isFunctionType ||
+ it.isMarkedStable() ||
+ (type.isNullable() && isStable(it.makeNotNullable(), context)))
+ trace.record(STABLE_TYPE, it, isStable)
+ isStable
+ } else calculated
+ } ?: false
+}
+
private fun DeclarationDescriptor.isRoot() =
containingDeclaration?.containingDeclaration is ModuleDescriptor
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ResolvedKtxElementCall.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ResolvedKtxElementCall.kt
index 1f092e0..ed88c3c 100644
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ResolvedKtxElementCall.kt
+++ b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ResolvedKtxElementCall.kt
@@ -68,6 +68,7 @@
class AttributeNode(
name: String,
var isStatic: Boolean,
+ var isStable: Boolean,
val expression: KtExpression,
type: KotlinType,
descriptor: DeclarationDescriptor
@@ -97,6 +98,7 @@
class ValidatedAssignment(
val validationType: ValidationType,
val validationCall: ResolvedCall<*>?,
+ val uncheckedValidationCall: ResolvedCall<*>?,
val assignment: ResolvedCall<*>?,
val assignmentLambda: FunctionDescriptor?,
val attribute: AttributeNode
@@ -236,13 +238,13 @@
collector: MutableMap<String, MutableList<AttributeMeta>>
) {
callDescriptor?.let {
- it.valueParameters.forEach { param ->
+ it.valueParameters.forEachIndexed { index, param ->
collector.multiPut(
AttributeMeta(
name = param.name.asString(),
type = param.type,
descriptor = param,
- isChildren = param.hasChildrenAnnotation()
+ isChildren = param.hasChildrenAnnotation() || it.valueParameters.size-1 == index
)
)
}
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/analysis/ComposeDefaultErrorMessages.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/analysis/ComposeDefaultErrorMessages.kt
index 68acda7..ced33ab 100644
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/analysis/ComposeDefaultErrorMessages.kt
+++ b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/analysis/ComposeDefaultErrorMessages.kt
@@ -157,5 +157,10 @@
"Ambiguous targets. {0}",
Renderers.AMBIGUOUS_CALLS
)
+ MAP.put(
+ ComposeErrors.CHILDREN_MUST_BE_LAST,
+ "Children annotation must only occur on last parameter. This annotation is deprecated (move children to " +
+ "be last parameter, make it @Composable, and remove the @Children annotation.)."
+ )
}
}
\ No newline at end of file
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/analysis/ComposeErrors.java b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/analysis/ComposeErrors.java
index 467ab57..a9b2802 100644
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/analysis/ComposeErrors.java
+++ b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/analysis/ComposeErrors.java
@@ -94,6 +94,8 @@
MISSING_REQUIRED_CHILDREN = DiagnosticFactory1.create(ERROR);
DiagnosticFactory2<KtExpression, Collection<KotlinType>, Collection<KotlinType>>
ILLEGAL_ASSIGN_TO_UNIONTYPE = DiagnosticFactory2.create(ERROR);
+ DiagnosticFactory0<KtElement>
+ CHILDREN_MUST_BE_LAST = DiagnosticFactory0.create(ERROR);
@SuppressWarnings("UnusedDeclaration")
Object INITIALIZER = new Object() {
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/analysis/ComposeWritableSlices.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/analysis/ComposeWritableSlices.kt
index 5eadb7f..146c0b8 100644
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/analysis/ComposeWritableSlices.kt
+++ b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/analysis/ComposeWritableSlices.kt
@@ -1,15 +1,16 @@
package androidx.compose.plugins.kotlin.analysis
+import androidx.compose.plugins.kotlin.ComposableAnnotationChecker
+import androidx.compose.plugins.kotlin.ResolvedKtxElementCall
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtReferenceExpression
-import androidx.compose.plugins.kotlin.ComposableAnnotationChecker
-import androidx.compose.plugins.kotlin.ResolvedKtxElementCall
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.util.slicedMap.BasicWritableSlice
import org.jetbrains.kotlin.util.slicedMap.RewritePolicy
import org.jetbrains.kotlin.util.slicedMap.WritableSlice
+import org.jetbrains.kotlin.types.KotlinType
object ComposeWritableSlices {
val COMPOSABLE_ANALYSIS: WritableSlice<KtElement, ComposableAnnotationChecker.Composability> =
@@ -27,6 +28,8 @@
BasicWritableSlice(RewritePolicy.DO_NOTHING)
val INFERRED_COMPOSABLE_DESCRIPTOR: WritableSlice<FunctionDescriptor, Boolean> =
BasicWritableSlice(RewritePolicy.DO_NOTHING)
+ val STABLE_TYPE: WritableSlice<KotlinType, Boolean?> =
+ BasicWritableSlice(RewritePolicy.DO_NOTHING)
}
private val REWRITES_ALLOWED = object : RewritePolicy {
diff --git a/compose/compose-runtime/build.gradle b/compose/compose-runtime/build.gradle
index 6f7a33a..52f9ae6 100644
--- a/compose/compose-runtime/build.gradle
+++ b/compose/compose-runtime/build.gradle
@@ -15,6 +15,7 @@
*/
+import org.jetbrains.kotlin.codegen.ImplementationBodyCodegen
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import static androidx.build.dependencies.DependenciesKt.*
@@ -25,15 +26,30 @@
plugins {
id("AndroidXPlugin")
id("com.android.library")
- id("kotlin-android")
+ id("kotlin-multiplatform")
}
-dependencies {
- implementation "androidx.annotation:annotation:1.0.0"
- implementation(KOTLIN_COMPOSE_STDLIB)
- implementation(KOTLIN_COMPOSE_REFLECT)
- testImplementation(JUNIT)
- testImplementation(ROBOLECTRIC)
+kotlin {
+ android()
+
+ sourceSets {
+ commonMain.dependencies {
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-common:$KOTLIN_VERSION"
+ }
+ commonTest.dependencies {
+ // TODO https://youtrack.jetbrains.com/issue/KT-29343
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-common:$KOTLIN_VERSION"
+ implementation(kotlin("test-annotations-common"))
+ }
+ androidMain.dependencies {
+ implementation "androidx.annotation:annotation:1.0.0"
+ implementation(KOTLIN_COMPOSE_STDLIB)
+ implementation(KOTLIN_COMPOSE_REFLECT)
+ }
+ androidTest.dependencies {
+ }
+
+ }
}
android {
@@ -51,6 +67,11 @@
testCoverageEnabled = false
}
}
+ sourceSets {
+ main {
+ manifest.srcFile 'src/androidMain/AndroidManifest.xml'
+ }
+ }
}
androidx {
diff --git a/compose/compose-runtime/compose-runtime-benchmark/build.gradle b/compose/compose-runtime/compose-runtime-benchmark/build.gradle
index c82e12e..dcb32f6 100644
--- a/compose/compose-runtime/compose-runtime-benchmark/build.gradle
+++ b/compose/compose-runtime/compose-runtime-benchmark/build.gradle
@@ -24,6 +24,7 @@
id("com.android.library")
id("AndroidXUiPlugin")
id("kotlin-android")
+ id("androidx.benchmark")
}
android {
@@ -48,6 +49,7 @@
androidTestImplementation(project(":compose:compose-runtime"))
androidTestImplementation(project(":ui:ui-core"))
+ androidTestImplementation(project(":ui:ui-text"))
androidTestImplementation(project(":ui:ui-framework"))
androidTestImplementation(project(":ui:ui-layout"))
androidTestImplementation(project(":ui:ui-material"))
@@ -61,7 +63,7 @@
androidTestImplementation(ANDROIDX_TEST_RULES)
androidTestImplementation(KOTLIN_COMPOSE_STDLIB)
androidTestImplementation(KOTLIN_COMPOSE_REFLECT)
- androidTestImplementation(project(":benchmark"))
+ androidTestImplementation(project(":benchmark:benchmark-junit4"))
}
tasks.withType(KotlinCompile).configureEach {
diff --git a/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/AndroidManifest.xml b/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/AndroidManifest.xml
index 4bba238..e48051b 100644
--- a/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/AndroidManifest.xml
+++ b/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/AndroidManifest.xml
@@ -18,8 +18,12 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="androidx.compose.benchmark">
- <!-- Important: disable debuggable for accurate performance results -->
+ <!--
+ ~ Important: disable debuggable for accurate performance results
+ ~ requestLegacyExternalStorage to enable legacy JSON reporting when targeting Q
+ -->
<application
+ android:requestLegacyExternalStorage="true"
android:debuggable="false"
tools:replace="android:debuggable">
<activity
diff --git a/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/ComposeBenchmark.kt b/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/ComposeBenchmark.kt
index 0a320fc..9d5c40f 100644
--- a/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/ComposeBenchmark.kt
+++ b/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/ComposeBenchmark.kt
@@ -16,29 +16,19 @@
package androidx.compose.benchmark
-import androidx.benchmark.BenchmarkRule
-import androidx.benchmark.measureRepeated
import androidx.compose.Composable
-import androidx.compose.Composer
-import androidx.compose.FrameManager
import androidx.compose.Model
import androidx.compose.Observe
import androidx.compose.benchmark.realworld4.RealWorld4_FancyWidget_000
import androidx.compose.composer
-import androidx.compose.disposeComposition
-import androidx.compose.runWithCurrent
import androidx.test.annotation.UiThreadTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
-import androidx.test.rule.ActivityTestRule
import androidx.ui.core.dp
-import androidx.ui.core.setContent
import androidx.ui.foundation.ColoredRect
import androidx.ui.graphics.Color
-import org.junit.Assert.assertTrue
import org.junit.FixMethodOrder
import org.junit.Ignore
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -46,12 +36,7 @@
@LargeTest
@RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ComposeBenchmark {
- @get:Rule
- val benchmarkRule = BenchmarkRule()
-
- @get:Rule
- val activityRule = ActivityTestRule(ComposeActivity::class.java)
+class ComposeBenchmark: ComposeBenchmarkBase() {
@UiThreadTest
@Test
@@ -76,7 +61,7 @@
fun benchmark_03_Compose_100Rects() {
val model = ColorModel()
measureCompose {
- HunderedRects(model)
+ HundredRects(model = model)
}
}
@@ -128,7 +113,7 @@
val model = ColorModel()
measureRecompose {
compose {
- HunderedRects(model, narrow = false)
+ HundredRects(model, narrow = false)
}
update {
model.toggle()
@@ -142,7 +127,7 @@
val model = ColorModel()
measureRecompose {
compose {
- HunderedRects(model, narrow = true)
+ HundredRects(model, narrow = true)
}
update {
model.toggle()
@@ -165,48 +150,6 @@
}
}
- private fun measureCompose(block: @Composable() () -> Unit) {
- benchmarkRule.measureRepeated {
- val activity = activityRule.activity
-
- activity.setContent {
- block()
- }
-
- runWithTimingDisabled {
- activity.disposeComposition()
- }
- }
- }
-
- private fun measureRecompose(block: RecomposeReceiver.() -> Unit) {
- val receiver = RecomposeReceiver()
- receiver.block()
- var activeComposer: Composer<*>? = null
-
- val activity = activityRule.activity
-
- activity.setContent {
- activeComposer = composer.composer
- receiver.composeCb()
- }
-
- benchmarkRule.measureRepeated {
- runWithTimingDisabled {
- receiver.updateModelCb()
- FrameManager.nextFrame()
- }
-
- val didSomething = activeComposer?.let { composer ->
- composer.runWithCurrent {
- composer.recompose().also { composer.applyChanges() }
- }
- } ?: false
- assertTrue(didSomething)
- }
-
- activity.disposeComposition()
- }
}
private val color = Color.Yellow
@@ -238,7 +181,7 @@
}
@Composable
-fun HunderedRects(model: ColorModel, narrow: Boolean = false) {
+fun HundredRects(model: ColorModel, narrow: Boolean = false) {
repeat(100) {
if (it % 10 == 0)
if (narrow) {
@@ -252,16 +195,3 @@
ColoredRect(color = color, width = 10.dp, height = 10.dp)
}
}
-
-private class RecomposeReceiver {
- var composeCb: @Composable() () -> Unit = @Composable { }
- var updateModelCb: () -> Unit = { }
-
- fun compose(block: @Composable() () -> Unit) {
- composeCb = block
- }
-
- fun update(block: () -> Unit) {
- updateModelCb = block
- }
-}
diff --git a/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/ComposeBenchmarkBase.kt b/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/ComposeBenchmarkBase.kt
new file mode 100644
index 0000000..29fd820
--- /dev/null
+++ b/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/ComposeBenchmarkBase.kt
@@ -0,0 +1,94 @@
+/*
+ * 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.compose.benchmark
+
+import androidx.benchmark.junit4.BenchmarkRule
+import androidx.benchmark.junit4.measureRepeated
+import androidx.compose.Composable
+import androidx.compose.Composer
+import androidx.compose.FrameManager
+import androidx.compose.composer
+import androidx.compose.disposeComposition
+import androidx.compose.runWithCurrent
+import androidx.test.rule.ActivityTestRule
+import androidx.ui.core.setContent
+import org.junit.Assert.assertTrue
+import org.junit.Rule
+
+abstract class ComposeBenchmarkBase {
+ @get:Rule
+ val benchmarkRule = BenchmarkRule()
+
+ @get:Rule
+ val activityRule = ActivityTestRule(ComposeActivity::class.java)
+
+ fun measureCompose(block: @Composable() () -> Unit) {
+ val activity = activityRule.activity
+ benchmarkRule.measureRepeated {
+ activity.setContent {
+ block()
+ }
+
+ runWithTimingDisabled {
+ activity.setContent { }
+ }
+ }
+ activity.disposeComposition()
+ }
+
+ fun measureRecompose(block: RecomposeReceiver.() -> Unit) {
+ val receiver = RecomposeReceiver()
+ receiver.block()
+ var activeComposer: Composer<*>? = null
+
+ val activity = activityRule.activity
+
+ activity.setContent {
+ activeComposer = composer.composer
+ receiver.composeCb()
+ }
+
+ benchmarkRule.measureRepeated {
+ runWithTimingDisabled {
+ receiver.updateModelCb()
+ FrameManager.nextFrame()
+ }
+
+ val didSomething = activeComposer?.let { composer ->
+ composer.runWithCurrent {
+ composer.recompose().also { composer.applyChanges() }
+ }
+ } ?: false
+ assertTrue(didSomething)
+ }
+
+ activity.disposeComposition()
+ }
+}
+
+class RecomposeReceiver {
+ var composeCb: @Composable() () -> Unit = @Composable { }
+ var updateModelCb: () -> Unit = { }
+
+ fun compose(block: @Composable() () -> Unit) {
+ composeCb = block
+ }
+
+ fun update(block: () -> Unit) {
+ updateModelCb = block
+ }
+}
diff --git a/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/DbMonsterBenchmark.kt b/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/DbMonsterBenchmark.kt
new file mode 100644
index 0000000..6e38b77
--- /dev/null
+++ b/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/DbMonsterBenchmark.kt
@@ -0,0 +1,84 @@
+/*
+ * 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.compose.benchmark
+
+import android.app.Activity
+import androidx.compose.Composable
+import androidx.compose.Composer
+import androidx.compose.FrameManager
+import androidx.compose.benchmark.dbmonster.DatabaseList
+import androidx.compose.benchmark.dbmonster.DatabaseRow
+import androidx.compose.benchmark.dbmonster.Table
+import androidx.compose.composer
+import androidx.compose.disposeComposition
+import androidx.compose.runWithCurrent
+import androidx.test.annotation.UiThreadTest
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.rule.ActivityTestRule
+import androidx.ui.core.setContent
+import org.junit.Assert
+import org.junit.FixMethodOrder
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import kotlin.random.Random
+
+/**
+ * This is an implementation of a classic web perf benchmark "dbmonster". This can provide insight into apps with
+ * lots of updating parts at once. It may also be good tests for the Text and Layout stacks of compose UI.
+ *
+ * See: http://mathieuancelin.github.io/js-repaint-perfs/
+ */
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class DbMonsterBenchmark : ComposeBenchmarkBase() {
+
+ @UiThreadTest
+ @Test
+ fun dbMonster_count10_mutate10() = dbMonsterBenchmark(count = 10, mutate = 10)
+
+ @UiThreadTest
+ @Test
+ fun dbMonster_count20_mutate01() = dbMonsterBenchmark(count = 20, mutate = 1)
+
+ /**
+ * @param count - the number of databases (2x this will be number of rows)
+ * @param mutate - the number of databases to mutate/update on each frame (2x count will be 100%)
+ */
+ private fun dbMonsterBenchmark(count: Int, mutate: Int) {
+ val random = Random(0)
+ println(count)
+ println(mutate)
+ println(random)
+ val list = DatabaseList(count, random)
+ measureRecompose {
+ compose {
+ Table {
+ for (db in list.databases) {
+ DatabaseRow(db = db)
+ }
+ }
+ }
+ update {
+ list.update(mutate)
+ }
+ }
+ }
+}
diff --git a/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/DeepTreeBenchmark.kt b/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/DeepTreeBenchmark.kt
new file mode 100644
index 0000000..89f4dd1
--- /dev/null
+++ b/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/DeepTreeBenchmark.kt
@@ -0,0 +1,75 @@
+/*
+ * 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.compose.benchmark
+
+import androidx.compose.Composable
+import androidx.compose.benchmark.deeptree.DeepTree
+import androidx.compose.disposeComposition
+import androidx.compose.composer
+import androidx.test.annotation.UiThreadTest
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.rule.ActivityTestRule
+import androidx.ui.core.setContent
+import org.junit.FixMethodOrder
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+
+/**
+ * This is definitely a synthetic benchmark that may not map to realistic trees, but it is an effective way of
+ * stress-testing some very large and very complex trees that may map pretty well to the more complicated
+ * scenarios. I think this is a decent benchmark for testing Compose UI’s layout system in addition to
+ * Compose’s composition performance.
+ */
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class DeepTreeBenchmark : ComposeBenchmarkBase() {
+ @UiThreadTest
+ @Test
+ fun benchmark_deep_tree_01_depth1_breadth100_wrap2() {
+ measureCompose {
+ DeepTree(depth = 1, breadth = 100, wrap = 2)
+ }
+ }
+
+ @UiThreadTest
+ @Test
+ fun benchmark_deep_tree_02_depth7_breadth3_wrap2() {
+ measureCompose {
+ DeepTree(depth = 7, breadth = 3, wrap = 2)
+ }
+ }
+
+ @UiThreadTest
+ @Test
+ fun benchmark_deep_tree_03_depth2_breadth10_wrap2() {
+ measureCompose {
+ DeepTree(depth = 2, breadth = 10, wrap = 2)
+ }
+ }
+
+ @UiThreadTest
+ @Test
+ fun benchmark_deep_tree_04_depth2_breadth10_wrap6() {
+ measureCompose {
+ DeepTree(depth = 2, breadth = 10, wrap = 6)
+ }
+ }
+}
\ No newline at end of file
diff --git a/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/SiblingBenchmark.kt b/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/SiblingBenchmark.kt
new file mode 100644
index 0000000..dd67ae1
--- /dev/null
+++ b/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/SiblingBenchmark.kt
@@ -0,0 +1,131 @@
+/*
+ * 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.compose.benchmark
+
+import androidx.compose.Composable
+import androidx.compose.Composer
+import androidx.compose.FrameManager
+import androidx.compose.Model
+import androidx.compose.Observe
+import androidx.compose.State
+import androidx.compose.benchmark.deeptree.DeepTree
+import androidx.compose.benchmark.realworld4.RealWorld4_FancyWidget_000
+import androidx.compose.benchmark.siblings.IdentityType
+import androidx.compose.benchmark.siblings.Item
+import androidx.compose.benchmark.siblings.ReorderType
+import androidx.compose.benchmark.siblings.SiblingManagement
+import androidx.compose.benchmark.siblings.update
+import androidx.compose.composer
+import androidx.compose.disposeComposition
+import androidx.compose.runWithCurrent
+import androidx.compose.state
+import androidx.compose.unaryPlus
+import androidx.test.annotation.UiThreadTest
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.rule.ActivityTestRule
+import androidx.ui.core.dp
+import androidx.ui.core.setContent
+import androidx.ui.foundation.ColoredRect
+import androidx.ui.graphics.Color
+import org.junit.Assert.assertTrue
+import org.junit.FixMethodOrder
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+import kotlin.random.Random
+
+/**
+ * Managing “lists” of components that are siblings in Compose and other declarative reactive frameworks ends up
+ * being an important performance characteristic to monitor. The algorithm we use here can depend greatly on how
+ * we update lists of items and we might choose to bias towards what happens more commonly in the real world,
+ * but understanding our performance characteristics for various types of list updates will be useful.
+ *
+ * @param count - number of items in the list. Really long lists probably should use a Recycler or something
+ * similar in the real world, but testing this will at least let us understand our asymptotic complexity
+ * characteristics.
+ *
+ * @param reorder - This determines what kinds of changes we will be making to the list each frame. Different list
+ * management algorithms that Compose uses will yield different trade offs depending on where items in the list
+ * are moved/added/removed/etc. For instance, we might be optimized for "append" but not "prepend", so we
+ * should benchmark these types of changes individually. Note that some like "AddMiddle" insert at a random
+ * index, so benchmarks should run this many times in order to average the randomness into something reasonable
+ * to compare with a different run of the same benchmark.
+ *
+ * @param identity - this will toggle how Compose identifies a row. These three options are slightly different and
+ * we might want to test all three.
+ */
+@LargeTest
+@RunWith(Parameterized::class)
+class SiblingBenchmark(
+ val count: Int,
+ val reorder: ReorderType,
+ val identity: IdentityType
+) : ComposeBenchmarkBase() {
+ companion object {
+ @JvmStatic
+ @Parameterized.Parameters(name = "{0}_{1}_{2}")
+ fun data(): Collection<Array<Any>> {
+ val counts = listOf(100)
+ val reorders = ReorderType.values()
+ val identities = IdentityType.values()
+
+ val results = mutableListOf<Array<Any>>()
+
+ for (count in counts) {
+ for (reorder in reorders) {
+ for (identity in identities) {
+ results.add(arrayOf(count, reorder, identity))
+ }
+ }
+ }
+
+ return results
+ }
+ }
+
+ @UiThreadTest
+ @Test
+ fun runBenchmark() {
+ activityRule.runUiRunnable {
+ val items = ValueHolder((0..count).map { Item(it) })
+ val random = Random(0)
+ measureRecompose {
+ compose {
+ SiblingManagement(identity = identity, items = items.value)
+ }
+ update {
+ items.value = items.value.update(reorder, random) { Item(it + 1) }
+ }
+ }
+ }
+ }
+}
+
+// NOTE: remove when SAM conversion works in IR
+fun ActivityTestRule<ComposeActivity>.runUiRunnable(block: () -> Unit) {
+ runOnUiThread(object : Runnable {
+ override fun run() {
+ block()
+ }
+ })
+}
+
+@Model private class ValueHolder<T>(var value: T)
\ No newline at end of file
diff --git a/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/dbmonster/DbMonster.kt b/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/dbmonster/DbMonster.kt
new file mode 100644
index 0000000..da720b8
--- /dev/null
+++ b/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/dbmonster/DbMonster.kt
@@ -0,0 +1,101 @@
+/*
+ * 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.compose.benchmark.dbmonster
+
+import androidx.compose.Children
+import androidx.compose.Composable
+import androidx.compose.Model
+import androidx.compose.composer
+import androidx.ui.core.Text
+import androidx.ui.layout.Column
+import androidx.ui.layout.Row
+
+import kotlin.random.Random
+
+private fun randomQuery(random: Random): String = random.nextDouble().let {
+ when {
+ it < 0.1 -> "Idle"
+ it < 0.2 -> "vacuum"
+ else -> "SELECT blah FROM something"
+ }
+}
+
+private const val MAX_ELAPSED = 15.0
+
+@Model
+class Query(random: Random, var elapsed: Double = random.nextDouble() * MAX_ELAPSED) {
+ var query = randomQuery(random)
+}
+
+@Model
+class Database(var name: String, val random: Random) {
+ var queries: List<Query> = (1..10).map {
+ Query(
+ random
+ )
+ }
+ fun topQueries(n: Int): List<Query> {
+ return queries/*.sortedByDescending { it.elapsed }*/.take(n)
+ }
+ fun update() {
+ val r = random.nextInt(queries.size)
+ (0..r).forEach {
+ queries[it].elapsed = random.nextDouble() * MAX_ELAPSED
+ }
+ }
+}
+
+class DatabaseList(n: Int, val random: Random) {
+ val databases: List<Database> = (0..n).flatMap {
+ listOf(
+ Database("cluster $it", random),
+ Database("cluster $it slave", random)
+ )
+ }
+ fun update(n: Int) {
+ // update n random databases in the list
+ databases.shuffled(random).take(n).forEach { it.update() }
+ }
+}
+
+@Composable
+fun Table(children: @Composable() () -> Unit) {
+ Column { children() }
+}
+
+@Composable
+fun QueryColumn(query: Query) {
+ // TODO: we could do some conditional styling here which would make the test better
+ Column {
+ Text(text="${query.elapsed}")
+ Text(text=query.query)
+ }
+}
+
+@Composable
+fun DatabaseRow(db: Database) {
+ println(db)
+ val columns = 5
+ val topQueries = db.topQueries(columns)
+ Row {
+ Column { Text(text=db.name) }
+ Column { Text(text="${db.queries.size}") }
+ topQueries.forEach { query ->
+ QueryColumn(query = query)
+ }
+ }
+}
diff --git a/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/deeptree/DeepTree.kt b/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/deeptree/DeepTree.kt
new file mode 100644
index 0000000..c7667bc
--- /dev/null
+++ b/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/deeptree/DeepTree.kt
@@ -0,0 +1,89 @@
+/*
+ * 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.compose.benchmark.deeptree
+
+import androidx.compose.Children
+import androidx.compose.Composable
+import androidx.compose.composer
+import androidx.ui.core.dp
+import androidx.ui.foundation.ColoredRect
+import androidx.ui.graphics.Color
+import androidx.ui.layout.Column
+import androidx.ui.layout.Row
+
+@Composable
+fun Terminal(style: Int) {
+ val color = when (style) {
+ 0 -> Color.Blue
+ 1 -> Color.Black
+ else -> Color.Fuchsia
+ }
+ ColoredRect(color = color, height = 16.dp, width = 16.dp)
+}
+
+@Composable
+fun Stack(vertical: Boolean, children: @Composable() () -> Unit) {
+ if (vertical) {
+ Column { children() }
+ } else {
+ Row { children() }
+ }
+}
+
+@Composable
+fun Container(children: @Composable() () -> Unit) {
+ // non-layout node component. just adds depth to the composition hierarchy.
+ children()
+}
+
+/**
+ *
+ * This Component will emit `breadth ^ depth` Terminal components.
+ *
+ *
+ * @param depth - increasing this will determine how many nested <Stack> elements will result in the tree. Higher
+ * numbers would be a proxy for very complicated layouts
+ *
+ * @param breadth - increasing this will increase the number of nodes at each level. Correlates to exponential
+ * growth in the number of nodes in the tree, so be careful making it too large.
+ *
+ * @param wrap - to make the depth of the composition tree greater, we can increase this and it will just wrap
+ * the component this many times at each level. It will not increase the number of layout nodes in the tree, but
+ * will make composition more expensive.
+ *
+ * @param id - an int that determines the style of the next terminal
+ */
+@Suppress("UNUSED_PARAMETER")
+@Composable
+fun DeepTree(depth: Int, breadth: Int, wrap: Int, id: Int = 0) {
+// if (wrap > 0) {
+// Container {
+// DeepTree(depth=depth, breadth=breadth, wrap=wrap - 1, id=id)
+// }
+// } else {
+ Stack(vertical=depth % 2 == 0) {
+ if (depth == 0) {
+ Terminal(style=id % 3)
+ } else {
+ repeat(breadth) {
+ ColoredRect(color = Color.Blue, height = 16.dp, width = 16.dp)
+// DeepTree(depth=depth - 1, wrap=wrap, breadth=breadth, id=id)
+ }
+ }
+ }
+// }
+}
\ No newline at end of file
diff --git a/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/realworld4/RealWorld4_Widgets.kt b/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/realworld4/RealWorld4_Widgets.kt
index 20e1b98..eda5f71 100644
--- a/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/realworld4/RealWorld4_Widgets.kt
+++ b/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/realworld4/RealWorld4_Widgets.kt
@@ -28,7 +28,7 @@
import androidx.ui.foundation.ColoredRect
import androidx.ui.core.dp
import androidx.ui.core.toRect
-import androidx.ui.core.vectorgraphics.SolidColor
+import androidx.ui.graphics.vectorgraphics.SolidColor
import androidx.ui.painting.Paint
import androidx.ui.graphics.Color
import androidx.ui.layout.FlexColumn
diff --git a/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/siblings/SiblingManagement.kt b/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/siblings/SiblingManagement.kt
new file mode 100644
index 0000000..f91235e
--- /dev/null
+++ b/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/siblings/SiblingManagement.kt
@@ -0,0 +1,133 @@
+/*
+ * 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.compose.benchmark.siblings
+
+import androidx.compose.Children
+import androidx.compose.Composable
+import androidx.compose.Key
+import androidx.compose.composer
+import androidx.compose.Pivotal
+import androidx.ui.core.Text
+import androidx.ui.core.dp
+import androidx.ui.foundation.ColoredRect
+import androidx.ui.graphics.Color
+import androidx.ui.layout.Column
+import androidx.ui.layout.Row
+import androidx.ui.text.TextStyle
+import kotlin.random.Random
+
+@Composable
+fun Stack(children: @Composable() () -> Unit) {
+ Column {
+ children()
+ }
+}
+
+@Composable
+fun PivotalItemRow(@Pivotal item: Item) {
+ val color = when (item.id % 3) {
+ 0 -> Color.Blue
+ 1 -> Color.Black
+ else -> Color.Fuchsia
+ }
+ Row {
+ ColoredRect(color=color, width = 16.dp, height = 16.dp)
+ Text(text="${item.id}", style = TextStyle(color=color))
+ }
+}
+
+@Composable
+fun ItemRow(item: Item) {
+ // the complexity of this will influence the benchmark a lot because if
+ // identity doesn't influence what the component looks like, it's not
+ // very important to track it.
+ val color = when (item.id % 3) {
+ 0 -> Color.Blue
+ 1 -> Color.Black
+ else -> Color.Fuchsia
+ }
+ Row {
+ ColoredRect(color=color, width = 16.dp, height = 16.dp)
+ Text(text="${item.id}", style = TextStyle(color=color))
+ }
+}
+
+data class Item(val id: Int)
+
+enum class IdentityType { Pivotal, Index, Key }
+
+enum class ReorderType {
+ Shuffle, ShiftRight, ShiftLeft, Swap,
+ AddEnd, RemoveEnd,
+ AddStart, RemoveStart,
+ AddMiddle, RemoveMiddle
+}
+
+fun <T> List<T>.move(from: Int, to: Int): List<T> {
+ if (to < from) return move(to, from)
+ if (from == to) return this
+ val item = get(from)
+ val currentItem = get(to)
+ val left = if (from > 0) subList(0, from) else emptyList()
+ val right = if (to < size) subList(to + 1, size) else emptyList()
+ val middle = if (to - from > 1) subList(from + 1, to) else emptyList()
+ return left + listOf(currentItem) + middle + listOf(item) + right
+}
+
+fun <T> List<T>.update(reorderType: ReorderType, random: Random, factory: (Int) -> T): List<T> {
+ // NOTE: might be some off by one errors in here :)
+ @Suppress("ReplaceSingleLineLet")
+ return when (reorderType) {
+ ReorderType.Shuffle -> shuffled(random)
+ ReorderType.ShiftRight -> listOf(get(size - 1)) + subList(0, size - 1)
+ ReorderType.ShiftLeft -> subList(1, size) + listOf(get(0))
+ ReorderType.Swap -> move(random.nextInt(size), random.nextInt(size))
+ ReorderType.AddEnd -> this + listOf(factory(size))
+ ReorderType.RemoveEnd -> dropLast(1)
+ ReorderType.AddStart -> listOf(factory(size)) + this
+ ReorderType.RemoveStart -> drop(1)
+ ReorderType.AddMiddle -> random.nextInt(size).let {
+ subList(0, it) + listOf(factory(size)) + subList(it, size)
+ }
+ ReorderType.RemoveMiddle -> random.nextInt(size).let { filterIndexed { i, _ -> i == it } }
+ }
+}
+
+@Composable
+fun SiblingManagement(identity: IdentityType, items: List<Item>) {
+ Stack {
+ when (identity) {
+ IdentityType.Pivotal -> {
+ for (item in items) {
+ PivotalItemRow(item=item)
+ }
+ }
+ IdentityType.Index -> {
+ for (item in items) {
+ ItemRow(item=item)
+ }
+ }
+ IdentityType.Key -> {
+ for (item in items) {
+ Key(key=item.id) {
+ ItemRow(item=item)
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/benchmark/build.gradle b/compose/compose-runtime/integration-tests/android-tests/build.gradle
similarity index 64%
copy from benchmark/build.gradle
copy to compose/compose-runtime/integration-tests/android-tests/build.gradle
index 386f71d..e733507 100644
--- a/benchmark/build.gradle
+++ b/compose/compose-runtime/integration-tests/android-tests/build.gradle
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * 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.
@@ -14,6 +14,10 @@
* limitations under the License.
*/
+
+import org.jetbrains.kotlin.codegen.ImplementationBodyCodegen
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
import static androidx.build.dependencies.DependenciesKt.*
import androidx.build.LibraryGroups
import androidx.build.LibraryVersions
@@ -26,20 +30,16 @@
}
dependencies {
- implementation(ANDROIDX_TEST_RULES)
- implementation(ANDROIDX_TEST_RUNNER)
- implementation(KOTLIN_STDLIB)
- implementation(SUPPORT_ANNOTATIONS)
+ androidTestImplementation(project(":compose:compose-runtime"))
+ androidTestImplementation(project(":compose:compose-runtime:integration-tests"))
- androidTestImplementation(ANDROIDX_TEST_CORE)
+ androidTestImplementation(KOTLIN_COMPOSE_STDLIB)
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
+ androidTestImplementation(ANDROIDX_TEST_RULES)
}
-androidx {
- name = "Android Benchmark"
- publish = Publish.SNAPSHOT_AND_RELEASE
- mavenVersion = LibraryVersions.BENCHMARK
- mavenGroup = LibraryGroups.BENCHMARK
- inceptionYear = "2018"
- description = "Android Benchmark"
+android {
+ defaultConfig {
+ minSdkVersion 18
+ }
}
diff --git a/benchmark/src/androidTest/AndroidManifest.xml b/compose/compose-runtime/integration-tests/android-tests/src/androidTest/AndroidManifest.xml
similarity index 62%
copy from benchmark/src/androidTest/AndroidManifest.xml
copy to compose/compose-runtime/integration-tests/android-tests/src/androidTest/AndroidManifest.xml
index f5ec776..bb550d8 100644
--- a/benchmark/src/androidTest/AndroidManifest.xml
+++ b/compose/compose-runtime/integration-tests/android-tests/src/androidTest/AndroidManifest.xml
@@ -15,12 +15,13 @@
limitations under the License.
-->
<manifest
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- package="androidx.benchmark.test">
-
- <application
- android:name="androidx.benchmark.ArgumentInjectingApplication">
- <activity android:name="android.app.Activity"/>
+ package="androidx.compose.integrationtests.androidtests" xmlns:android="http://schemas.android.com/apk/res/android">
+ <application>
+ <activity
+ android:name="androidx.compose.TestActivity"/>
+ <activity
+ android:name="androidx.compose.ComposeIntoTestActivity"/>
+ <activity
+ android:name="androidx.compose.DisposeTests$DisposeTestActivity"/>
</application>
-</manifest>
\ No newline at end of file
+</manifest>
diff --git a/compose/compose-runtime/src/test/java/androidx/compose/ComposeIntoTests.kt b/compose/compose-runtime/integration-tests/android-tests/src/androidTest/java/androidx/compose/ComposeIntoTests.kt
similarity index 68%
rename from compose/compose-runtime/src/test/java/androidx/compose/ComposeIntoTests.kt
rename to compose/compose-runtime/integration-tests/android-tests/src/androidTest/java/androidx/compose/ComposeIntoTests.kt
index 14d1805..00dc77a 100644
--- a/compose/compose-runtime/src/test/java/androidx/compose/ComposeIntoTests.kt
+++ b/compose/compose-runtime/integration-tests/android-tests/src/androidTest/java/androidx/compose/ComposeIntoTests.kt
@@ -17,28 +17,32 @@
package androidx.compose
import android.app.Activity
-import android.os.Bundle
-import junit.framework.TestCase
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.rule.ActivityTestRule
+import org.junit.Assert.assertEquals
+import org.junit.Ignore
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.robolectric.Robolectric
-import org.robolectric.annotation.Config
-@RunWith(ComposeRobolectricTestRunner::class)
-@Config(
- manifest = Config.NONE,
- minSdk = 23,
- maxSdk = 23
-)
-class ComposeIntoTests : TestCase() {
+@RunWith(AndroidJUnit4::class)
+class ComposeIntoTests {
+
+ @get:Rule
+ val activityRule = ActivityTestRule(ComposeIntoTestActivity::class.java)
@Test
+ @SmallTest
+ @Ignore("TODO(b/138720405): Investigate synchronisation issues in tests")
fun testMultipleSetContentCalls() {
- val controller = Robolectric.buildActivity(ComposeIntoTestActivity::class.java)
- val activity = controller.create().get() as ComposeIntoTestActivity
+ val activity = activityRule.activity
+ activity.run()
+
assertEquals(1, activity.initializationCount)
assertEquals(1, activity.commitCount)
activity.run()
+
// if we call setContent multiple times, we want to ensure that it doesn't tear
// down the whole hierarchy, so onActive should only get called once.
assertEquals(1, activity.initializationCount)
@@ -46,13 +50,10 @@
}
}
-private class ComposeIntoTestActivity : Activity() {
+class ComposeIntoTestActivity : Activity() {
var initializationCount = 0
var commitCount = 0
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- run()
- }
+
fun run() {
setViewContent {
+onActive { initializationCount++ }
diff --git a/compose/compose-runtime/src/test/java/androidx/compose/ComposeModelTests.kt b/compose/compose-runtime/integration-tests/android-tests/src/androidTest/java/androidx/compose/ComposeModelTests.kt
similarity index 90%
rename from compose/compose-runtime/src/test/java/androidx/compose/ComposeModelTests.kt
rename to compose/compose-runtime/integration-tests/android-tests/src/androidTest/java/androidx/compose/ComposeModelTests.kt
index ea060c3..cfd4457 100644
--- a/compose/compose-runtime/src/test/java/androidx/compose/ComposeModelTests.kt
+++ b/compose/compose-runtime/integration-tests/android-tests/src/androidTest/java/androidx/compose/ComposeModelTests.kt
@@ -17,10 +17,7 @@
package androidx.compose
import android.app.Activity
-import android.os.Bundle
-import android.view.Choreographer
import android.view.ViewGroup
-import android.widget.LinearLayout
import android.widget.TextView
import androidx.compose.frames.AbstractRecord
import androidx.compose.frames.Framed
@@ -29,13 +26,16 @@
import androidx.compose.frames._readable
import androidx.compose.frames._writable
import androidx.compose.frames.currentFrame
-import junit.framework.TestCase
-import org.junit.Before
+import androidx.test.annotation.UiThreadTest
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.rule.ActivityTestRule
+import junit.framework.TestCase.assertEquals
+import junit.framework.TestCase.assertFalse
+import org.junit.Ignore
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.robolectric.Robolectric
-import org.robolectric.RuntimeEnvironment
-import org.robolectric.annotation.Config
val PRESIDENT_NAME_1 = "George Washington"
val PRESIDENT_AGE_1 = 57
@@ -120,21 +120,15 @@
}
}
-@RunWith(ComposeRobolectricTestRunner::class)
-@Config(
- manifest = Config.NONE,
- minSdk = 23,
- maxSdk = 23
-)
-class ModelViewTests : TestCase() {
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ModelViewTests {
- @Before
- fun beforeTest() {
- val scheduler = RuntimeEnvironment.getMasterScheduler()
- scheduler.pause()
- }
+ @get:Rule
+ val activityRule = ActivityTestRule(TestActivity::class.java)
@Test
+ @UiThreadTest
fun testModelView_Simple(): Unit = FrameManager.isolated {
val tvId = 67
compose {
@@ -150,6 +144,7 @@
}
@Test
+ @UiThreadTest
fun testModelView_Simple_Recompose(): Unit = FrameManager.isolated {
val tvId = 71
compose {
@@ -168,6 +163,8 @@
}
@Test
+ @Ignore("TODO(b/138720405): Investigate synchronisation issues in tests")
+ @UiThreadTest
fun testModelView_PersonModel(): Unit = FrameManager.isolated {
val tvIdName = 90
val tvIdAge = 91
@@ -204,6 +201,7 @@
}
@Test
+ @UiThreadTest
fun testModelView_RecomposeScopeCleanup(): Unit = FrameManager.isolated {
val washington = Person(
PRESIDENT_NAME_1,
@@ -249,6 +247,7 @@
// b/122548164
@Test
+ @Ignore("TODO(b/138720405): Investigate synchronisation issues in tests")
fun testObserverEntering(): Unit = FrameManager.isolated {
val president = Person(
PRESIDENT_NAME_1,
@@ -297,6 +296,7 @@
}
@Test
+ @Ignore("TODO(b/138720405): Investigate synchronisation issues in tests")
fun testModelUpdatesNextFrameVisibility(): Unit = FrameManager.isolated {
val president = Person(
PRESIDENT_NAME_1,
@@ -344,7 +344,7 @@
president.name = PRESIDENT_NAME_16
// check that changes aren't there yet
assertEquals(PRESIDENT_NAME_1, (activity.findViewById(tvName) as TextView).text)
- Choreographer.getInstance().postFrameCallback {
+ Choreographer.postFrameCallback {
// after one frame we should see changes
assertEquals(PRESIDENT_NAME_16, (activity.findViewById(tvName) as TextView).text)
}
@@ -358,15 +358,13 @@
}
fun compose(block: ViewComposition.() -> Unit) =
- CompositionModelTest(block)
+ CompositionModelTest(block, activityRule.activity)
- class CompositionModelTest(val composable: ViewComposition.() -> Unit) {
+ class CompositionModelTest(val composable: ViewComposition.() -> Unit, val activity: Activity) {
var savedContext: CompositionContext? = null
inner class ActiveTest(val activity: Activity) {
private var firstCompose = true
private fun compose() {
- val scheduler = RuntimeEnvironment.getMasterScheduler()
- scheduler.advanceToLastPostedRunnable()
if (firstCompose) {
val composer = composer.composer
composer.startRoot()
@@ -392,8 +390,6 @@
}
fun then(block: (activity: Activity) -> Unit): ActiveTest {
- val controller = Robolectric.buildActivity(FrameTestActivity::class.java)
- val activity = controller.create().get()
val cc = Compose.createCompositionContext(
activity,
activity.root,
@@ -412,13 +408,3 @@
}
private val Activity.root get() = findViewById(ComposerComposeTestCase.ROOT_ID) as ViewGroup
-
-private class FrameTestActivity : Activity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(LinearLayout(this).apply {
- id =
- ComposerComposeTestCase.ROOT_ID
- })
- }
-}
diff --git a/compose/compose-runtime/src/test/java/androidx/compose/ComposerComposeTestCase.kt b/compose/compose-runtime/integration-tests/android-tests/src/androidTest/java/androidx/compose/ComposerComposeTestCase.kt
similarity index 63%
rename from compose/compose-runtime/src/test/java/androidx/compose/ComposerComposeTestCase.kt
rename to compose/compose-runtime/integration-tests/android-tests/src/androidTest/java/androidx/compose/ComposerComposeTestCase.kt
index 90dbe54..6339325 100644
--- a/compose/compose-runtime/src/test/java/androidx/compose/ComposerComposeTestCase.kt
+++ b/compose/compose-runtime/integration-tests/android-tests/src/androidTest/java/androidx/compose/ComposerComposeTestCase.kt
@@ -17,30 +17,19 @@
package androidx.compose
import android.app.Activity
-import android.os.Bundle
import android.view.ViewGroup
-import android.widget.LinearLayout
-import junit.framework.TestCase
-import org.junit.runner.RunWith
-import org.robolectric.Robolectric
-import org.robolectric.annotation.Config
-@RunWith(ComposeRobolectricTestRunner::class)
-@Config(
- manifest = Config.NONE,
- minSdk = 23,
- maxSdk = 23
-)
-abstract class ComposerComposeTestCase : TestCase() {
- fun compose(composable: (ViewComposition) -> Unit) =
+abstract class ComposerComposeTestCase {
+
+ fun compose(activity: Activity, composable: (ViewComposition) -> Unit) =
ComposeTest(
- Root(composable)
+ Root(composable),
+ activity
)
- class ComposeTest(val component: Component) {
+ class ComposeTest(val component: Component, val activity: Activity) {
+
fun then(fn: (CompositionContext, Component, ViewGroup, Activity) -> Unit) {
- val controller = Robolectric.buildActivity(TestActivity::class.java)
- val activity = controller.create().get()
val root = activity.findViewById(ROOT_ID) as ViewGroup
val cc = Compose.createCompositionContext(root.context, root, component, null)
cc.compose()
@@ -52,15 +41,6 @@
override fun compose() = composable(composer)
}
- private class TestActivity : Activity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(LinearLayout(this).apply { id =
- ROOT_ID
- })
- }
- }
-
companion object {
val ROOT_ID = 18284847
}
diff --git a/compose/compose-runtime/src/test/java/androidx/compose/ComposerExtensions.kt b/compose/compose-runtime/integration-tests/android-tests/src/androidTest/java/androidx/compose/ComposerExtensions.kt
similarity index 100%
rename from compose/compose-runtime/src/test/java/androidx/compose/ComposerExtensions.kt
rename to compose/compose-runtime/integration-tests/android-tests/src/androidTest/java/androidx/compose/ComposerExtensions.kt
diff --git a/compose/compose-runtime/src/test/java/androidx/compose/CompositionTests.kt b/compose/compose-runtime/integration-tests/android-tests/src/androidTest/java/androidx/compose/CompositionTests.kt
similarity index 97%
rename from compose/compose-runtime/src/test/java/androidx/compose/CompositionTests.kt
rename to compose/compose-runtime/integration-tests/android-tests/src/androidTest/java/androidx/compose/CompositionTests.kt
index 6a58b69..cd26098 100644
--- a/compose/compose-runtime/src/test/java/androidx/compose/CompositionTests.kt
+++ b/compose/compose-runtime/integration-tests/android-tests/src/androidTest/java/androidx/compose/CompositionTests.kt
@@ -43,9 +43,11 @@
import androidx.compose.mock.text
import androidx.compose.mock.update
import androidx.compose.mock.validate
+import androidx.test.filters.SmallTest
import junit.framework.TestCase
import org.junit.Assert
+@SmallTest
class CompositionTests : TestCase() {
fun testComposeAModel() {
val model = testModel()
@@ -210,6 +212,52 @@
}
}
+ fun testReplace() {
+ var includeA = true
+ fun MockViewComposition.composition() {
+ text("Before")
+ if (includeA) {
+ linear {
+ text("A")
+ }
+ } else {
+ edit("B")
+ }
+ text("After")
+ }
+ fun MockViewValidator.composition() {
+ text("Before")
+ if (includeA) {
+ linear {
+ text("A")
+ }
+ } else {
+ edit("B")
+ }
+ text("After")
+ }
+ val composer = compose {
+ composition()
+ }
+ validate(composer.root) {
+ composition()
+ }
+ includeA = false
+ compose(composer) {
+ composition()
+ }
+ validate(composer.root) {
+ composition()
+ }
+ includeA = true
+ compose(composer) {
+ composition()
+ }
+ validate(composer.root) {
+ composition()
+ }
+ }
+
fun testInsertWithMultipleRoots() {
val chars = listOf('a', 'b', 'c')
diff --git a/compose/compose-runtime/src/test/java/androidx/compose/EffectsTests.kt b/compose/compose-runtime/integration-tests/android-tests/src/androidTest/java/androidx/compose/EffectsTests.kt
similarity index 94%
rename from compose/compose-runtime/src/test/java/androidx/compose/EffectsTests.kt
rename to compose/compose-runtime/integration-tests/android-tests/src/androidTest/java/androidx/compose/EffectsTests.kt
index f3655ef..a8c8971 100644
--- a/compose/compose-runtime/src/test/java/androidx/compose/EffectsTests.kt
+++ b/compose/compose-runtime/integration-tests/android-tests/src/androidTest/java/androidx/compose/EffectsTests.kt
@@ -17,38 +17,30 @@
package androidx.compose
import android.app.Activity
-import android.os.Bundle
import android.view.ViewGroup
import android.widget.Button
-import android.widget.LinearLayout
import android.widget.TextView
-import junit.framework.TestCase
+import androidx.test.annotation.UiThreadTest
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.rule.ActivityTestRule
import junit.framework.TestCase.assertEquals
+import junit.framework.TestCase.assertFalse
+import junit.framework.TestCase.assertTrue
+import org.junit.Ignore
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.robolectric.Robolectric
-import org.robolectric.RuntimeEnvironment
-import org.robolectric.annotation.Config
-private class EffectTestActivity : Activity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(LinearLayout(this).apply {
- id =
- ComposerComposeTestCase.ROOT_ID
- })
- }
-}
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class EffectsTests {
-@RunWith(ComposeRobolectricTestRunner::class)
-@Config(
- manifest = Config.NONE,
- minSdk = 23,
- maxSdk = 23
-)
-class EffectsTests : TestCase() {
+ @get:Rule
+ val activityRule = ActivityTestRule(TestActivity::class.java)
@Test
+ @UiThreadTest
fun testMemoization1() {
var inc = 0
@@ -62,6 +54,7 @@
}
@Test
+ @UiThreadTest
fun testMemoization2() {
var calculations = 0
var compositions = 0
@@ -97,6 +90,7 @@
}
@Test
+ @UiThreadTest
fun testState1() {
val tv1Id = 100
var inc = 0
@@ -127,6 +121,7 @@
}
@Test
+ @UiThreadTest
fun testState2() {
val tv1Id = 100
val tv2Id = 200
@@ -173,6 +168,7 @@
}
@Test
+ @UiThreadTest
fun testPreCommit1() {
var mount = true
@@ -233,6 +229,7 @@
}
@Test
+ @UiThreadTest
fun testPreCommit2() {
var mount = true
@@ -308,6 +305,7 @@
}
@Test
+ @UiThreadTest
fun testPreCommit3() {
var x = 0
@@ -338,6 +336,7 @@
}
@Test
+ @UiThreadTest
fun testPreCommit31() {
var a = 0
var b = 0
@@ -379,6 +378,7 @@
}
@Test
+ @UiThreadTest
fun testPreCommit4() {
var x = 0
var key = 123
@@ -421,6 +421,7 @@
}
@Test
+ @UiThreadTest
fun testPreCommit5() {
var a = 0
var b = 0
@@ -484,6 +485,7 @@
}
@Test
+ @Ignore("TODO(b/138720405): Investigate synchronisation issues in tests")
fun testOnCommit1() {
var mount = true
@@ -514,10 +516,6 @@
log("Unmountable:end")
}
- val scheduler = RuntimeEnvironment.getMasterScheduler()
-
- scheduler.pause()
-
compose {
with(composer) {
log("compose:start")
@@ -531,7 +529,6 @@
log("compose:end")
}
}.then { _ ->
- scheduler.unPause()
assertArrayEquals(
listOf(
"compose:start",
@@ -545,9 +542,7 @@
logHistory
)
mount = false
- scheduler.pause()
}.then { _ ->
- scheduler.unPause()
assertArrayEquals(
listOf(
"compose:start",
@@ -569,6 +564,7 @@
}
@Test
+ @UiThreadTest
fun testAmbient1() {
val tv1Id = 100
@@ -615,6 +611,7 @@
}
@Test
+ @UiThreadTest
fun testAmbient2() {
val MyAmbient = Ambient.of<Double>("Hello") { throw Exception("not set") }
@@ -676,6 +673,7 @@
}
@Test
+ @UiThreadTest
fun testUpdatedComposition() {
val tv1Id = 100
var inc = 0
@@ -696,7 +694,8 @@
}
}
- class CompositionTest(val composable: () -> Unit) {
+ class CompositionTest(val composable: () -> Unit, val activity: Activity) {
+
inner class ActiveTest(
val activity: Activity,
val cc: CompositionContext,
@@ -717,8 +716,6 @@
}
fun then(block: (activity: Activity) -> Unit): ActiveTest {
- val controller = Robolectric.buildActivity(EffectTestActivity::class.java)
- val activity = controller.create().get()
val root = activity.root
val component = Root(composable)
val cc = Compose.createCompositionContext(root.context, root, component, null)
@@ -726,7 +723,7 @@
}
}
- fun compose(composable: () -> Unit) = CompositionTest(composable)
+ fun compose(composable: () -> Unit) = CompositionTest(composable, activityRule.activity)
}
private val Activity.root get() = findViewById(ComposerComposeTestCase.ROOT_ID) as ViewGroup
diff --git a/compose/compose-runtime/src/test/java/androidx/compose/ObserverMapTests.kt b/compose/compose-runtime/integration-tests/android-tests/src/androidTest/java/androidx/compose/ObserverMapTests.kt
similarity index 94%
rename from compose/compose-runtime/src/test/java/androidx/compose/ObserverMapTests.kt
rename to compose/compose-runtime/integration-tests/android-tests/src/androidTest/java/androidx/compose/ObserverMapTests.kt
index f33e5f4..7c6cf81 100644
--- a/compose/compose-runtime/src/test/java/androidx/compose/ObserverMapTests.kt
+++ b/compose/compose-runtime/integration-tests/android-tests/src/androidTest/java/androidx/compose/ObserverMapTests.kt
@@ -16,18 +16,12 @@
package androidx.compose
+import androidx.test.filters.SmallTest
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
-import org.junit.runner.RunWith
-import org.robolectric.annotation.Config
-@RunWith(ComposeRobolectricTestRunner::class)
-@Config(
- manifest = Config.NONE,
- minSdk = 23,
- maxSdk = 23
-)
+@SmallTest
class ObserverMapTests {
private val node1 = 1
diff --git a/compose/compose-runtime/src/test/java/androidx/compose/RecomposerTests.kt b/compose/compose-runtime/integration-tests/android-tests/src/androidTest/java/androidx/compose/RecomposerTests.kt
similarity index 91%
rename from compose/compose-runtime/src/test/java/androidx/compose/RecomposerTests.kt
rename to compose/compose-runtime/integration-tests/android-tests/src/androidTest/java/androidx/compose/RecomposerTests.kt
index e03abca..b19ebed 100644
--- a/compose/compose-runtime/src/test/java/androidx/compose/RecomposerTests.kt
+++ b/compose/compose-runtime/integration-tests/android-tests/src/androidTest/java/androidx/compose/RecomposerTests.kt
@@ -21,21 +21,33 @@
import android.widget.LinearLayout
import android.widget.TextView
import androidx.compose.frames.currentFrame
+import androidx.test.annotation.UiThreadTest
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SmallTest
+import androidx.test.rule.ActivityTestRule
import junit.framework.TestCase
+import junit.framework.TestCase.assertEquals
+import junit.framework.TestCase.assertFalse
+import junit.framework.TestCase.assertNotSame
+import junit.framework.TestCase.assertTrue
+import org.junit.Ignore
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.robolectric.RuntimeEnvironment
-import org.robolectric.annotation.Config
-@RunWith(ComposeRobolectricTestRunner::class)
-@Config(
- manifest = Config.NONE,
- minSdk = 23,
- maxSdk = 23
-)
+@RunWith(AndroidJUnit4::class)
class ComposerCompositionContextTests : ComposerComposeTestCase() {
+ @get:Rule
+ val activityRule = ActivityTestRule(TestActivity::class.java)
+
+ private fun compose(composable: (ViewComposition) -> Unit) =
+ compose(activityRule.activity, composable)
+
@Test
+ @SmallTest
+ @UiThreadTest
fun testNativeViewWithAttributes() = compose {
with(it) {
// <TextView id={456} text="some text" />
@@ -54,6 +66,8 @@
}
@Test
+ @SmallTest
+ @Ignore("TODO(b/138720405): Investigate synchronisation issues in tests")
fun testSlotKeyChangeCausesRecreate() {
var i = 1
@@ -91,6 +105,8 @@
}
@Test
+ @SmallTest
+ @UiThreadTest
fun testViewWithViewChildren() {
compose {
// <LinearLayout id={345}>
@@ -129,6 +145,8 @@
}
@Test
+ @SmallTest
+ @UiThreadTest
fun testForLoop() {
val items = listOf(1, 2, 3, 4, 5, 6)
compose {
@@ -161,6 +179,9 @@
}
@Test
+ @SmallTest
+ @UiThreadTest
+ @Ignore("TODO(b/138720405): Investigate synchronisation issues in tests")
fun testRecompose() {
val counter = Counter()
@@ -179,10 +200,6 @@
assertEquals(1, counter["101"])
assertEquals(1, counter["102"])
- // Robolectric will by default just run everything sync. pause() is needed to emulate
- // delays
- RuntimeEnvironment.getMasterScheduler().pause()
-
(activity.findViewById(100) as TextView).performClick()
(activity.findViewById(102) as TextView).performClick()
@@ -192,24 +209,17 @@
assertEquals(1, counter["101"])
assertEquals(1, counter["102"])
- RuntimeEnvironment.getMasterScheduler().unPause()
-
// only the clicked view got rerendered
assertEquals(1, counter["A"])
assertEquals(2, counter["100"])
assertEquals(1, counter["101"])
assertEquals(2, counter["102"])
- RuntimeEnvironment.getMasterScheduler().pause()
-
// recompose() both the parent and the child... and show that the child only
// recomposes once as a result
(activity.findViewById(99) as LinearLayout).performClick()
(activity.findViewById(102) as TextView).performClick()
- RuntimeEnvironment.getMasterScheduler().unPause()
- RuntimeEnvironment.getMasterScheduler().advanceToLastPostedRunnable()
-
assertEquals(2, counter["A"])
assertEquals(2, counter["100"])
assertEquals(1, counter["101"])
@@ -218,6 +228,8 @@
}
@Test
+ @SmallTest
+ @UiThreadTest
fun testRecomposeSync() {
val counter = Counter()
@@ -236,9 +248,6 @@
assertEquals(1, counter["101"])
assertEquals(1, counter["102"])
- // stop the time so only sync recomposes will take place
- RuntimeEnvironment.getMasterScheduler().pause()
-
(activity.findViewById(100) as TextView).performClick()
// only the clicked view got rerendered
@@ -247,15 +256,11 @@
assertEquals(1, counter["101"])
assertEquals(1, counter["102"])
- // unpause to see if nothing is scheduled during recomposeSync()
- RuntimeEnvironment.getMasterScheduler().unPause()
-
assertEquals(1, counter["A"])
assertEquals(2, counter["100"])
assertEquals(1, counter["101"])
assertEquals(1, counter["102"])
- RuntimeEnvironment.getMasterScheduler().pause()
// try to recompose the parent, but ensure that even if we tap textView several times,
// it's all got recomposed once
(activity.findViewById(99) as LinearLayout).performClick()
@@ -269,12 +274,13 @@
assertEquals(5, counter["100"])
assertEquals(1, counter["101"])
assertEquals(1, counter["102"])
-
- RuntimeEnvironment.getMasterScheduler().unPause()
}
}
@Test
+ @SmallTest
+ @UiThreadTest
+ @Ignore("TODO(b/138720405): Investigate synchronisation issues in tests")
fun testRootRecompose() {
val counter = Counter()
@@ -300,10 +306,6 @@
assertEquals(1, counter["101"])
assertEquals(1, counter["102"])
- // Robolectric will by default just run everything sync. pause() is needed to emulate
- // delays
- RuntimeEnvironment.getMasterScheduler().pause()
-
(activity.findViewById(100) as TextView).performClick()
(activity.findViewById(102) as TextView).performClick()
@@ -313,8 +315,6 @@
assertEquals(1, counter["101"])
assertEquals(1, counter["102"])
- RuntimeEnvironment.getMasterScheduler().unPause()
-
// as we recompose ROOT on every tap, only root(and LinearLayout) counter should we
// increased once, because two clicks layed to one frame
assertEquals(2, counter["A"])
@@ -322,14 +322,9 @@
assertEquals(1, counter["101"])
assertEquals(1, counter["102"])
- RuntimeEnvironment.getMasterScheduler().pause()
-
(activity.findViewById(99) as LinearLayout).performClick()
(activity.findViewById(102) as TextView).performClick()
- RuntimeEnvironment.getMasterScheduler().unPause()
- RuntimeEnvironment.getMasterScheduler().advanceToLastPostedRunnable()
-
// again, no matter what we tappes, we want to recompose root, so LinearLayout's counter
// got increased
assertEquals(3, counter["A"])
@@ -340,6 +335,8 @@
}
@Test
+ @SmallTest
+ @UiThreadTest
fun testRootRecomposeSync() {
val counter = Counter()
@@ -364,9 +361,6 @@
assertEquals(1, counter["101"])
assertEquals(1, counter["102"])
- // stop the time so only sync recomposes will take place
- RuntimeEnvironment.getMasterScheduler().pause()
-
(activity.findViewById(100) as TextView).performClick()
// important! as we recompose Root every time
@@ -383,8 +377,6 @@
assertEquals(1, counter["101"])
assertEquals(1, counter["102"])
- RuntimeEnvironment.getMasterScheduler().unPause()
- RuntimeEnvironment.getMasterScheduler().advanceToLastPostedRunnable()
// make sure nothing has been scheduled inside recomposeSync()
assertEquals(3, counter["A"])
assertEquals(1, counter["100"])
@@ -464,6 +456,8 @@
}
@Test
+ @SmallTest
+ @UiThreadTest
fun testCorrectViewTree() = compose {
// <LinearLayout>
// <LinearLayout />
@@ -491,6 +485,8 @@
}
@Test
+ @SmallTest
+ @UiThreadTest
fun testCorrectViewTreeWithComponents() {
class B : Component() {
@@ -540,6 +536,8 @@
}
@Test
+ @MediumTest
+ @UiThreadTest
fun testCorrectViewTreeWithComponentWithMultipleRoots() {
class B : Component() {
@@ -592,6 +590,8 @@
}
@Test
+ @SmallTest
+ @UiThreadTest
fun testFrameTransition() {
var frameId: Int? = null
compose {
diff --git a/compose/compose-runtime/src/test/java/androidx/compose/ReconciliationInternalTests.kt b/compose/compose-runtime/integration-tests/android-tests/src/androidTest/java/androidx/compose/ReconciliationInternalTests.kt
similarity index 100%
rename from compose/compose-runtime/src/test/java/androidx/compose/ReconciliationInternalTests.kt
rename to compose/compose-runtime/integration-tests/android-tests/src/androidTest/java/androidx/compose/ReconciliationInternalTests.kt
diff --git a/compose/compose-runtime/src/test/java/androidx/compose/SlotTableTests.kt b/compose/compose-runtime/integration-tests/android-tests/src/androidTest/java/androidx/compose/SlotTableTests.kt
similarity index 99%
rename from compose/compose-runtime/src/test/java/androidx/compose/SlotTableTests.kt
rename to compose/compose-runtime/integration-tests/android-tests/src/androidTest/java/androidx/compose/SlotTableTests.kt
index b6bd33c..8d4f8ac 100644
--- a/compose/compose-runtime/src/test/java/androidx/compose/SlotTableTests.kt
+++ b/compose/compose-runtime/integration-tests/android-tests/src/androidTest/java/androidx/compose/SlotTableTests.kt
@@ -16,9 +16,11 @@
package androidx.compose
+import androidx.test.filters.SmallTest
import junit.framework.TestCase
import org.junit.Assert
+@SmallTest
class SlotTableTests : TestCase() {
fun testCanCreate() {
SlotTable()
diff --git a/compose/compose-runtime/src/test/java/androidx/compose/ViewComposerTests.kt b/compose/compose-runtime/integration-tests/android-tests/src/androidTest/java/androidx/compose/ViewComposerTests.kt
similarity index 91%
rename from compose/compose-runtime/src/test/java/androidx/compose/ViewComposerTests.kt
rename to compose/compose-runtime/integration-tests/android-tests/src/androidTest/java/androidx/compose/ViewComposerTests.kt
index 8f3ebb8..08a097b 100644
--- a/compose/compose-runtime/src/test/java/androidx/compose/ViewComposerTests.kt
+++ b/compose/compose-runtime/integration-tests/android-tests/src/androidTest/java/androidx/compose/ViewComposerTests.kt
@@ -24,14 +24,17 @@
import android.widget.FrameLayout
import android.widget.LinearLayout
import android.widget.TextView
-import junit.framework.TestCase
+import androidx.test.annotation.UiThreadTest
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.rule.ActivityTestRule
+import junit.framework.TestCase.assertEquals
+import org.junit.Ignore
+import org.junit.Rule
import org.junit.runner.RunWith
import org.junit.Test
-import org.robolectric.Robolectric
-import org.robolectric.RuntimeEnvironment
-import org.robolectric.annotation.Config
-private class TestActivity : Activity() {
+class TestActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(LinearLayout(this).apply {
@@ -41,15 +44,15 @@
}
}
-@RunWith(ComposeRobolectricTestRunner::class)
-@Config(
- manifest = Config.NONE,
- minSdk = 23,
- maxSdk = 23
-)
-class NewCodeGenTests : TestCase() {
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class NewCodeGenTests {
+
+ @get:Rule
+ val activityRule = ActivityTestRule(TestActivity::class.java)
@Test
+ @UiThreadTest
fun testStaticComposition() {
val tv1Id = 100
val tv2Id = 200
@@ -81,6 +84,7 @@
}
@Test
+ @UiThreadTest
fun testUpdatedComposition() {
val tv1Id = 100
val tv2Id = 200
@@ -119,56 +123,7 @@
}
@Test
- fun testDisposeComposition() {
- class DisposeTestActivity : Activity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- val root = FrameLayout(this)
- val log = mutableListOf<String>()
- val composable = @Composable {
- +onPreCommit {
- log.add("onPreCommit")
- onDispose {
- log.add("onPreCommitDispose")
- }
- }
- +onActive {
- log.add("onActive")
- onDispose {
- log.add("onActiveDispose")
- }
- }
- }
-
- val scheduler = RuntimeEnvironment.getMasterScheduler()
-
- log.clear()
- Compose.composeInto(container = root, composable = composable)
- scheduler.advanceToLastPostedRunnable()
- assertEquals("onPreCommit, onActive", log.joinToString())
-
- log.clear()
- Compose.composeInto(container = root, composable = composable)
- scheduler.advanceToLastPostedRunnable()
- assertEquals("onPreCommitDispose, onPreCommit", log.joinToString())
-
- log.clear()
- Compose.disposeComposition(container = root)
- scheduler.advanceToLastPostedRunnable()
- assertEquals("onActiveDispose, onPreCommitDispose", log.joinToString())
-
- log.clear()
- Compose.composeInto(container = root, composable = composable)
- scheduler.advanceToLastPostedRunnable()
- assertEquals("onPreCommit, onActive", log.joinToString())
- }
- }
-
- val controller = Robolectric.buildActivity(DisposeTestActivity::class.java)
- controller.create()
- }
-
- @Test
+ @UiThreadTest
fun testSingleView() {
val tvId = 237
var text = "Hello world"
@@ -190,6 +145,7 @@
}
@Test
+ @UiThreadTest
fun testViewGroup() {
val tvId = 258
val llId = 260
@@ -223,6 +179,7 @@
}
@Test
+ @UiThreadTest
fun testComposableFunctionInvocationOneParameter() {
data class Phone(val area: String, val prefix: String, val number: String)
@@ -234,7 +191,7 @@
// @Composable
// fun PhoneView(phone: Phone) {
// phoneCalled++
- // <TextView text="..." />
+ // TextView(text="...")
// }
fun PhoneView(phone: Phone) {
phoneCalled++
@@ -253,15 +210,16 @@
}.then { _ ->
assertEquals(1, phoneCalled)
}.then { _ ->
- assertEquals(2, phoneCalled)
+ assertEquals(1, phoneCalled)
phone = Phone("124", "456", "7890")
}.then { _ ->
- assertEquals(3, phoneCalled)
+ assertEquals(2, phoneCalled)
}
}
@Test
+ @UiThreadTest
fun testComposableFunctionInvocationTwoParameters() {
val tvId = 279
var left = 0
@@ -317,6 +275,7 @@
}
@Test
+ @UiThreadTest
fun testStatelessComposableClassInvocationProperties() {
val tvId = 338
var addCalled = 0
@@ -381,6 +340,7 @@
}
@Test
+ @UiThreadTest
fun testStatelessComposableClassInvocationParameters() {
val tvId = 338
var addCalled = 0
@@ -439,6 +399,7 @@
}
@Test
+ @UiThreadTest
fun testStatefulComposableClassInvocation() {
val tvId = 470
val tvPrivateValue = 471
@@ -527,6 +488,7 @@
}
@Test
+ @UiThreadTest
fun testStatefulComposableClassWithCtorParametersInvocation() {
val tvId = 604
val tvOffsetId = 605
@@ -598,6 +560,7 @@
}
@Test
+ @UiThreadTest
fun testStatefulComposableClassWithPivotalProperty() {
val tvId = 604
val tvOffsetId = 605
@@ -668,6 +631,7 @@
}
@Test
+ @UiThreadTest
fun testMoveComponents() {
val data = mutableListOf(1, 2, 3, 4, 5)
compose {
@@ -688,6 +652,7 @@
}
@Test
+ @UiThreadTest
fun testViewClassWithCtorParametersInvocation() {
val tvId = 749
@@ -715,6 +680,7 @@
}
@Test
+ @UiThreadTest
fun testViewClassWithMutableCtorParameter() {
val tvId = 749
@@ -743,6 +709,7 @@
}
@Test
+ @UiThreadTest
fun testEmittingAnEmittable() {
class MyEmittable : MockEmittable() {
@@ -772,6 +739,7 @@
}
@Test
+ @UiThreadTest
fun testCGEmittingAnEmittable() {
class MyEmittable : MockEmittable() {
@@ -814,6 +782,7 @@
}
@Test
+ @UiThreadTest
fun testCGEmittableAsRoot() {
class MyEmittable : MockEmittable() {
var message: String = ""
@@ -898,9 +867,9 @@
}
fun compose(block: ViewComposition.() -> Unit) =
- CompositionTest(block)
+ CompositionTest(activityRule.activity, block)
- class CompositionTest(val composable: ViewComposition.() -> Unit) {
+ class CompositionTest(val activity: Activity, val composable: ViewComposition.() -> Unit) {
inner class ActiveTest(val composition: ViewComposition, val activity: Activity) {
private fun compose() {
@@ -918,8 +887,6 @@
}
fun then(block: (activity: Activity) -> Unit): ActiveTest {
- val controller = Robolectric.buildActivity(TestActivity::class.java)
- val activity = controller.create().get()
val composition = ViewComposition(
ViewComposer(activity.root, activity, object : Recomposer() {
override fun scheduleChangesDispatch() {}
@@ -953,13 +920,16 @@
}
fun composeCG(block: TestContext.(activity: Activity) -> Unit) =
- CompositionCodeGenTest(block)
+ CompositionCodeGenTest(activityRule.activity, block)
private class Root : Component() {
override fun compose() {}
}
- class CompositionCodeGenTest(val composable: TestContext.(activity: Activity) -> Unit) {
+ class CompositionCodeGenTest(
+ val activity: Activity,
+ val composable: TestContext.(activity: Activity) -> Unit
+ ) {
inner class ActiveTest(
val activity: Activity,
val context: TestContext,
@@ -980,8 +950,6 @@
}
fun then(block: TestContext.(activity: Activity) -> Unit): ActiveTest {
- val controller = Robolectric.buildActivity(TestActivity::class.java)
- val activity = controller.create().get()
val root = activity.root
val component = Root()
val cc = Compose.createCompositionContext(root.context, root, component, null)
@@ -990,4 +958,56 @@
}
}
+@RunWith(AndroidJUnit4::class)
+class DisposeTests {
+
+ @get:Rule
+ val disposeActivityRule = ActivityTestRule(DisposeTestActivity::class.java)
+
+ class DisposeTestActivity : Activity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ val root = FrameLayout(this)
+ val log = mutableListOf<String>()
+ val composable = @Composable {
+ +onPreCommit {
+ log.add("onPreCommit")
+ onDispose {
+ log.add("onPreCommitDispose")
+ }
+ }
+ +onActive {
+ log.add("onActive")
+ onDispose {
+ log.add("onActiveDispose")
+ }
+ }
+ }
+
+ log.clear()
+ Compose.composeInto(container = root, composable = composable)
+ assertEquals("onPreCommit, onActive", log.joinToString())
+
+ log.clear()
+ Compose.composeInto(container = root, composable = composable)
+ assertEquals("onPreCommitDispose, onPreCommit", log.joinToString())
+
+ log.clear()
+ Compose.disposeComposition(container = root)
+ assertEquals("onActiveDispose, onPreCommitDispose", log.joinToString())
+
+ log.clear()
+ Compose.composeInto(container = root, composable = composable)
+ assertEquals("onPreCommit, onActive", log.joinToString())
+ }
+ }
+
+ @Test
+ @SmallTest
+ @Ignore("TODO(b/138720405): Investigate synchronisation issues in tests")
+ fun testDisposeComposition() {
+ disposeActivityRule.activity
+ }
+}
+
private val Activity.root get() = findViewById(ComposerComposeTestCase.ROOT_ID) as ViewGroup
diff --git a/compose/compose-runtime/src/test/java/androidx/compose/frames/FramesTests.kt b/compose/compose-runtime/integration-tests/android-tests/src/androidTest/java/androidx/compose/frames/FramesTests.kt
similarity index 97%
rename from compose/compose-runtime/src/test/java/androidx/compose/frames/FramesTests.kt
rename to compose/compose-runtime/integration-tests/android-tests/src/androidTest/java/androidx/compose/frames/FramesTests.kt
index 8c4e3e3..7c2e9c4 100644
--- a/compose/compose-runtime/src/test/java/androidx/compose/frames/FramesTests.kt
+++ b/compose/compose-runtime/integration-tests/android-tests/src/androidTest/java/androidx/compose/frames/FramesTests.kt
@@ -1,5 +1,22 @@
+/*
+ * 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.compose.frames
+import androidx.test.filters.SmallTest
import junit.framework.TestCase
import org.junit.Assert
import java.util.ArrayDeque
@@ -10,6 +27,7 @@
const val NEW_STREET = "456 New Street"
const val NEW_CITY = "AnyCity"
+@SmallTest
class FrameTest : TestCase() {
fun testCreatingAddress() {
@@ -134,7 +152,7 @@
}
speculate()
address.street = NEW_STREET
- val speculation = androidx.compose.frames.suspend()
+ val speculation = suspend()
frame {
Assert.assertEquals(OLD_STREET, address.street)
}
@@ -1075,7 +1093,7 @@
open(false)
try {
block()
- return androidx.compose.frames.suspend()
+ return suspend()
} catch (e: Exception) {
abortHandler()
throw e
diff --git a/benchmark/src/androidTest/AndroidManifest.xml b/compose/compose-runtime/integration-tests/android-tests/src/main/AndroidManifest.xml
similarity index 67%
copy from benchmark/src/androidTest/AndroidManifest.xml
copy to compose/compose-runtime/integration-tests/android-tests/src/main/AndroidManifest.xml
index f5ec776..50d6c6b3 100644
--- a/benchmark/src/androidTest/AndroidManifest.xml
+++ b/compose/compose-runtime/integration-tests/android-tests/src/main/AndroidManifest.xml
@@ -15,12 +15,6 @@
limitations under the License.
-->
<manifest
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- package="androidx.benchmark.test">
-
- <application
- android:name="androidx.benchmark.ArgumentInjectingApplication">
- <activity android:name="android.app.Activity"/>
- </application>
-</manifest>
\ No newline at end of file
+ package="androidx.compose.integrationtests.androidtests" xmlns:android="http://schemas.android.com/apk/res/android">
+ <application/>
+</manifest>
diff --git a/benchmark/build.gradle b/compose/compose-runtime/integration-tests/build.gradle
similarity index 60%
copy from benchmark/build.gradle
copy to compose/compose-runtime/integration-tests/build.gradle
index 386f71d..122416d 100644
--- a/benchmark/build.gradle
+++ b/compose/compose-runtime/integration-tests/build.gradle
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * 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.
@@ -14,6 +14,10 @@
* limitations under the License.
*/
+
+import org.jetbrains.kotlin.codegen.ImplementationBodyCodegen
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
import static androidx.build.dependencies.DependenciesKt.*
import androidx.build.LibraryGroups
import androidx.build.LibraryVersions
@@ -26,20 +30,14 @@
}
dependencies {
- implementation(ANDROIDX_TEST_RULES)
- implementation(ANDROIDX_TEST_RUNNER)
- implementation(KOTLIN_STDLIB)
- implementation(SUPPORT_ANNOTATIONS)
+ implementation(project(":compose:compose-runtime"))
+ implementation(KOTLIN_COMPOSE_STDLIB)
- androidTestImplementation(ANDROIDX_TEST_CORE)
- androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
+ implementation(JUNIT)
}
-androidx {
- name = "Android Benchmark"
- publish = Publish.SNAPSHOT_AND_RELEASE
- mavenVersion = LibraryVersions.BENCHMARK
- mavenGroup = LibraryGroups.BENCHMARK
- inceptionYear = "2018"
- description = "Android Benchmark"
-}
+android {
+ defaultConfig {
+ minSdkVersion 18
+ }
+}
\ No newline at end of file
diff --git a/benchmark/src/androidTest/AndroidManifest.xml b/compose/compose-runtime/integration-tests/src/main/AndroidManifest.xml
similarity index 67%
copy from benchmark/src/androidTest/AndroidManifest.xml
copy to compose/compose-runtime/integration-tests/src/main/AndroidManifest.xml
index f5ec776..50d6c6b3 100644
--- a/benchmark/src/androidTest/AndroidManifest.xml
+++ b/compose/compose-runtime/integration-tests/src/main/AndroidManifest.xml
@@ -15,12 +15,6 @@
limitations under the License.
-->
<manifest
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- package="androidx.benchmark.test">
-
- <application
- android:name="androidx.benchmark.ArgumentInjectingApplication">
- <activity android:name="android.app.Activity"/>
- </application>
-</manifest>
\ No newline at end of file
+ package="androidx.compose.integrationtests.androidtests" xmlns:android="http://schemas.android.com/apk/res/android">
+ <application/>
+</manifest>
diff --git a/compose/compose-runtime/src/test/java/androidx/compose/frames/Address.kt b/compose/compose-runtime/integration-tests/src/main/java/androidx/compose/frames/Address.kt
similarity index 100%
rename from compose/compose-runtime/src/test/java/androidx/compose/frames/Address.kt
rename to compose/compose-runtime/integration-tests/src/main/java/androidx/compose/frames/Address.kt
diff --git a/compose/compose-runtime/src/test/java/androidx/compose/mock/ComposeContact.kt b/compose/compose-runtime/integration-tests/src/main/java/androidx/compose/mock/ComposeContact.kt
similarity index 75%
rename from compose/compose-runtime/src/test/java/androidx/compose/mock/ComposeContact.kt
rename to compose/compose-runtime/integration-tests/src/main/java/androidx/compose/mock/ComposeContact.kt
index f53c431..7409e90 100644
--- a/compose/compose-runtime/src/test/java/androidx/compose/mock/ComposeContact.kt
+++ b/compose/compose-runtime/integration-tests/src/main/java/androidx/compose/mock/ComposeContact.kt
@@ -1,3 +1,19 @@
+/*
+ * 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.compose.mock
// <linear>
diff --git a/compose/compose-runtime/integration-tests/src/main/java/androidx/compose/mock/ComposePoints.kt b/compose/compose-runtime/integration-tests/src/main/java/androidx/compose/mock/ComposePoints.kt
new file mode 100644
index 0000000..8a62094
--- /dev/null
+++ b/compose/compose-runtime/integration-tests/src/main/java/androidx/compose/mock/ComposePoints.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.compose.mock
+
+fun MockViewComposition.point(point: Point) {
+ text("X: ${point.x} Y: ${point.y}")
+}
+
+fun MockViewValidator.point(point: Point) {
+ text("X: ${point.x} Y: ${point.y}")
+}
+
+object SLPoints
+
+fun MockViewComposition.points(points: Iterable<Point>) {
+ repeat(of = points) {
+ memoize(SLPoints, it) { point(it) }
+ }
+}
+
+fun MockViewValidator.points(points: Iterable<Point>) {
+ repeat(of = points) {
+ point(it)
+ }
+}
diff --git a/compose/compose-runtime/integration-tests/src/main/java/androidx/compose/mock/ComposeReport.kt b/compose/compose-runtime/integration-tests/src/main/java/androidx/compose/mock/ComposeReport.kt
new file mode 100644
index 0000000..cf50387
--- /dev/null
+++ b/compose/compose-runtime/integration-tests/src/main/java/androidx/compose/mock/ComposeReport.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.compose.mock
+
+fun MockViewComposition.reportsTo(report: Report) {
+ text(report.from)
+ text("reports to")
+ text(report.to)
+}
+fun MockViewValidator.reportsTo(report: Report) {
+ text(report.from)
+ text("reports to")
+ text(report.to)
+}
+
+fun MockViewComposition.reportsReport(reports: Iterable<Report>) {
+ linear {
+ repeat(of = reports) { report ->
+ reportsTo(report)
+ }
+ }
+}
+
+fun MockViewValidator.reportsReport(reports: Iterable<Report>) {
+ linear {
+ repeat(of = reports) { report ->
+ reportsTo(report)
+ }
+ }
+}
diff --git a/compose/compose-runtime/src/test/java/androidx/compose/mock/Contact.kt b/compose/compose-runtime/integration-tests/src/main/java/androidx/compose/mock/Contact.kt
similarity index 100%
rename from compose/compose-runtime/src/test/java/androidx/compose/mock/Contact.kt
rename to compose/compose-runtime/integration-tests/src/main/java/androidx/compose/mock/Contact.kt
diff --git a/compose/compose-runtime/integration-tests/src/main/java/androidx/compose/mock/ContactModel.kt b/compose/compose-runtime/integration-tests/src/main/java/androidx/compose/mock/ContactModel.kt
new file mode 100644
index 0000000..622cb0d
--- /dev/null
+++ b/compose/compose-runtime/integration-tests/src/main/java/androidx/compose/mock/ContactModel.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.compose.mock
+
+class ContactModel(
+ var filter: String = "",
+ val contacts: MutableList<Contact>,
+ var selected: Contact? = null
+) {
+ val filtered get() = contacts.filter { it.name.contains(filter) }
+
+ fun add(contact: Contact, after: Contact? = null) {
+ if (after == null) {
+ contacts.add(contact)
+ } else {
+ contacts.add(find(after) + 1, contact)
+ }
+ }
+
+ fun move(contact: Contact, after: Contact?) {
+ if (after == null) {
+ contacts.removeAt(find(contact))
+ contacts.add(0, contact)
+ } else {
+ contacts.removeAt(find(contact))
+ contacts.add(find(after) + 1, contact)
+ }
+ }
+
+ private fun find(contact: Contact): Int {
+ val index = contacts.indexOf(contact)
+ if (index < 0) error("Contact $contact not found")
+ return index
+ }
+}
diff --git a/compose/compose-runtime/src/test/java/androidx/compose/mock/MockViewValidator.kt b/compose/compose-runtime/integration-tests/src/main/java/androidx/compose/mock/MockViewValidator.kt
similarity index 77%
rename from compose/compose-runtime/src/test/java/androidx/compose/mock/MockViewValidator.kt
rename to compose/compose-runtime/integration-tests/src/main/java/androidx/compose/mock/MockViewValidator.kt
index 5e12eb8..6d3377d 100644
--- a/compose/compose-runtime/src/test/java/androidx/compose/mock/MockViewValidator.kt
+++ b/compose/compose-runtime/integration-tests/src/main/java/androidx/compose/mock/MockViewValidator.kt
@@ -1,3 +1,19 @@
+/*
+ * 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.compose.mock
import org.junit.Assert
diff --git a/compose/compose-runtime/src/test/java/androidx/compose/mock/Point.kt b/compose/compose-runtime/integration-tests/src/main/java/androidx/compose/mock/Point.kt
similarity index 100%
rename from compose/compose-runtime/src/test/java/androidx/compose/mock/Point.kt
rename to compose/compose-runtime/integration-tests/src/main/java/androidx/compose/mock/Point.kt
diff --git a/compose/compose-runtime/src/test/java/androidx/compose/mock/Report.kt b/compose/compose-runtime/integration-tests/src/main/java/androidx/compose/mock/Report.kt
similarity index 100%
rename from compose/compose-runtime/src/test/java/androidx/compose/mock/Report.kt
rename to compose/compose-runtime/integration-tests/src/main/java/androidx/compose/mock/Report.kt
diff --git a/compose/compose-runtime/src/test/java/androidx/compose/mock/View.kt b/compose/compose-runtime/integration-tests/src/main/java/androidx/compose/mock/View.kt
similarity index 74%
rename from compose/compose-runtime/src/test/java/androidx/compose/mock/View.kt
rename to compose/compose-runtime/integration-tests/src/main/java/androidx/compose/mock/View.kt
index ae356bc..fd36381 100644
--- a/compose/compose-runtime/src/test/java/androidx/compose/mock/View.kt
+++ b/compose/compose-runtime/integration-tests/src/main/java/androidx/compose/mock/View.kt
@@ -1,3 +1,19 @@
+/*
+ * 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.compose.mock
fun indent(indent: Int) {
diff --git a/compose/compose-runtime/src/test/java/androidx/compose/mock/ViewComposer.kt b/compose/compose-runtime/integration-tests/src/main/java/androidx/compose/mock/ViewComposer.kt
similarity index 90%
rename from compose/compose-runtime/src/test/java/androidx/compose/mock/ViewComposer.kt
rename to compose/compose-runtime/integration-tests/src/main/java/androidx/compose/mock/ViewComposer.kt
index 6381542..e60ef68 100644
--- a/compose/compose-runtime/src/test/java/androidx/compose/mock/ViewComposer.kt
+++ b/compose/compose-runtime/integration-tests/src/main/java/androidx/compose/mock/ViewComposer.kt
@@ -1,3 +1,19 @@
+/*
+ * 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.compose.mock
import androidx.compose.Applier
diff --git a/compose/compose-runtime/src/test/java/androidx/compose/mock/Views.kt b/compose/compose-runtime/integration-tests/src/main/java/androidx/compose/mock/Views.kt
similarity index 68%
rename from compose/compose-runtime/src/test/java/androidx/compose/mock/Views.kt
rename to compose/compose-runtime/integration-tests/src/main/java/androidx/compose/mock/Views.kt
index 6e21d32..f86c36f 100644
--- a/compose/compose-runtime/src/test/java/androidx/compose/mock/Views.kt
+++ b/compose/compose-runtime/integration-tests/src/main/java/androidx/compose/mock/Views.kt
@@ -1,3 +1,19 @@
+/*
+ * 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.compose.mock
class SourceLocation(val name: String) {
diff --git a/compose/compose-runtime/src/main/AndroidManifest.xml b/compose/compose-runtime/src/androidMain/AndroidManifest.xml
similarity index 100%
rename from compose/compose-runtime/src/main/AndroidManifest.xml
rename to compose/compose-runtime/src/androidMain/AndroidManifest.xml
diff --git a/compose/compose-runtime/src/androidMain/kotlin/androidx/compose/ActualAndroid.kt b/compose/compose-runtime/src/androidMain/kotlin/androidx/compose/ActualAndroid.kt
new file mode 100644
index 0000000..60234d1
--- /dev/null
+++ b/compose/compose-runtime/src/androidMain/kotlin/androidx/compose/ActualAndroid.kt
@@ -0,0 +1,82 @@
+/*
+ * 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.compose
+
+// TODO(b/137794549): Remove View System-related expect/actuals
+actual typealias ViewParent = android.view.ViewParent
+
+// TODO(b/137794549): Remove View System-related expect/actuals
+actual typealias View = android.view.View
+
+// TODO(b/137794549): Remove View System-related expect/actuals
+actual val View.parent: ViewParent
+ get() = parent
+
+// TODO(b/137794549): Remove View System-related expect/actuals
+actual val View.context: Context
+ get() = context
+
+// TODO(b/137794549): Remove View System-related expect/actuals
+actual typealias ViewGroup = android.view.ViewGroup
+
+// TODO(b/137794549): Remove View System-related expect/actuals
+actual typealias Context = android.content.Context
+
+// TODO(b/137794549): Remove View System-related expect/actuals
+actual typealias FrameLayout = android.widget.FrameLayout
+
+// TODO(b/137794558): Create portable abstraction for scheduling
+actual typealias Looper = android.os.Looper
+
+// TODO(b/137794558): Create portable abstraction for scheduling
+actual object LooperWrapper {
+ actual fun getMainLooper(): Looper = android.os.Looper.getMainLooper()
+}
+
+actual fun isMainThread(): Boolean {
+ return android.os.Looper.myLooper() == android.os.Looper.getMainLooper()
+}
+
+// TODO(b/137794558): Create portable abstraction for scheduling
+actual class Handler {
+ val handler: android.os.Handler
+
+ actual constructor(looper: Looper) {
+ handler = android.os.Handler(looper)
+ }
+ actual fun postAtFrontOfQueue(block: () -> Unit): Boolean {
+ return handler.postAtFrontOfQueue { block() }
+ }
+}
+
+// TODO(b/137794558): Create portable abstraction for scheduling
+actual object Choreographer {
+ actual fun postFrameCallback(callback: (Long) -> Unit) {
+ android.view.Choreographer.getInstance().postFrameCallback(callback)
+ }
+ actual fun postFrameCallbackDelayed(delayMillis: Long, callback: (Long) -> Unit) {
+ android.view.Choreographer.getInstance().postFrameCallbackDelayed(callback, delayMillis)
+ }
+ actual fun removeFrameCallback(callback: (Long) -> Unit) {
+ android.view.Choreographer.getInstance().removeFrameCallback(callback)
+ }
+}
+
+actual object Trace {
+ actual fun beginSection(name: String) = android.os.Trace.beginSection(name)
+ actual fun endSection() = android.os.Trace.endSection()
+}
diff --git a/compose/compose-runtime/src/androidMain/kotlin/androidx/compose/ActualJvm.kt b/compose/compose-runtime/src/androidMain/kotlin/androidx/compose/ActualJvm.kt
new file mode 100644
index 0000000..7762a4c
--- /dev/null
+++ b/compose/compose-runtime/src/androidMain/kotlin/androidx/compose/ActualJvm.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.compose
+
+actual typealias BitSet = java.util.BitSet
+
+actual open class ThreadLocal<T> actual constructor() : java.lang.ThreadLocal<T>() {
+ actual override fun get(): T? {
+ return super.get()
+ }
+
+ actual override fun set(value: T?) {
+ super.set(value)
+ }
+
+ actual override fun initialValue(): T? {
+ return super.initialValue()
+ }
+}
+
+actual typealias WeakHashMap<K, V> = java.util.WeakHashMap<K, V>
+
+internal actual fun arraycopy(source: Any, sourcePos: Int, dest: Any, destPos: Int, len: Int) =
+ System.arraycopy(source, sourcePos, dest, destPos, len)
+
+actual fun identityHashCode(instance: Any?): Int = System.identityHashCode(instance)
+
+actual inline fun <R> synchronized(lock: Any, block: () -> R): R {
+ kotlin.synchronized(lock) {
+ return block()
+ }
+}
+
+actual typealias WeakReference<T> = java.lang.ref.WeakReference<T>
+
+actual typealias MainThread = androidx.annotation.MainThread
+
+actual typealias TestOnly = org.jetbrains.annotations.TestOnly
+
+actual typealias CheckResult = androidx.annotation.CheckResult
diff --git a/compose/compose-runtime/src/androidMain/kotlin/androidx/compose/ComposeAndroid.kt b/compose/compose-runtime/src/androidMain/kotlin/androidx/compose/ComposeAndroid.kt
new file mode 100644
index 0000000..3e0763a
--- /dev/null
+++ b/compose/compose-runtime/src/androidMain/kotlin/androidx/compose/ComposeAndroid.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.compose
+
+import android.app.Activity
+
+/**
+ * Sets the contentView of an activity to a FrameLayout, and composes the contents of the layout
+ * with the passed in [composable]. This is a convenience method around [Compose.composeInto].
+ *
+ * @see Compose.composeInto
+ * @see Activity.setContentView
+ */
+fun Activity.setViewContent(composable: @Composable() () -> Unit): CompositionContext? {
+ // If there is already a FrameLayout in the root, we assume we want to compose
+ // into it instead of create a new one. This allows for `setContent` to be
+ // called multiple times.
+ val root = window
+ .decorView
+ .findViewById<ViewGroup>(android.R.id.content)
+ .getChildAt(0) as? ViewGroup
+ ?: FrameLayout(this).also { setContentView(it) }
+ return root.setViewContent(composable)
+}
+
+/**
+ * Disposes of a composition that was started using [setContent]. This is a convenience method
+ * around [Compose.disposeComposition].
+ *
+ * @see setContent
+ * @see Compose.disposeComposition
+ */
+fun Activity.disposeComposition() {
+ val view = window
+ .decorView
+ .findViewById<ViewGroup>(android.R.id.content)
+ .getChildAt(0) as? ViewGroup
+ ?: error("No root view found")
+ Compose.disposeComposition(view, null)
+}
\ No newline at end of file
diff --git a/compose/compose-runtime/src/androidMain/kotlin/androidx/compose/Recomposer.kt b/compose/compose-runtime/src/androidMain/kotlin/androidx/compose/Recomposer.kt
new file mode 100644
index 0000000..6f92528
--- /dev/null
+++ b/compose/compose-runtime/src/androidMain/kotlin/androidx/compose/Recomposer.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.compose
+
+import android.view.Choreographer
+
+private class AndroidRecomposer : Recomposer() {
+
+ private var frameScheduled = false
+
+ private val frameCallback = Choreographer.FrameCallback {
+ frameScheduled = false
+ dispatchRecomposes()
+ }
+
+ override fun scheduleChangesDispatch() {
+ if (!frameScheduled) {
+ frameScheduled = true
+ Choreographer.getInstance().postFrameCallback(frameCallback)
+ }
+ }
+
+ override fun hasPendingChanges(): Boolean = frameScheduled
+}
+
+internal actual fun createRecomposer(): Recomposer {
+ return AndroidRecomposer()
+}
\ No newline at end of file
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/ViewComposer.kt b/compose/compose-runtime/src/androidMain/kotlin/androidx/compose/ViewComposer.kt
similarity index 91%
rename from compose/compose-runtime/src/main/java/androidx/compose/ViewComposer.kt
rename to compose/compose-runtime/src/androidMain/kotlin/androidx/compose/ViewComposer.kt
index b7ec9f7..c93eaf0 100644
--- a/compose/compose-runtime/src/main/java/androidx/compose/ViewComposer.kt
+++ b/compose/compose-runtime/src/androidMain/kotlin/androidx/compose/ViewComposer.kt
@@ -268,7 +268,17 @@
// TODO: Add more overloads for common primitive types like String and Float etc to avoid boxing
// and the immutable check
@Suppress("NOTHING_TO_INLINE")
- inline fun changed(value: Int) = with(composer) {
+ fun changed(value: Int) = with(composer) {
+ if ((nextSlot() as? Int)?.let { value != it } ?: true || inserting) {
+ updateValue(value)
+ true
+ } else {
+ skipValue()
+ false
+ }
+ }
+
+ fun <T> changed(value: T) = with(composer) {
if (nextSlot() != value || inserting) {
updateValue(value)
true
@@ -278,20 +288,10 @@
}
}
- inline fun <reified T> changed(value: T) = with(composer) {
- if (nextSlot() != value || inserting || !isEffectivelyImmutable(value)) {
- updateValue(value)
- true
- } else {
- skipValue()
- false
- }
- }
-
@Suppress("NOTHING_TO_INLINE")
- inline fun updated(value: Int) = with(composer) {
+ fun updated(value: Int) = with(composer) {
inserting.let { inserting ->
- if (nextSlot() != value || inserting) {
+ if (((nextSlot() as? Int)?.let { it != value } ?: true) || inserting) {
updateValue(value)
!inserting
} else {
@@ -301,9 +301,9 @@
}
}
- inline fun <reified T> updated(value: T) = with(composer) {
+ fun <T> updated(value: T) = with(composer) {
inserting.let { inserting ->
- if (nextSlot() != value || inserting || !isEffectivelyImmutable(value)) {
+ if (nextSlot() != value || inserting) {
updateValue(value)
!inserting
} else {
@@ -325,6 +325,21 @@
inline fun <reified T> update(value: T, /*crossinline*/ block: (value: T) -> Unit): Boolean =
updated(value).also { if (it) block(value) }
+ @Suppress("UNUSED")
+ fun <T> changedUnchecked(@Suppress("UNUSED_PARAMETER") value: T) = true
+
+ @Suppress("UNUSED")
+ inline fun <T> setUnchecked(value: T, block: (value: T) -> Unit): Boolean {
+ block(value)
+ return true
+ }
+
+ @Suppress("UNUSED")
+ inline fun <T> updateUnchecked(value: T, block: (value: T) -> Unit): Boolean {
+ block(value)
+ return true
+ }
+
/*inline*/ operator fun Boolean.plus(other: Boolean) = this || other
}
@@ -383,7 +398,7 @@
}
}
-internal val currentComposerNonNull
+internal actual val currentComposerNonNull
get() = currentComposer ?: emptyComposition()
private fun emptyComposition(): Nothing =
@@ -391,10 +406,10 @@
val composer get() = ViewComposition(currentComposerNonNull as ViewComposer)
-internal var currentComposer: Composer<*>? = null
+internal actual var currentComposer: Composer<*>? = null
private set
-fun <T> Composer<*>.runWithCurrent(block: () -> T): T {
+actual fun <T> Composer<*>.runWithCurrent(block: () -> T): T {
val prev = currentComposer
try {
currentComposer = this
@@ -410,14 +425,10 @@
typealias ViewUpdater<T> = ComposerUpdater<Any, T>
-@PublishedApi
-internal val invocation = Object()
-
-@PublishedApi
-internal val provider = Object()
-
-@PublishedApi
-internal val consumer = Object()
-
-@PublishedApi
-internal val reference = Object()
+internal actual fun createComposer(
+ root: Any,
+ context: Context,
+ recomposer: Recomposer
+): Composer<*> {
+ return ViewComposer(root, context, recomposer)
+}
\ No newline at end of file
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/adapters/ViewAdapter.kt b/compose/compose-runtime/src/androidMain/kotlin/androidx/compose/adapters/ViewAdapter.kt
similarity index 100%
rename from compose/compose-runtime/src/main/java/androidx/compose/adapters/ViewAdapter.kt
rename to compose/compose-runtime/src/androidMain/kotlin/androidx/compose/adapters/ViewAdapter.kt
diff --git a/benchmark/src/androidTest/AndroidManifest.xml b/compose/compose-runtime/src/androidTest/AndroidManifest.xml
similarity index 66%
copy from benchmark/src/androidTest/AndroidManifest.xml
copy to compose/compose-runtime/src/androidTest/AndroidManifest.xml
index f5ec776..356bbf4 100644
--- a/benchmark/src/androidTest/AndroidManifest.xml
+++ b/compose/compose-runtime/src/androidTest/AndroidManifest.xml
@@ -14,13 +14,7 @@
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.benchmark.test">
+<manifest package="androidx.ui.material" xmlns:android="http://schemas.android.com/apk/res/android">
+ <application/>
- <application
- android:name="androidx.benchmark.ArgumentInjectingApplication">
- <activity android:name="android.app.Activity"/>
- </application>
-</manifest>
\ No newline at end of file
+</manifest>
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/Ambient.kt b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Ambient.kt
similarity index 97%
rename from compose/compose-runtime/src/main/java/androidx/compose/Ambient.kt
rename to compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Ambient.kt
index 806b474f..4b74acb 100644
--- a/compose/compose-runtime/src/main/java/androidx/compose/Ambient.kt
+++ b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Ambient.kt
@@ -87,7 +87,7 @@
* will be thrown. This factory will not be executed more than once.
*/
inline fun <reified T> of(
- key: String = T::class.java.simpleName,
+ key: String = T::class.simpleName!!,
noinline defaultFactory: (() -> T)? = null
) = Ambient(key, defaultFactory)
}
@@ -113,8 +113,7 @@
@Composable
fun Provider(
value: T,
- @Children
- children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
with(currentComposerNonNull) {
val holder = +memo {
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/Applier.kt b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Applier.kt
similarity index 99%
rename from compose/compose-runtime/src/main/java/androidx/compose/Applier.kt
rename to compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Applier.kt
index f94b0a5..59787e0 100644
--- a/compose/compose-runtime/src/main/java/androidx/compose/Applier.kt
+++ b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Applier.kt
@@ -73,4 +73,4 @@
fun reset() {
stack.clear()
}
-}
+}
\ No newline at end of file
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/Children.kt b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Children.kt
similarity index 100%
rename from compose/compose-runtime/src/main/java/androidx/compose/Children.kt
rename to compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Children.kt
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/Component.kt b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Component.kt
similarity index 95%
rename from compose/compose-runtime/src/main/java/androidx/compose/Component.kt
rename to compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Component.kt
index 1bc0663..809eb78 100644
--- a/compose/compose-runtime/src/main/java/androidx/compose/Component.kt
+++ b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Component.kt
@@ -55,7 +55,8 @@
@Suppress("PLUGIN_ERROR")
abstract class Component {
@HiddenAttribute
- internal var recomposeCallback: ((sync: Boolean) -> Unit)? = null
+ // TODO(138720404): Investigate if Compose APIs can be internal despite MPP limitations
+ var recomposeCallback: ((sync: Boolean) -> Unit)? = null
private var composing = false
/**
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/Composable.kt b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Composable.kt
similarity index 100%
rename from compose/compose-runtime/src/main/java/androidx/compose/Composable.kt
rename to compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Composable.kt
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/Compose.kt b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Compose.kt
similarity index 84%
rename from compose/compose-runtime/src/main/java/androidx/compose/Compose.kt
rename to compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Compose.kt
index 71a5973..7565647 100644
--- a/compose/compose-runtime/src/main/java/androidx/compose/Compose.kt
+++ b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Compose.kt
@@ -16,15 +16,6 @@
package androidx.compose
-import android.app.Activity
-import android.content.Context
-import android.view.View
-import android.view.ViewGroup
-import android.widget.FrameLayout
-import androidx.annotation.MainThread
-import org.jetbrains.annotations.TestOnly
-import java.util.WeakHashMap
-
// TODO(lmr): consider moving this to the ViewComposer directly
/**
* A global namespace to hold some Compose utility methods, such as [Compose.composeInto] and
@@ -53,8 +44,8 @@
return view.getTag(TAG_ROOT_COMPONENT) as? Component
}
- // TODO(lmr): used by tests only. consider ways to remove.
- internal fun findRoot(view: View): Component? {
+ // TODO(b/138254844): Make findRoot/setRoot test-only & Android-only
+ fun findRoot(view: View): Component? {
var node: View? = view
while (node != null) {
val cc = node.getTag(TAG_ROOT_COMPONENT) as? Component
@@ -72,6 +63,7 @@
return EMITTABLE_ROOT_COMPONENT[emittable]
}
+ // TODO(b/138254844): Make findRoot/setRoot test-only & Android-only
private fun setRoot(emittable: Emittable, component: Component) {
EMITTABLE_ROOT_COMPONENT[emittable] = component
}
@@ -235,41 +227,6 @@
}
/**
- * Sets the contentView of an activity to a FrameLayout, and composes the contents of the layout
- * with the passed in [composable]. This is a convenience method around [Compose.composeInto].
- *
- * @see Compose.composeInto
- * @see Activity.setContentView
- */
-fun Activity.setViewContent(composable: @Composable() () -> Unit): CompositionContext? {
- // If there is already a FrameLayout in the root, we assume we want to compose
- // into it instead of create a new one. This allows for `setContent` to be
- // called multiple times.
- val root = window
- .decorView
- .findViewById<ViewGroup>(android.R.id.content)
- .getChildAt(0) as? ViewGroup
- ?: FrameLayout(this).also { setContentView(it) }
- return root.setViewContent(composable)
-}
-
-/**
- * Disposes of a composition that was started using [setContent]. This is a convenience method
- * around [Compose.disposeComposition].
- *
- * @see setContent
- * @see Compose.disposeComposition
- */
-fun Activity.disposeComposition() {
- val view = window
- .decorView
- .findViewById<ViewGroup>(android.R.id.content)
- .getChildAt(0) as? ViewGroup
- ?: error("No root view found")
- Compose.disposeComposition(view, null)
-}
-
-/**
* Composes the children of the view with the passed in [composable]. This is a convenience method
* around [Compose.composeInto].
*
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/Composer.kt b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Composer.kt
similarity index 98%
rename from compose/compose-runtime/src/main/java/androidx/compose/Composer.kt
rename to compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Composer.kt
index e5fb5ec..d0f032a 100644
--- a/compose/compose-runtime/src/main/java/androidx/compose/Composer.kt
+++ b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Composer.kt
@@ -16,6 +16,8 @@
package androidx.compose
+import kotlin.collections.isNotEmpty
+
internal typealias Change<N> = (
applier: Applier<N>,
slots: SlotWriter,
@@ -54,7 +56,7 @@
var groupIndex: Int = 0
init {
- assert(startIndex >= 0) { "Invalid start index" }
+ require(startIndex >= 0) { "Invalid start index" }
}
var nodeCount = parentKeyInfo.nodes
@@ -352,7 +354,7 @@
}
override fun emitNode(node: N) {
- assert(inserting) { "emitNode() called when not inserting" }
+ require(inserting) { "emitNode() called when not inserting" }
val insertIndex = nodeIndexStack.peek()
// see emitNode
pending?.let { it.nodeCount++ }
@@ -366,7 +368,7 @@
}
override fun useNode(): N {
- assert(!inserting) { "useNode() called while inserting" }
+ require(!inserting) { "useNode() called while inserting" }
recordDown()
val result = slots.next()
childrenAllowed = true
@@ -595,7 +597,7 @@
invalidateStack.let { if (it.isNotEmpty()) it.peek() else null }
private fun start(key: Any, action: SlotAction) {
- assert(childrenAllowed) { "A call to createNode(), emitNode() or useNode() expected" }
+ require(childrenAllowed) { "A call to createNode(), emitNode() or useNode() expected" }
// 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.
@@ -834,7 +836,8 @@
if (action == END_NODE) recordUp()
recordEnd(action)
- if (slots.inEmpty) {
+ val inserting = slots.inEmpty
+ if (inserting) {
slots.endEmpty()
if (!slots.inEmpty) recordOperation { _, slots, _ -> slots.endInsert() }
}
@@ -847,7 +850,8 @@
previousPending?.let<Pending, Unit> { previous ->
// Update the parent count of nodes
previous.updateNodeCount(pending?.parentKeyInfo, expectedNodeCount)
- previous.groupIndex++
+ if (!inserting)
+ previous.groupIndex++
}
this.pending = previousPending
this.parentKeyInfo = keyStack.pop()
@@ -881,7 +885,7 @@
trace("Compose:recordEnters") {
while (true) {
skipToGroupContaining(location)
- assert(
+ require(
slots.isGroup && location >= slots.current &&
location < slots.current + slots.groupSize
) {
@@ -1132,14 +1136,14 @@
internal fun finalizeCompose() {
finalRealizeSlots()
- assert(pendingStack.isEmpty()) { "Start end imbalance" }
+ require(pendingStack.isEmpty()) { "Start end imbalance" }
pending = null
nodeIndex = 0
groupNodeCount = 0
}
private fun recordSlotNext(count: Int = 1) {
- assert(count >= 1) { "Invalid call to recordSlotNext()" }
+ require(count >= 1) { "Invalid call to recordSlotNext()" }
val actionsSize = slotActions.size
if (actionsSize > 0) {
// If the last action was also a skip just add this one to the last one
@@ -1192,7 +1196,7 @@
private fun recordRemoveNode(nodeIndex: Int, count: Int) {
if (count > 0) {
- assert(nodeIndex >= 0) { "Invalid remove index $nodeIndex" }
+ require(nodeIndex >= 0) { "Invalid remove index $nodeIndex" }
if (previousRemove == nodeIndex) previousCount += count
else {
realizeMovement()
@@ -1342,13 +1346,13 @@
fun add(action: SlotAction) {
if (size >= actions.size) {
- actions = actions.copyOf(Math.max(size, actions.size * 2))
+ actions = actions.copyOf(kotlin.math.max(size, actions.size * 2))
}
actions[size++] = action
}
fun remove(count: Int) {
- assert(count <= size) { "Removing too many actions" }
+ require(count <= size) { "Removing too many actions" }
size -= count
}
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/Composition.kt b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Composition.kt
similarity index 100%
rename from compose/compose-runtime/src/main/java/androidx/compose/Composition.kt
rename to compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Composition.kt
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/CompositionContext.kt b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/CompositionContext.kt
similarity index 95%
rename from compose/compose-runtime/src/main/java/androidx/compose/CompositionContext.kt
rename to compose/compose-runtime/src/commonMain/kotlin/androidx/compose/CompositionContext.kt
index 09ce471..0455c6f 100644
--- a/compose/compose-runtime/src/main/java/androidx/compose/CompositionContext.kt
+++ b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/CompositionContext.kt
@@ -16,8 +16,6 @@
package androidx.compose
-import android.content.Context
-
// TODO(lmr): this is really only needed for "composition management", but that could maybe move
// somewhere else. Consider ways to remove this class. Maybe should merge with FrameManager?
class CompositionContext private constructor(val component: Component, val composer: Composer<*>) {
@@ -30,7 +28,7 @@
) = prepare(
component,
compositionReference
- ) { ViewComposer(root, context, this) }
+ ) { createComposer(root, context, this) }
fun prepare(
component: Component,
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/CompositionLifecycleObserver.kt b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/CompositionLifecycleObserver.kt
similarity index 95%
rename from compose/compose-runtime/src/main/java/androidx/compose/CompositionLifecycleObserver.kt
rename to compose/compose-runtime/src/commonMain/kotlin/androidx/compose/CompositionLifecycleObserver.kt
index 934ace9..e5e6907 100644
--- a/compose/compose-runtime/src/main/java/androidx/compose/CompositionLifecycleObserver.kt
+++ b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/CompositionLifecycleObserver.kt
@@ -43,5 +43,5 @@
override fun equals(other: Any?): Boolean =
other === instance || other is CompositionLifecycleObserverHolder &&
instance === other.instance
- override fun hashCode(): Int = System.identityHashCode(instance)
-}
+ override fun hashCode(): Int = identityHashCode(instance)
+}
\ No newline at end of file
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/CompositionReference.kt b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/CompositionReference.kt
similarity index 100%
rename from compose/compose-runtime/src/main/java/androidx/compose/CompositionReference.kt
rename to compose/compose-runtime/src/commonMain/kotlin/androidx/compose/CompositionReference.kt
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/Effects.kt b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Effects.kt
similarity index 95%
rename from compose/compose-runtime/src/main/java/androidx/compose/Effects.kt
rename to compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Effects.kt
index c3826a2..c8560cb 100644
--- a/compose/compose-runtime/src/main/java/androidx/compose/Effects.kt
+++ b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Effects.kt
@@ -18,8 +18,6 @@
package androidx.compose
-import android.view.Choreographer
-import androidx.annotation.CheckResult
import androidx.compose.annotations.Hide
import androidx.compose.frames.AbstractRecord
import androidx.compose.frames.Framed
@@ -33,7 +31,8 @@
* This is just a sentinel object that represents the absence of an explicit key being defined. This is necessary because
* we want `null` to be a valid key, and not the absence of one.
*/
-private val absentKey = object {}
+// TODO delete the explicit type after https://youtrack.jetbrains.com/issue/KT-20996
+private val absentKey: Any = object {}
/**
* This creates a composite key of any value to be used as the key for the group of an effect
@@ -167,7 +166,7 @@
private var disposeCallback = emptyDispose
override fun onDispose(callback: () -> Unit) {
- assert(disposeCallback === emptyDispose) {
+ require(disposeCallback === emptyDispose) {
"onDispose(...) should only be called once"
}
disposeCallback = callback
@@ -185,26 +184,25 @@
@PublishedApi
internal class PostCommitScopeImpl(
internal val onCommit: CommitScope.() -> Unit
-) : CommitScope, CompositionLifecycleObserver, Choreographer.FrameCallback {
+) : CommitScope, CompositionLifecycleObserver {
private var disposeCallback = emptyDispose
private var hasRun = false
override fun onDispose(callback: () -> Unit) {
- assert(disposeCallback === emptyDispose) {
+ require(disposeCallback === emptyDispose) {
"onDispose(...) should only be called once"
}
disposeCallback = callback
}
- override fun doFrame(frameTimeNanos: Long) {
+ private val doFrame: (Long) -> Unit = {
hasRun = true
onCommit(this)
}
override fun onEnter() {
- // TODO(lmr): we should eventually move this to an expect/actual "scheduler" of some sort
- Choreographer.getInstance().postFrameCallback(this)
+ Choreographer.postFrameCallback(doFrame)
}
override fun onLeave() {
@@ -213,7 +211,7 @@
if (hasRun) {
disposeCallback()
} else {
- Choreographer.getInstance().removeFrameCallback(this)
+ Choreographer.removeFrameCallback(doFrame)
}
}
}
@@ -233,7 +231,7 @@
* @param v1 The value to use as the key. This will be compared to its previous value using `Object.equals`
* @param block The block to execute other effects in
*/
-@CheckResult(suggest = "+")
+@CheckResult("+")
fun <T, V1> key(v1: V1, block: Effect<T>.() -> T) =
Effect(block, v1)
@@ -255,7 +253,7 @@
* @param v2 The second value to use as a key. This will be compared to its previous value using `Object.equals`
* @param block The block to execute other effects in
*/
-@CheckResult(suggest = "+")
+@CheckResult("+")
fun <T, V1, V2> key(v1: V1, v2: V2, block: Effect<T>.() -> T) =
Effect(block, joinKey(v1, v2))
@@ -274,7 +272,7 @@
* @param inputs The set of values to be used to create a compound key. This will be compared to its previous value using `Object.equals`
* @param block The block to execute other effects in
*/
-@CheckResult(suggest = "+")
+@CheckResult("+")
fun <T> key(vararg inputs: Any?, block: Effect<T>.() -> T) =
Effect(
block,
@@ -286,7 +284,7 @@
* @param calculation A function to produce the result
* @return The result of the calculation, or the cached value from the composition
*/
-@CheckResult(suggest = "+")
+@CheckResult("+")
/* inline */ fun <T> memo(/* crossinline */ calculation: () -> T) = effectOf<T> {
context.remember(calculation)
}
@@ -298,7 +296,7 @@
* @param calculation A function to produce the result
* @return The result of the calculation, or the cached value from the composition
*/
-@CheckResult(suggest = "+")
+@CheckResult("+")
/* inline */ fun <T, /* reified */ V1> memo(
v1: V1,
/* crossinline */
@@ -315,7 +313,7 @@
* @param calculation A function to produce the result
* @return The result of the calculation, or the cached value from the composition
*/
-@CheckResult(suggest = "+")
+@CheckResult("+")
/* inline */ fun <T, /* reified */ V1, /* reified */ V2> memo(
v1: V1,
v2: V2,
@@ -332,7 +330,7 @@
* @param calculation A function to produce the result
* @return The result of the calculation, or the cached value from the composition
*/
-@CheckResult(suggest = "+")
+@CheckResult("+")
fun <T> memo(vararg inputs: Any?, calculation: () -> T) = effectOf<T> {
context.remember(*inputs) { calculation() }
}
@@ -351,7 +349,7 @@
* @see [onPreCommit]
* @see [onDispose]
*/
-@CheckResult(suggest = "+")
+@CheckResult("+")
fun onActive(callback: CommitScope.() -> Unit) = effectOf<Unit> {
context.remember { PostCommitScopeImpl(callback) }
}
@@ -367,7 +365,7 @@
* @see [onPreCommit]
* @see [onActive]
*/
-@CheckResult(suggest = "+")
+@CheckResult("+")
fun onDispose(callback: () -> Unit) = onActive { onDispose(callback) }
/**
@@ -382,7 +380,7 @@
* @see [onPreCommit]
* @see [onActive]
*/
-@CheckResult(suggest = "+")
+@CheckResult("+")
fun onCommit(callback: CommitScope.() -> Unit) = effectOf<Unit> {
context.changed(PostCommitScopeImpl(callback))
}
@@ -400,7 +398,7 @@
* @see [onPreCommit]
* @see [onActive]
*/
-@CheckResult(suggest = "+")
+@CheckResult("+")
/* inline */ fun </* reified */ V1> onCommit(
v1: V1,
/* noinline */
@@ -423,7 +421,7 @@
* @see [onPreCommit]
* @see [onActive]
*/
-@CheckResult(suggest = "+")
+@CheckResult("+")
/* inline */ fun </* reified */ V1, /* reified */ V2> onCommit(
v1: V1,
v2: V2,
@@ -446,7 +444,7 @@
* @see [onPreCommit]
* @see [onActive]
*/
-@CheckResult(suggest = "+")
+@CheckResult("+")
fun onCommit(vararg inputs: Any?, callback: CommitScope.() -> Unit) =
effectOf<Unit> {
context.remember(*inputs) { PostCommitScopeImpl(callback) }
@@ -466,7 +464,7 @@
* @see [onPreCommit]
* @see [onActive]
*/
-@CheckResult(suggest = "+")
+@CheckResult("+")
fun onPreCommit(callback: CommitScope.() -> Unit) =
effectOf<Unit> {
context.changed(PreCommitScopeImpl(callback))
@@ -487,7 +485,7 @@
* @see [onCommit]
* @see [onActive]
*/
-@CheckResult(suggest = "+")
+@CheckResult("+")
/* inline */ fun </* reified */ V1> onPreCommit(
v1: V1,
/* noinline */
@@ -512,7 +510,7 @@
* @see [onCommit]
* @see [onActive]
*/
-@CheckResult(suggest = "+")
+@CheckResult("+")
/* inline */ fun </* reified */ V1, /* reified */ V2> onPreCommit(
v1: V1,
v2: V2,
@@ -537,7 +535,7 @@
* @see [onCommit]
* @see [onActive]
*/
-@CheckResult(suggest = "+")
+@CheckResult("+")
fun onPreCommit(vararg inputs: Any?, callback: CommitScope.() -> Unit) =
effectOf<Unit> {
context.remember(*inputs) { PreCommitScopeImpl(callback) }
@@ -604,7 +602,7 @@
* @see [model]
* @see [modelFor]
*/
-@CheckResult(suggest = "+")
+@CheckResult("+")
/* inline */ fun <T> state(/* crossinline */ init: () -> T) =
memo { State(init()) }
@@ -625,7 +623,7 @@
* @see [model]
* @see [modelFor]
*/
-@CheckResult(suggest = "+")
+@CheckResult("+")
/* inline */ fun <T, /* reified */ V1> stateFor(v1: V1, /* crossinline */ init: () -> T) =
memo(v1) { State(init()) }
@@ -647,7 +645,7 @@
* @see [model]
* @see [modelFor]
*/
-@CheckResult(suggest = "+")
+@CheckResult("+")
/* inline */ fun <T, /* reified */ V1, /* reified */ V2> stateFor(
v1: V1,
v2: V2,
@@ -672,7 +670,7 @@
* @see [model]
* @see [modelFor]
*/
-@CheckResult(suggest = "+")
+@CheckResult("+")
/* inline */ fun <T> stateFor(vararg inputs: Any?, /* crossinline */ init: () -> T) =
memo(*inputs) { State(init()) }
@@ -686,8 +684,9 @@
* @see [state]
* @see [stateFor]
*/
+// TODO(138720404): Investigate if Compose APIs can be internal despite MPP limitations
@Model
-class State<T> @PublishedApi internal constructor(value: T) : Framed {
+class State<T> /*@PublishedApi internal*/ constructor(value: T) : Framed {
/* NOTE(lmr): When this module is compiled with IR, we will need to remove the below Framed implementation */
@Suppress("UNCHECKED_CAST")
@@ -784,7 +783,7 @@
* @see [state]
* @see [stateFor]
*/
-@CheckResult(suggest = "+")
+@CheckResult("+")
/* inline */ fun <T> model(/* crossinline */ init: () -> T) = memo { init() }
/**
@@ -802,7 +801,7 @@
* @see [state]
* @see [stateFor]
*/
-@CheckResult(suggest = "+")
+@CheckResult("+")
/* inline */ fun <T, /* reified */ V1> modelFor(v1: V1, /* crossinline */ init: () -> T) =
memo(v1) { init() }
@@ -822,7 +821,7 @@
* @see [state]
* @see [stateFor]
*/
-@CheckResult(suggest = "+")
+@CheckResult("+")
/* inline */ fun <
T,
/* reified */ V1,
@@ -845,7 +844,7 @@
* @see [state]
* @see [stateFor]
*/
-@CheckResult(suggest = "+")
+@CheckResult("+")
/* inline */ fun <T> modelFor(vararg inputs: Any?, /* crossinline */ init: () -> T) =
memo(*inputs) { init() }
@@ -857,7 +856,7 @@
*
* @see [Ambient]
*/
-@CheckResult(suggest = "+")
+@CheckResult("+")
fun <T> ambient(key: Ambient<T>) = effectOf<T> {
context.consume(key)
}
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/EffectsDsl.kt b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/EffectsDsl.kt
similarity index 100%
rename from compose/compose-runtime/src/main/java/androidx/compose/EffectsDsl.kt
rename to compose/compose-runtime/src/commonMain/kotlin/androidx/compose/EffectsDsl.kt
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/Emittable.kt b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Emittable.kt
similarity index 100%
rename from compose/compose-runtime/src/main/java/androidx/compose/Emittable.kt
rename to compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Emittable.kt
diff --git a/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Expect.kt b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Expect.kt
new file mode 100644
index 0000000..59307db
--- /dev/null
+++ b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Expect.kt
@@ -0,0 +1,102 @@
+/*
+ * 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.compose
+
+expect class BitSet() {
+ fun set(bitIndex: Int)
+ fun or(set: BitSet)
+ fun clear(bitIndex: Int)
+ operator fun get(bitIndex: Int): Boolean
+}
+
+expect open class ThreadLocal<T>() {
+ fun get(): T?
+ fun set(value: T?)
+ protected open fun initialValue(): T?
+}
+
+expect class WeakHashMap<K, V>() : MutableMap<K, V>
+
+internal expect fun arraycopy(source: Any, sourcePos: Int, dest: Any, destPos: Int, len: Int)
+
+expect fun identityHashCode(instance: Any?): Int
+
+expect interface ViewParent
+
+expect open class View {
+ fun getTag(key: Int): Any
+ fun setTag(key: Int, tag: Any?)
+}
+expect val View.parent: ViewParent
+expect val View.context: Context
+
+expect abstract class ViewGroup : View {
+ fun removeAllViews()
+}
+
+expect abstract class Context
+
+expect class FrameLayout(context: Context)
+
+expect inline fun <R> synchronized(lock: Any, block: () -> R): R
+
+expect class WeakReference<T>(instance: T) {
+ fun get(): T?
+}
+
+expect class Looper
+
+expect fun isMainThread(): Boolean
+
+expect object LooperWrapper {
+ fun getMainLooper(): Looper
+}
+
+expect class Handler(looper: Looper) {
+ fun postAtFrontOfQueue(block: () -> Unit): Boolean
+}
+
+expect object Choreographer {
+ fun postFrameCallback(callback: (Long) -> Unit)
+ fun postFrameCallbackDelayed(delayMillis: Long, callback: (Long) -> Unit)
+ fun removeFrameCallback(callback: (Long) -> Unit)
+}
+
+@MustBeDocumented
+@Retention(AnnotationRetention.BINARY)
+@Target(
+ AnnotationTarget.FUNCTION,
+ AnnotationTarget.CONSTRUCTOR
+)
+expect annotation class MainThread()
+
+@MustBeDocumented
+@Retention(AnnotationRetention.SOURCE)
+@Target(
+ AnnotationTarget.FUNCTION,
+ AnnotationTarget.CONSTRUCTOR
+)
+expect annotation class TestOnly()
+
+@Target(AnnotationTarget.FUNCTION,
+ AnnotationTarget.PROPERTY_GETTER,
+ AnnotationTarget.PROPERTY_SETTER)
+@Retention(AnnotationRetention.BINARY)
+@MustBeDocumented
+expect annotation class CheckResult(
+ val suggest: String
+)
\ No newline at end of file
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/FrameManager.kt b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/FrameManager.kt
similarity index 97%
rename from compose/compose-runtime/src/main/java/androidx/compose/FrameManager.kt
rename to compose/compose-runtime/src/commonMain/kotlin/androidx/compose/FrameManager.kt
index 0937e0f..f5abf9a 100644
--- a/compose/compose-runtime/src/main/java/androidx/compose/FrameManager.kt
+++ b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/FrameManager.kt
@@ -16,8 +16,6 @@
package androidx.compose
-import android.os.Handler
-import android.os.Looper
import androidx.compose.frames.open
import androidx.compose.frames.commit
import androidx.compose.frames.suspend
@@ -40,7 +38,7 @@
private var invalidations = ObserverMap<Any, RecomposeScope>()
private var removeCommitObserver: (() -> Unit)? = null
- private val handler by lazy { Handler(Looper.getMainLooper()) }
+ private val handler by lazy { Handler(LooperWrapper.getMainLooper()) }
fun ensureStarted() {
if (!started) {
@@ -163,4 +161,4 @@
private inline fun schedule(crossinline block: () -> Unit) {
handler.postAtFrontOfQueue { block() }
}
-}
+}
\ No newline at end of file
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/HiddenAttribute.kt b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/HiddenAttribute.kt
similarity index 100%
rename from compose/compose-runtime/src/main/java/androidx/compose/HiddenAttribute.kt
rename to compose/compose-runtime/src/commonMain/kotlin/androidx/compose/HiddenAttribute.kt
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/JoinedKey.kt b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Immutable.kt
similarity index 82%
copy from compose/compose-runtime/src/main/java/androidx/compose/JoinedKey.kt
copy to compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Immutable.kt
index 601fbf1..a490564 100644
--- a/compose/compose-runtime/src/main/java/androidx/compose/JoinedKey.kt
+++ b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Immutable.kt
@@ -16,7 +16,8 @@
package androidx.compose
-internal data class JoinedKey(
- @JvmField val left: Any?,
- @JvmField val right: Any?
-)
\ No newline at end of file
+@MustBeDocumented
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.BINARY)
+@StableMarker
+annotation class Immutable
\ No newline at end of file
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/JoinedKey.kt b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/JoinedKey.kt
similarity index 96%
rename from compose/compose-runtime/src/main/java/androidx/compose/JoinedKey.kt
rename to compose/compose-runtime/src/commonMain/kotlin/androidx/compose/JoinedKey.kt
index 601fbf1..bd37846 100644
--- a/compose/compose-runtime/src/main/java/androidx/compose/JoinedKey.kt
+++ b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/JoinedKey.kt
@@ -16,6 +16,8 @@
package androidx.compose
+import kotlin.jvm.JvmField
+
internal data class JoinedKey(
@JvmField val left: Any?,
@JvmField val right: Any?
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/Key.kt b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Key.kt
similarity index 96%
rename from compose/compose-runtime/src/main/java/androidx/compose/Key.kt
rename to compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Key.kt
index 5557f00..41e9c56 100644
--- a/compose/compose-runtime/src/main/java/androidx/compose/Key.kt
+++ b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Key.kt
@@ -71,6 +71,6 @@
@Composable
@Suppress("PLUGIN_ERROR")
/* inline */
-fun Key(@Suppress("UNUSED_PARAMETER") @Pivotal key: Any?, @Children children: () -> Unit) {
+fun Key(@Suppress("UNUSED_PARAMETER") @Pivotal key: Any?, children: @Composable() () -> Unit) {
children()
}
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/Model.kt b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Model.kt
similarity index 99%
rename from compose/compose-runtime/src/main/java/androidx/compose/Model.kt
rename to compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Model.kt
index 6a53af3..9682b69 100644
--- a/compose/compose-runtime/src/main/java/androidx/compose/Model.kt
+++ b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Model.kt
@@ -53,4 +53,5 @@
@MustBeDocumented
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
+@StableMarker
annotation class Model
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/Observe.kt b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Observe.kt
similarity index 94%
rename from compose/compose-runtime/src/main/java/androidx/compose/Observe.kt
rename to compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Observe.kt
index 3a05644..6aa9dcb 100644
--- a/compose/compose-runtime/src/main/java/androidx/compose/Observe.kt
+++ b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Observe.kt
@@ -31,7 +31,7 @@
*/
@Composable
@Suppress("PLUGIN_ERROR")
-fun Observe(@Children body: @Composable() () -> Unit) =
+fun Observe(body: @Composable() () -> Unit) =
currentComposerNonNull.let { composer ->
trace("Compose:Observe") {
composer.startGroup(observer)
@@ -42,4 +42,4 @@
}
}
-private val observer = Object()
\ No newline at end of file
+private val observer = Any()
\ No newline at end of file
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/ObserverMap.kt b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/ObserverMap.kt
similarity index 97%
rename from compose/compose-runtime/src/main/java/androidx/compose/ObserverMap.kt
rename to compose/compose-runtime/src/commonMain/kotlin/androidx/compose/ObserverMap.kt
index 65623e7b..d0e4c36 100644
--- a/compose/compose-runtime/src/main/java/androidx/compose/ObserverMap.kt
+++ b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/ObserverMap.kt
@@ -16,8 +16,6 @@
package androidx.compose
-import java.lang.ref.WeakReference
-
/**
* A map from a key to a set of values used for keeping the relation between some
* entities and a models changes of which this entities are observing.
@@ -104,7 +102,7 @@
*/
private class WeakIdentity<T : Any>(value: T) {
// Save the hash code of value as it might be reclaimed making value.hashCode inaccessible
- private val myHc = System.identityHashCode(value)
+ private val myHc = identityHashCode(value)
// Preserve a weak reference to the value to prevent read observers from leaking observed values
private val weakValue = WeakReference(value)
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/Pivotal.kt b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Pivotal.kt
similarity index 100%
rename from compose/compose-runtime/src/main/java/androidx/compose/Pivotal.kt
rename to compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Pivotal.kt
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/Recompose.kt b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Recompose.kt
similarity index 96%
rename from compose/compose-runtime/src/main/java/androidx/compose/Recompose.kt
rename to compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Recompose.kt
index 421d431..fb57067 100644
--- a/compose/compose-runtime/src/main/java/androidx/compose/Recompose.kt
+++ b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Recompose.kt
@@ -59,7 +59,7 @@
* @see invalidate
*/
@Composable
-fun Recompose(@Children body: @Composable() (recompose: () -> Unit) -> Unit) {
+fun Recompose(body: @Composable() (recompose: () -> Unit) -> Unit) {
val composer = currentComposerNonNull
val recomposer = RecomposeHelper()
val callback = composer.startJoin(false) {
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/Recomposer.kt b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Recomposer.kt
similarity index 76%
rename from compose/compose-runtime/src/main/java/androidx/compose/Recomposer.kt
rename to compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Recomposer.kt
index 79ed5df..baa8521 100644
--- a/compose/compose-runtime/src/main/java/androidx/compose/Recomposer.kt
+++ b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Recomposer.kt
@@ -16,10 +16,6 @@
package androidx.compose
-import android.annotation.SuppressLint
-import android.os.Looper
-import android.view.Choreographer
-
abstract class Recomposer {
companion object {
@@ -31,17 +27,19 @@
*/
fun hasPendingChanges() = current().hasPendingChanges()
- @SuppressLint("SyntheticAccessor")
internal fun current(): Recomposer {
- assert(Looper.myLooper() == Looper.getMainLooper())
+ require(isMainThread()) {
+ "No Recomposer for this Thread"
+ }
return threadRecomposer.get() ?: error("No Recomposer for this Thread")
}
internal fun recompose(component: Component, composer: Composer<*>) =
current().recompose(component, composer)
- private val threadRecomposer = object : ThreadLocal<Recomposer>() {
- override fun initialValue(): Recomposer? = AndroidRecomposer()
+ // TODO delete the explicit type after https://youtrack.jetbrains.com/issue/KT-20996
+ private val threadRecomposer: ThreadLocal<Recomposer> = object : ThreadLocal<Recomposer>() {
+ override fun initialValue(): Recomposer? = createRecomposer()
}
}
@@ -83,7 +81,8 @@
}
}
- internal abstract fun hasPendingChanges(): Boolean
+ // TODO(138720404): Investigate if Compose APIs can be internal despite MPP limitations
+ protected abstract fun hasPendingChanges(): Boolean
internal fun scheduleRecompose(composer: Composer<*>) {
composers.add(composer)
@@ -104,21 +103,4 @@
}
}
-private class AndroidRecomposer : Recomposer() {
-
- private var frameScheduled = false
-
- private val frameCallback = Choreographer.FrameCallback {
- frameScheduled = false
- dispatchRecomposes()
- }
-
- override fun scheduleChangesDispatch() {
- if (!frameScheduled) {
- frameScheduled = true
- Choreographer.getInstance().postFrameCallback(frameCallback)
- }
- }
-
- override fun hasPendingChanges(): Boolean = frameScheduled
-}
\ No newline at end of file
+internal expect fun createRecomposer(): Recomposer
\ No newline at end of file
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/SlotTable.kt b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/SlotTable.kt
similarity index 90%
rename from compose/compose-runtime/src/main/java/androidx/compose/SlotTable.kt
rename to compose/compose-runtime/src/commonMain/kotlin/androidx/compose/SlotTable.kt
index 4193169..d93deac2 100644
--- a/compose/compose-runtime/src/main/java/androidx/compose/SlotTable.kt
+++ b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/SlotTable.kt
@@ -172,7 +172,7 @@
nodeCount = 0
if (validate) {
val groupStart = advance().asGroupStart
- assert(groupStart.kind == kind) { "Group kind changed" }
+ require(groupStart.kind == kind) { "Group kind changed" }
currentEnd = current + groupStart.slots
}
}
@@ -197,16 +197,16 @@
internal fun recordEndGroup(writing: Boolean, inserting: Boolean, uncertain: Boolean): Int {
var count = nodeCount
- assert(!startStack.isEmpty()) {
+ require(startStack.isNotEmpty()) {
"Invalid state. Unbalanced calls to startGroup() and endGroup()"
}
- assert(inserting || current == currentEnd) { "Expected to be at the end of a group" }
+ require(inserting || current == currentEnd) { "Expected to be at the end of a group" }
// Update group length
val startLocation = startStack.pop()
val groupKind = groupKindStack.pop()
val effectiveStartLocation = effectiveIndex(startLocation)
- assert(slots[effectiveStartLocation] === SlotTable.EMPTY ||
+ require(slots[effectiveStartLocation] === SlotTable.EMPTY ||
slots[effectiveStartLocation] is GroupStart
) {
"Invalid state. Start location stack doesn't refer to a start location"
@@ -217,7 +217,7 @@
} else {
val start = slots[effectiveStartLocation].asGroupStart
// A node count < 0 means that it was reported as uncertain while reading
- assert(start.slots == len && (nodeCount == start.nodes || uncertain)) {
+ require(start.slots == len && (nodeCount == start.nodes || uncertain)) {
"Invalid endGroup call, expected ${start.slots} slots and ${
start.nodes} nodes but received, $len slots and $nodeCount nodes"
}
@@ -261,7 +261,7 @@
*/
fun previous() {
if (emptyCount <= 0) {
- assert(current > 0) { "Invalid call to previous" }
+ require(current > 0) { "Invalid call to previous" }
current--
}
}
@@ -280,7 +280,7 @@
* End reporting empty for calls to net() and get().
*/
fun endEmpty() {
- assert(emptyCount > 0) { "Unbalanced begin/end empty" }
+ require(emptyCount > 0) { "Unbalanced begin/end empty" }
emptyCount--
}
@@ -302,7 +302,7 @@
* Skip a group. Must be called at the start of a group.
*/
fun skipGroup(): Int {
- assert(emptyCount == 0) { "Cannot skip while in an empty region" }
+ require(emptyCount == 0) { "Cannot skip while in an empty region" }
return advanceToNextGroup()
}
@@ -310,8 +310,8 @@
* Skip the to the end of the group.
*/
fun skipEnclosingGroup(): Int {
- assert(emptyCount == 0) { "Cannot skip the enclosing group while in an empty region" }
- assert(startStack.isNotEmpty()) { "No enclosing group to skip" }
+ require(emptyCount == 0) { "Cannot skip the enclosing group while in an empty region" }
+ require(startStack.isNotEmpty()) { "No enclosing group to skip" }
val startLocation = startStack.peek()
val start = get(startLocation).asGroupStart
current = currentEnd
@@ -347,7 +347,7 @@
* Skip the current item
*/
fun skipItem(): Int {
- assert(emptyCount == 0) { "Cannot skip an item in an empty region" }
+ require(emptyCount == 0) { "Cannot skip an item in an empty region" }
return advanceToNextItem()
}
@@ -376,7 +376,10 @@
}
override fun toString(): String {
- return "${javaClass.simpleName}(current=$current, size=${slots.size - table.gapLen}, gap=${
+ return "${this::class.simpleName}" +
+ "(current=$current, " +
+ "size=${slots.size - table.gapLen}, " +
+ "gap=${
if (table.gapLen > 0) "$table.gapStart-${table.gapStart + table.gapLen - 1}" else "none"}${
if (inEmpty) ", in empty" else ""})"
}
@@ -420,7 +423,7 @@
* current to be before the key.
*/
fun previous() {
- assert(current > 0) { "Invalid call to previous" }
+ require(current > 0) { "Invalid call to previous" }
current--
}
@@ -436,7 +439,7 @@
* Ends inserting.
*/
fun endInsert() {
- assert(insertCount > 0) { "Unbalenced begin/end insert" }
+ require(insertCount > 0) { "Unbalenced begin/end insert" }
insertCount--
}
@@ -458,7 +461,7 @@
* Skip a group. Must be called at the start of a group.
*/
fun skipGroup(): Int {
- assert(insertCount == 0) { "Cannot skip while inserting" }
+ require(insertCount == 0) { "Cannot skip while inserting" }
return advanceToNextGroup()
}
@@ -473,7 +476,7 @@
* a keyed group is expected.
*/
fun moveItem(offset: Int) {
- assert(insertCount == 0) { "Cannot move an item while inserting" }
+ require(insertCount == 0) { "Cannot move an item while inserting" }
val oldCurrent = current
val oldNodeCount = nodeCount
@@ -495,7 +498,7 @@
val newMoveLocation = moveLocation + moveLen
current = oldCurrent
nodeCount = oldNodeCount
- System.arraycopy(
+ arraycopy(
slots,
effectiveIndex(newMoveLocation),
slots,
@@ -508,14 +511,14 @@
// Remove the now duplicate entries
val anchorsRemoved = remove(moveLocation + moveLen, moveLen)
- assert(!anchorsRemoved) { "Unexpectedly removed anchors" }
+ require(!anchorsRemoved) { "Unexpectedly removed anchors" }
}
/**
* Remove an item. Must be called at the startGroup of an item.
*/
fun removeItem(): Boolean {
- assert(insertCount == 0) { "Cannot remove and item while inserting" }
+ require(insertCount == 0) { "Cannot remove and item while inserting" }
val oldCurrent = current
val count = advanceToNextItem()
val anchorsRemoved = remove(oldCurrent, current - oldCurrent)
@@ -560,7 +563,7 @@
* Skip the current item
*/
fun skipItem(): Int {
- assert(insertCount == 0) { "Cannot skip an item while inserting" }
+ require(insertCount == 0) { "Cannot skip an item while inserting" }
return advanceToNextItem()
}
@@ -579,10 +582,10 @@
if (table.anchors.isNotEmpty()) table.updateAnchors(index)
if (index < table.gapStart) {
val len = table.gapStart - index
- System.arraycopy(slots, index, slots, index + table.gapLen, len)
+ arraycopy(slots, index, slots, index + table.gapLen, len)
} else {
val len = index - table.gapStart
- System.arraycopy(
+ arraycopy(
slots,
table.gapStart + table.gapLen,
slots,
@@ -607,8 +610,8 @@
val oldCapacity = slots.size
val oldSize = slots.size - table.gapLen
// Double the size of the array, but at least MIN_GROWTH_SIZE and >= size
- val newCapacity = Math.max(
- Math.max(oldCapacity * 2, oldSize + size),
+ val newCapacity = kotlin.math.max(
+ kotlin.math.max(oldCapacity * 2, oldSize + size),
MIN_GROWTH_SIZE
)
val newSlots = arrayOfNulls<Any?>(newCapacity)
@@ -616,8 +619,8 @@
val oldGapEnd = table.gapStart + table.gapLen
val newGapEnd = table.gapStart + newGapLen
// Copy the old array into the new array
- System.arraycopy(slots, 0, newSlots, 0, table.gapStart)
- System.arraycopy(slots, oldGapEnd, newSlots, newGapEnd, oldCapacity - oldGapEnd)
+ arraycopy(slots, 0, newSlots, 0, table.gapStart)
+ arraycopy(slots, oldGapEnd, newSlots, newGapEnd, oldCapacity - oldGapEnd)
// Update the anchors
if (table.anchors.isNotEmpty()) table.anchorGapResize(newGapLen - table.gapLen)
@@ -638,7 +641,8 @@
}
}
- internal fun remove(start: Int, len: Int): Boolean {
+ // TODO(138720404): Investigate if Compose APIs can be internal despite MPP limitations
+ fun remove(start: Int, len: Int): Boolean {
return if (len > 0) {
pendingClear = false
var anchorsRemoved = false
@@ -667,7 +671,10 @@
pendingClear = false
table.clearGap()
}
- return "${javaClass.simpleName}(current=$current, size=${slots.size - table.gapLen}, gap=${
+ return "${this::class.simpleName}" +
+ "(current=$current, " +
+ "size=${slots.size - table.gapLen}, " +
+ "gap=${
if (table.gapLen > 0) "$table.gapStart-${table.gapStart + table.gapLen - 1}" else "none"}${
if (insertCount > 0) ", inserting" else ""})"
}
@@ -708,12 +715,12 @@
}
internal fun close(reader: SlotReader) {
- assert(reader.table === this && readers > 0) { "Unexpected reader close()" }
+ require(reader.table === this && readers > 0) { "Unexpected reader close()" }
readers--
}
internal fun close(writer: SlotWriter) {
- assert(writer.table === this && this.writer) { "Unexpected writer close()" }
+ require(writer.table === this && this.writer) { "Unexpected writer close()" }
this.writer = false
clearGap()
}
@@ -722,7 +729,8 @@
internal fun clearGap() = repeat(gapLen) { i -> slots[gapStart + i] = null }
- internal fun anchor(index: Int): Anchor {
+ // TODO(138720404): Investigate if Compose APIs can be internal despite MPP limitations
+ fun anchor(index: Int): Anchor {
// TODO: Consider a buffer gap list of anchors if middle inserts and deletes are common
val anchorIndex = effectiveIndex(index)
val location = anchors.search(anchorIndex)
@@ -819,7 +827,8 @@
}
}
- internal fun anchorLocation(anchor: Anchor) = anchor.loc.let {
+ // TODO(138720404): Investigate if Compose APIs can be internal despite MPP limitations
+ fun anchorLocation(anchor: Anchor) = anchor.loc.let {
if (it > gapStart) it - gapLen else it
}
diff --git a/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/StableMarker.kt b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/StableMarker.kt
new file mode 100644
index 0000000..1f8111d
--- /dev/null
+++ b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/StableMarker.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.compose
+
+/**
+ * [StableMarker] marks an annotation as indicating a type as having a stable
+ * equals comparision that can be used during composition. When all types passed
+ * as parameters to a [Composable] function are marked as stable then then the
+ * parameter values are compared for equality based on positional memoization and
+ * the call is skipped if all the values are the equal to the previous call.
+ *
+ * Primitive value types (such as Int, Float, etc), String and enum types are
+ * considered, a priori, stable.
+*
+ */
+@MustBeDocumented
+@Target(AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.BINARY)
+annotation class StableMarker
\ No newline at end of file
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/Stack.kt b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Stack.kt
similarity index 100%
rename from compose/compose-runtime/src/main/java/androidx/compose/Stack.kt
rename to compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Stack.kt
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/Stateful.kt b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Stateful.kt
similarity index 100%
rename from compose/compose-runtime/src/main/java/androidx/compose/Stateful.kt
rename to compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Stateful.kt
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/Trace.kt b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Trace.kt
similarity index 92%
rename from compose/compose-runtime/src/main/java/androidx/compose/Trace.kt
rename to compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Trace.kt
index e451296..24e89e2 100644
--- a/compose/compose-runtime/src/main/java/androidx/compose/Trace.kt
+++ b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Trace.kt
@@ -16,7 +16,10 @@
package androidx.compose
-import android.os.Trace
+expect object Trace {
+ fun beginSection(name: String)
+ fun endSection()
+}
/**
* Wrap the specified [block] in calls to [Trace.beginSection] (with the supplied [sectionName])
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/UnionType.kt b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/UnionType.kt
similarity index 100%
rename from compose/compose-runtime/src/main/java/androidx/compose/UnionType.kt
rename to compose/compose-runtime/src/commonMain/kotlin/androidx/compose/UnionType.kt
diff --git a/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/ViewComposerCommon.kt b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/ViewComposerCommon.kt
new file mode 100644
index 0000000..a6a573c6
--- /dev/null
+++ b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/ViewComposerCommon.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.compose
+
+internal expect val currentComposerNonNull: Composer<*>
+internal expect var currentComposer: Composer<*>?
+
+internal expect fun createComposer(root: Any, context: Context, recomposer: Recomposer): Composer<*>
+expect fun <T> Composer<*>.runWithCurrent(block: () -> T): T
+
+// TODO(138720404): Investigate if Compose APIs can be internal despite MPP limitations
+/*@PublishedApi
+internal*/ val invocation = Any()
+
+@PublishedApi
+internal val provider = Any()
+
+@PublishedApi
+internal val consumer = Any()
+
+@PublishedApi
+internal val reference = Any()
\ No newline at end of file
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/annotations/Hide.kt b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/annotations/Hide.kt
similarity index 100%
rename from compose/compose-runtime/src/main/java/androidx/compose/annotations/Hide.kt
rename to compose/compose-runtime/src/commonMain/kotlin/androidx/compose/annotations/Hide.kt
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/frames/FrameContainers.kt b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/frames/FrameContainers.kt
similarity index 81%
rename from compose/compose-runtime/src/main/java/androidx/compose/frames/FrameContainers.kt
rename to compose/compose-runtime/src/commonMain/kotlin/androidx/compose/frames/FrameContainers.kt
index 596d8fa0..716b22b 100644
--- a/compose/compose-runtime/src/main/java/androidx/compose/frames/FrameContainers.kt
+++ b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/frames/FrameContainers.kt
@@ -1,5 +1,23 @@
+/*
+ * 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.compose.frames
+import kotlin.jvm.JvmField
+
class ModelList<T> : MutableList<T>, Framed {
private var myFirst: Record =
ArrayContainer<T>()
@@ -12,16 +30,16 @@
@Suppress("UNCHECKED_CAST") private val readable: ArrayContainer<T>
get() =
- _readable(
- myFirst,
- this
- ) as ArrayContainer<T>
+ _readable(
+ myFirst,
+ this
+ ) as ArrayContainer<T>
@Suppress("UNCHECKED_CAST") private val writable: ArrayContainer<T>
get() =
- _writable(
- myFirst,
- this
- ) as ArrayContainer<T>
+ _writable(
+ myFirst,
+ this
+ ) as ArrayContainer<T>
fun asMutable(): MutableList<T> = writable.list
@@ -53,12 +71,13 @@
writable.list.subList(fromIndex, toIndex)
private class ArrayContainer<T> : AbstractRecord() {
- @JvmField var list: ArrayList<T> = arrayListOf<T>()
+ @JvmField
+ var list: ArrayList<T> = arrayListOf<T>()
override fun assign(value: Record) {
@Suppress("UNCHECKED_CAST")
(value as? ArrayContainer<T>)?.let {
- this.list = it.list.clone() as ArrayList<T>
+ this.list = it.list.toList() as ArrayList<T>
}
}
@@ -145,23 +164,26 @@
override fun retainAll(elements: Collection<T>): Boolean = error()
}
+// TODO delete the explicit type after https://youtrack.jetbrains.com/issue/KT-20996
private fun <T> immutableIterator(
iterator: MutableIterator<T>
-) = object : MutableIterator<T> by iterator {
+): MutableIterator<T> = object : MutableIterator<T> by iterator {
override fun remove() = error()
}
+// TODO delete the explicit type after https://youtrack.jetbrains.com/issue/KT-20996
private fun <T> immutableListIterator(
iterator: MutableListIterator<T>
-) = object : MutableListIterator<T> by iterator {
+): MutableListIterator<T> = object : MutableListIterator<T> by iterator {
override fun add(element: T) = error()
override fun remove() = error()
override fun set(element: T) = error()
}
+// TODO delete the explicit type after https://youtrack.jetbrains.com/issue/KT-20996
private fun <T> immutableCollection(
collection: MutableCollection<T>
-) = object : MutableCollection<T> by collection {
+): MutableCollection<T> = object : MutableCollection<T> by collection {
override fun add(element: T): Boolean = error()
override fun addAll(elements: Collection<T>): Boolean = error()
override fun clear() = error()
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/frames/Frames.kt b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/frames/Frames.kt
similarity index 93%
rename from compose/compose-runtime/src/main/java/androidx/compose/frames/Frames.kt
rename to compose/compose-runtime/src/commonMain/kotlin/androidx/compose/frames/Frames.kt
index d70216e..02ead93 100644
--- a/compose/compose-runtime/src/main/java/androidx/compose/frames/Frames.kt
+++ b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/frames/Frames.kt
@@ -1,11 +1,48 @@
+/*
+ * 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.compose.frames
-import java.util.BitSet
-import java.util.HashSet
+import androidx.compose.BitSet
+import androidx.compose.ThreadLocal
+import androidx.compose.synchronized
class FrameAborted(val frame: Frame) : RuntimeException("Frame aborted")
/**
+ * The frame records are created with frame ID CREATION_FRAME when not in a frame.
+ * This allows framed object to be created in the in static initializers when a
+ * frame could not have been created yet.
+ *
+ * The value 2 was chosen because it must be greater than 0, as 0 is reserved to
+ * indicated an invalid frame (in order to avoid an uninitialized record begin
+ * treated a valid record) and 1 is odd and treated as a speculation frame. That
+ * leaves 2 as the lowest valid frame.
+ */
+private const val CREATION_FRAME = 2
+
+/**
+ * Base implementation of a frame record
+ */
+abstract class AbstractRecord : Record {
+ override var frameId: Int = threadFrame.get()?.id ?: CREATION_FRAME
+ override var next: Record? = null
+}
+
+/**
* Frame local values of a framed object.
*/
interface Record {
@@ -30,32 +67,17 @@
fun create(): Record
}
-/**
- * The frame records are created with frame ID CREATION_FRAME when not in a frame.
- * This allows framed object to be created in the in static initializers when a
- * frame could not have been created yet.
- *
- * The value 2 was chosen because it must be greater than 0, as 0 is reserved to
- * indicated an invalid frame (in order to avoid an uninitialized record begin
- * treated a valid record) and 1 is odd and treated as a speculation frame. That
- * leaves 2 as the lowest valid frame.
- */
-private const val CREATION_FRAME = 2
-
-/**
- * Base implementation of a frame record
- */
-abstract class AbstractRecord : Record {
- override var frameId: Int = threadFrame.get()?.id ?: CREATION_FRAME
- override var next: Record? = null
+interface Framed {
+ val firstFrameRecord: Record
+ fun prependFrameRecord(value: Record)
}
-internal val threadFrame = ThreadLocal<Frame>()
-
typealias FrameReadObserver = (read: Any) -> Unit
typealias FrameWriteObserver = (write: Any) -> Unit
typealias FrameCommitObserver = (committed: Set<Any>) -> Unit
+private val threadFrame = ThreadLocal<Frame>()
+
/**
* Information about a frame including the frame id and whether or not it is read only.
*/
@@ -88,7 +110,8 @@
) {
internal val modified = if (readOnly) null else HashSet<Framed>()
- internal val readObservers = mutableListOf<FrameReadObserver>()
+ // TODO(138720404): Investigate if Compose APIs can be internal despite MPP limitations
+ val readObservers = mutableListOf<FrameReadObserver>()
init {
if (readObserver != null) {
@@ -129,7 +152,7 @@
val inFrame: Boolean get() = threadFrame.get() != null
// A global synchronization object
-private val sync = Object()
+private val sync = Any()
// The following variables should only be written when sync is taken
private val openFrames = BitSet()
@@ -418,11 +441,6 @@
fun _writable(r: Record, framed: Framed): Record = r.writable(framed)
fun _created(framed: Framed) = threadFrame.get()?.writeObserver?.let { it(framed) }
-interface Framed {
- val firstFrameRecord: Record
- fun prependFrameRecord(value: Record)
-}
-
fun <T : Record> T.writable(framed: Framed): T {
return this.writable(framed, currentFrame())
}
@@ -494,8 +512,8 @@
@Suppress("UNCHECKED_CAST")
(used(framed, id, frame.invalid) as T?)?.apply { frameId = Int.MAX_VALUE }
?: readData.create().apply {
- frameId = Int.MAX_VALUE; framed.prependFrameRecord(this as T)
- } as T
+ frameId = Int.MAX_VALUE; framed.prependFrameRecord(this as T)
+ } as T
}
newData.assign(readData)
newData.frameId = id
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/sourceLocation.kt b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/sourceLocation.kt
similarity index 100%
rename from compose/compose-runtime/src/main/java/androidx/compose/sourceLocation.kt
rename to compose/compose-runtime/src/commonMain/kotlin/androidx/compose/sourceLocation.kt
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/Immutability.kt b/compose/compose-runtime/src/main/java/androidx/compose/Immutability.kt
deleted file mode 100644
index fb5689e..0000000
--- a/compose/compose-runtime/src/main/java/androidx/compose/Immutability.kt
+++ /dev/null
@@ -1,37 +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.compose
-
-/**
- * Just a dummy implementation to prove the behavior for a couple simple cases.
- * TODO: Should return true for deeply immutable objects, frozen objects, primitives, value types, inline classes of immutables, @Model
- * TODO: When we know at compile time, we shouldn't be doing a runtime check for this
- */
-@PublishedApi
-internal fun isEffectivelyImmutable(value: Any?): Boolean {
- return when (value) {
- is String,
- is Int,
- is Double,
- is Float,
- is Short,
- is Byte,
- is Char,
- is Boolean -> true
- else -> false
- }
-}
\ No newline at end of file
diff --git a/compose/compose-runtime/src/test/java/androidx/compose/ComposeRobolectricTestRunner.kt b/compose/compose-runtime/src/test/java/androidx/compose/ComposeRobolectricTestRunner.kt
deleted file mode 100644
index ff656ad..0000000
--- a/compose/compose-runtime/src/test/java/androidx/compose/ComposeRobolectricTestRunner.kt
+++ /dev/null
@@ -1,29 +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.compose
-
-import org.junit.runners.model.FrameworkMethod
-import org.robolectric.RobolectricTestRunner
-import org.robolectric.internal.bytecode.InstrumentationConfiguration
-
-class ComposeRobolectricTestRunner(testClass: Class<*>) : RobolectricTestRunner(testClass) {
- override fun createClassLoaderConfig(method: FrameworkMethod?): InstrumentationConfiguration {
- val builder = InstrumentationConfiguration.Builder(super.createClassLoaderConfig(method))
- builder.doNotInstrumentPackage("androidx.compose")
- return builder.build()
- }
-}
\ No newline at end of file
diff --git a/compose/compose-runtime/src/test/java/androidx/compose/mock/ComposePoints.kt b/compose/compose-runtime/src/test/java/androidx/compose/mock/ComposePoints.kt
deleted file mode 100644
index 9bf3948..0000000
--- a/compose/compose-runtime/src/test/java/androidx/compose/mock/ComposePoints.kt
+++ /dev/null
@@ -1,23 +0,0 @@
-package androidx.compose.mock
-
-fun MockViewComposition.point(point: Point) {
- text("X: ${point.x} Y: ${point.y}")
-}
-
-fun MockViewValidator.point(point: Point) {
- text("X: ${point.x} Y: ${point.y}")
-}
-
-object SLPoints
-
-fun MockViewComposition.points(points: Iterable<Point>) {
- repeat(of = points) {
- memoize(SLPoints, it) { point(it) }
- }
-}
-
-fun MockViewValidator.points(points: Iterable<Point>) {
- repeat(of = points) {
- point(it)
- }
-}
diff --git a/compose/compose-runtime/src/test/java/androidx/compose/mock/ComposeReport.kt b/compose/compose-runtime/src/test/java/androidx/compose/mock/ComposeReport.kt
deleted file mode 100644
index 057f9ab..0000000
--- a/compose/compose-runtime/src/test/java/androidx/compose/mock/ComposeReport.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-package androidx.compose.mock
-
-fun MockViewComposition.reportsTo(report: Report) {
- text(report.from)
- text("reports to")
- text(report.to)
-}
-fun MockViewValidator.reportsTo(report: Report) {
- text(report.from)
- text("reports to")
- text(report.to)
-}
-
-fun MockViewComposition.reportsReport(reports: Iterable<Report>) {
- linear {
- repeat(of = reports) { report ->
- reportsTo(report)
- }
- }
-}
-
-fun MockViewValidator.reportsReport(reports: Iterable<Report>) {
- linear {
- repeat(of = reports) { report ->
- reportsTo(report)
- }
- }
-}
diff --git a/compose/compose-runtime/src/test/java/androidx/compose/mock/ContactModel.kt b/compose/compose-runtime/src/test/java/androidx/compose/mock/ContactModel.kt
deleted file mode 100644
index 3d16cf7..0000000
--- a/compose/compose-runtime/src/test/java/androidx/compose/mock/ContactModel.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-package androidx.compose.mock
-
-class ContactModel(
- var filter: String = "",
- val contacts: MutableList<Contact>,
- var selected: Contact? = null
-) {
- val filtered get() = contacts.filter { it.name.contains(filter) }
-
- fun add(contact: Contact, after: Contact? = null) {
- if (after == null) {
- contacts.add(contact)
- } else {
- contacts.add(find(after) + 1, contact)
- }
- }
-
- fun move(contact: Contact, after: Contact?) {
- if (after == null) {
- contacts.removeAt(find(contact))
- contacts.add(0, contact)
- } else {
- contacts.removeAt(find(contact))
- contacts.add(find(after) + 1, contact)
- }
- }
-
- private fun find(contact: Contact): Int {
- val index = contacts.indexOf(contact)
- if (index < 0) error("Contact $contact not found")
- return index
- }
-}
diff --git a/concurrent/futures/api/1.0.0-rc01.txt b/concurrent/futures/api/1.0.0-rc01.txt
new file mode 100644
index 0000000..beb76bd
--- /dev/null
+++ b/concurrent/futures/api/1.0.0-rc01.txt
@@ -0,0 +1,21 @@
+// Signature format: 3.0
+package androidx.concurrent.futures {
+
+ public final class CallbackToFutureAdapter {
+ method public static <T> com.google.common.util.concurrent.ListenableFuture<T!> getFuture(androidx.concurrent.futures.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.futures.CallbackToFutureAdapter.Completer<T!>) throws java.lang.Exception;
+ }
+
+}
+
diff --git a/concurrent/futures/api/restricted_1.0.0-rc01.txt b/concurrent/futures/api/restricted_1.0.0-rc01.txt
new file mode 100644
index 0000000..6dabf0b
--- /dev/null
+++ b/concurrent/futures/api/restricted_1.0.0-rc01.txt
@@ -0,0 +1,45 @@
+// Signature format: 3.0
+package androidx.concurrent.futures {
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class AbstractResolvableFuture<V> implements com.google.common.util.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(com.google.common.util.concurrent.ListenableFuture<? extends V>!);
+ method protected final boolean wasInterrupted();
+ }
+
+ public final class CallbackToFutureAdapter {
+ method public static <T> com.google.common.util.concurrent.ListenableFuture<T!> getFuture(androidx.concurrent.futures.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.futures.CallbackToFutureAdapter.Completer<T!>) throws java.lang.Exception;
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class ResolvableFuture<V> extends androidx.concurrent.futures.AbstractResolvableFuture<V> {
+ method public static <V> androidx.concurrent.futures.ResolvableFuture<V!>! create();
+ method public boolean set(V?);
+ method public boolean setException(Throwable!);
+ method public boolean setFuture(com.google.common.util.concurrent.ListenableFuture<? extends V>!);
+ }
+
+}
+
diff --git a/coordinatorlayout/src/main/java/androidx/coordinatorlayout/widget/CoordinatorLayout.java b/coordinatorlayout/src/main/java/androidx/coordinatorlayout/widget/CoordinatorLayout.java
index 2eb7405..4a30d17 100644
--- a/coordinatorlayout/src/main/java/androidx/coordinatorlayout/widget/CoordinatorLayout.java
+++ b/coordinatorlayout/src/main/java/androidx/coordinatorlayout/widget/CoordinatorLayout.java
@@ -246,6 +246,12 @@
setupForInsets();
super.setOnHierarchyChangeListener(new HierarchyChangeListener());
+
+ if (ViewCompat.getImportantForAccessibility(this)
+ == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+ ViewCompat.setImportantForAccessibility(this,
+ ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
}
@Override
diff --git a/core/core/api/1.2.0-alpha03.txt b/core/core/api/1.2.0-alpha03.txt
index f03894b..d39053e 100644
--- a/core/core/api/1.2.0-alpha03.txt
+++ b/core/core/api/1.2.0-alpha03.txt
@@ -1610,7 +1610,7 @@
}
public interface Predicate<T> {
- method public boolean test(T);
+ method public boolean test(T!);
}
public interface Supplier<T> {
diff --git a/core/core/api/current.txt b/core/core/api/current.txt
index f03894b..d39053e 100644
--- a/core/core/api/current.txt
+++ b/core/core/api/current.txt
@@ -1610,7 +1610,7 @@
}
public interface Predicate<T> {
- method public boolean test(T);
+ method public boolean test(T!);
}
public interface Supplier<T> {
diff --git a/core/core/api/restricted_1.2.0-alpha03.txt b/core/core/api/restricted_1.2.0-alpha03.txt
index 0693bb2..7991be2 100644
--- a/core/core/api/restricted_1.2.0-alpha03.txt
+++ b/core/core/api/restricted_1.2.0-alpha03.txt
@@ -138,15 +138,16 @@
method public static void putBinder(android.os.Bundle, String?, android.os.IBinder?);
}
- @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class ComponentActivity extends android.app.Activity implements androidx.core.view.KeyEventDispatcher.Component {
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class ComponentActivity extends android.app.Activity implements androidx.core.view.KeyEventDispatcher.Component androidx.lifecycle.LifecycleOwner {
ctor public ComponentActivity();
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public <T extends androidx.core.app.ComponentActivity.ExtraData> T! getExtraData(Class<T!>!);
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void putExtraData(androidx.core.app.ComponentActivity.ExtraData!);
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public <T extends androidx.core.app.ComponentActivity.ExtraData> T! getExtraData(Class<T!>!);
+ method public androidx.lifecycle.Lifecycle getLifecycle();
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void putExtraData(androidx.core.app.ComponentActivity.ExtraData!);
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public boolean superDispatchKeyEvent(android.view.KeyEvent!);
}
- @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static class ComponentActivity.ExtraData {
- ctor public ComponentActivity.ExtraData();
+ @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static class ComponentActivity.ExtraData {
+ ctor @Deprecated public ComponentActivity.ExtraData();
}
@RequiresApi(api=28) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class CoreComponentFactory extends android.app.AppComponentFactory {
@@ -1968,7 +1969,7 @@
}
public interface Predicate<T> {
- method public boolean test(T);
+ method public boolean test(T!);
}
public interface Supplier<T> {
diff --git a/core/core/api/restricted_current.txt b/core/core/api/restricted_current.txt
index 0693bb2..7991be2 100644
--- a/core/core/api/restricted_current.txt
+++ b/core/core/api/restricted_current.txt
@@ -138,15 +138,16 @@
method public static void putBinder(android.os.Bundle, String?, android.os.IBinder?);
}
- @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class ComponentActivity extends android.app.Activity implements androidx.core.view.KeyEventDispatcher.Component {
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class ComponentActivity extends android.app.Activity implements androidx.core.view.KeyEventDispatcher.Component androidx.lifecycle.LifecycleOwner {
ctor public ComponentActivity();
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public <T extends androidx.core.app.ComponentActivity.ExtraData> T! getExtraData(Class<T!>!);
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void putExtraData(androidx.core.app.ComponentActivity.ExtraData!);
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public <T extends androidx.core.app.ComponentActivity.ExtraData> T! getExtraData(Class<T!>!);
+ method public androidx.lifecycle.Lifecycle getLifecycle();
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void putExtraData(androidx.core.app.ComponentActivity.ExtraData!);
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public boolean superDispatchKeyEvent(android.view.KeyEvent!);
}
- @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static class ComponentActivity.ExtraData {
- ctor public ComponentActivity.ExtraData();
+ @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static class ComponentActivity.ExtraData {
+ ctor @Deprecated public ComponentActivity.ExtraData();
}
@RequiresApi(api=28) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class CoreComponentFactory extends android.app.AppComponentFactory {
@@ -1968,7 +1969,7 @@
}
public interface Predicate<T> {
- method public boolean test(T);
+ method public boolean test(T!);
}
public interface Supplier<T> {
diff --git a/core/core/src/androidTest/java/androidx/core/app/ComponentActivityTest.java b/core/core/src/androidTest/java/androidx/core/app/ComponentActivityTest.java
index cda201b..98f553f 100644
--- a/core/core/src/androidTest/java/androidx/core/app/ComponentActivityTest.java
+++ b/core/core/src/androidTest/java/androidx/core/app/ComponentActivityTest.java
@@ -27,6 +27,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+@SuppressWarnings("deprecation")
@RunWith(AndroidJUnit4.class)
@LargeTest
public class ComponentActivityTest extends BaseInstrumentationTestCase<TestComponentActivity> {
@@ -54,10 +55,10 @@
assertEquals(mTestExtraData, mComponentActivity.getExtraData(TestExtraData.class));
}
- public class NeverAddedExtraData extends ComponentActivity.ExtraData {
+ private class NeverAddedExtraData extends ComponentActivity.ExtraData {
}
- public class TestExtraData extends ComponentActivity.ExtraData {
+ private class TestExtraData extends ComponentActivity.ExtraData {
}
}
diff --git a/core/core/src/androidTest/java/androidx/core/os/TraceCompatTest.java b/core/core/src/androidTest/java/androidx/core/os/TraceCompatTest.java
index d36b79a..5f229c4 100644
--- a/core/core/src/androidTest/java/androidx/core/os/TraceCompatTest.java
+++ b/core/core/src/androidTest/java/androidx/core/os/TraceCompatTest.java
@@ -21,15 +21,18 @@
import static java.nio.charset.StandardCharsets.UTF_8;
import android.app.UiAutomation;
+import android.os.Build;
import android.os.ParcelFileDescriptor;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.LargeTest;
import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -50,12 +53,23 @@
mByteArrayOutputStream = new ByteArrayOutputStream();
}
+ @After
+ public void stopAtrace() throws IOException {
+ // Since API 23, 'async_stop' will work. On lower API levels it was broken (see aosp/157142)
+ if (Build.VERSION.SDK_INT >= 23) {
+ executeCommand("atrace --async_stop");
+ } else {
+ // Ensure tracing is not currently running by performing a short synchronous trace.
+ executeCommand("atrace -t 0");
+ }
+ }
+
@Test
public void beginAndEndSection() throws IOException {
startTrace();
TraceCompat.beginSection("beginAndEndSection");
TraceCompat.endSection();
- endTrace();
+ dumpTrace();
assertTraceContains("tracing_mark_write:\\ B\\|.*\\|beginAndEndSection");
assertTraceContains("tracing_mark_write:\\ E");
@@ -66,7 +80,7 @@
startTrace();
TraceCompat.beginAsyncSection("beginAndEndSectionAsync", /*cookie=*/5099);
TraceCompat.endAsyncSection("beginAndEndSectionAsync", /*cookie=*/5099);
- endTrace();
+ dumpTrace();
assertTraceContains("tracing_mark_write:\\ S\\|.*\\|beginAndEndSectionAsync\\|5099");
assertTraceContains("tracing_mark_write:\\ F\\|.*\\|beginAndEndSectionAsync\\|5099");
@@ -78,7 +92,7 @@
TraceCompat.setCounter("counterName", 42);
TraceCompat.setCounter("counterName", 47);
TraceCompat.setCounter("counterName", 9787);
- endTrace();
+ dumpTrace();
assertTraceContains("tracing_mark_write:\\ C\\|.*\\|counterName\\|42");
assertTraceContains("tracing_mark_write:\\ C\\|.*\\|counterName\\|47");
@@ -89,7 +103,7 @@
public void isEnabledDuringTrace() throws IOException {
startTrace();
boolean enabled = TraceCompat.isEnabled();
- endTrace();
+ dumpTrace();
assertThat(enabled).isTrue();
}
@@ -101,39 +115,45 @@
}
private void startTrace() throws IOException {
- UiAutomation automation = InstrumentationRegistry.getInstrumentation()
- .getUiAutomation();
String processName =
ApplicationProvider.getApplicationContext().getApplicationInfo().processName;
// Write the "async_start" status to the byte array to ensure atrace has fully started
// before issuing any trace commands. This will also capture any errors that occur during
// start so they can be added to the assertion error's message.
- writeDataToByteStream(automation.executeShellCommand(
- String.format("atrace --async_start -b %d -a %s", TRACE_BUFFER_SIZE,
- processName)),
+ executeCommand(
+ String.format("atrace --async_start -b %d -a %s", TRACE_BUFFER_SIZE, processName));
+ }
+
+ private void dumpTrace() throws IOException {
+ // On older versions of atrace, the -b option is required when dumping the trace so the
+ // trace buffer doesn't get cleared before being dumped.
+ executeCommand(
+ String.format("atrace --async_dump -b %d", TRACE_BUFFER_SIZE),
mByteArrayOutputStream);
}
- private void endTrace() throws IOException {
+ private static void executeCommand(@NonNull String command) throws IOException {
+ executeCommand(command, null);
+ }
+
+ private static void executeCommand(@NonNull String command,
+ @Nullable ByteArrayOutputStream outputStream) throws IOException {
UiAutomation automation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
- writeDataToByteStream(automation.executeShellCommand("atrace --async_stop"),
- mByteArrayOutputStream);
- }
- private void writeDataToByteStream(ParcelFileDescriptor pfDescriptor,
- ByteArrayOutputStream outputStream) throws IOException {
- try (ParcelFileDescriptor.AutoCloseInputStream inputStream =
+ try (ParcelFileDescriptor pfDescriptor = automation.executeShellCommand(command);
+ ParcelFileDescriptor.AutoCloseInputStream inputStream =
new ParcelFileDescriptor.AutoCloseInputStream(
pfDescriptor)) {
byte[] buffer = new byte[1024];
int length;
while ((length = inputStream.read(buffer)) >= 0) {
- outputStream.write(buffer, 0, length);
+ if (outputStream != null) {
+ outputStream.write(buffer, 0, length);
+ }
}
}
-
}
private void assertTraceContains(@NonNull String contentRegex) {
diff --git a/core/core/src/main/java/androidx/core/app/ComponentActivity.java b/core/core/src/main/java/androidx/core/app/ComponentActivity.java
index 9672a78..78ebed2 100644
--- a/core/core/src/main/java/androidx/core/app/ComponentActivity.java
+++ b/core/core/src/main/java/androidx/core/app/ComponentActivity.java
@@ -18,13 +18,22 @@
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
+import android.annotation.SuppressLint;
import android.app.Activity;
+import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;
+import androidx.annotation.CallSuper;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.collection.SimpleArrayMap;
import androidx.core.view.KeyEventDispatcher;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LifecycleRegistry;
+import androidx.lifecycle.ReportFragment;
/**
* Base class for activities that enables composition of higher level components.
@@ -36,15 +45,22 @@
* @hide
*/
@RestrictTo(LIBRARY_GROUP_PREFIX)
-public class ComponentActivity extends Activity
- implements KeyEventDispatcher.Component {
+public class ComponentActivity extends Activity implements
+ LifecycleOwner,
+ KeyEventDispatcher.Component {
/**
* Storage for {@link ExtraData} instances.
*
* <p>Note that these objects are not retained across configuration changes</p>
*/
+ @SuppressWarnings("deprecation")
private SimpleArrayMap<Class<? extends ExtraData>, ExtraData> mExtraDataMap =
new SimpleArrayMap<>();
+ /**
+ * This is only used for apps that have not switched to Fragments 1.1.0, where this
+ * behavior is provided by <code>androidx.activity.ComponentActivity</code>.
+ */
+ private LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);
/**
* Store an instance of {@link ExtraData} for later retrieval by class name
@@ -54,24 +70,49 @@
*
* @see #getExtraData
* @hide
+ * @deprecated Use {@link View#setTag(int, Object)} with the window's decor view.
*/
+ @SuppressWarnings("deprecation")
@RestrictTo(LIBRARY_GROUP_PREFIX)
+ @Deprecated
public void putExtraData(ExtraData extraData) {
mExtraDataMap.put(extraData.getClass(), extraData);
}
+ @SuppressLint("RestrictedApi")
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ ReportFragment.injectIfNeededIn(this);
+ }
+
+ @CallSuper
+ @Override
+ protected void onSaveInstanceState(@NonNull Bundle outState) {
+ mLifecycleRegistry.markState(Lifecycle.State.CREATED);
+ super.onSaveInstanceState(outState);
+ }
+
/**
* Retrieves a previously set {@link ExtraData} by class name.
*
* @see #putExtraData
* @hide
+ * @deprecated Use {@link View#getTag(int)} with the window's decor view.
*/
@RestrictTo(LIBRARY_GROUP_PREFIX)
- @SuppressWarnings("unchecked")
+ @SuppressWarnings({"unchecked", "deprecation"})
+ @Deprecated
public <T extends ExtraData> T getExtraData(Class<T> extraDataClass) {
return (T) mExtraDataMap.get(extraDataClass);
}
+ @NonNull
+ @Override
+ public Lifecycle getLifecycle() {
+ return mLifecycleRegistry;
+ }
+
/**
* @hide
*/
@@ -101,8 +142,12 @@
/**
* @hide
+ * @deprecated Store the object you want to save directly by using
+ * {@link View#setTag(int, Object)} with the window's decor view.
*/
+ @SuppressWarnings("DeprecatedIsStillUsed")
@RestrictTo(LIBRARY_GROUP_PREFIX)
+ @Deprecated
public static class ExtraData {
}
}
diff --git a/core/core/src/main/java/androidx/core/util/Predicate.java b/core/core/src/main/java/androidx/core/util/Predicate.java
index af73843..ceb9e6f 100644
--- a/core/core/src/main/java/androidx/core/util/Predicate.java
+++ b/core/core/src/main/java/androidx/core/util/Predicate.java
@@ -16,7 +16,7 @@
package androidx.core.util;
-import androidx.annotation.NonNull;
+import android.annotation.SuppressLint;
/**
* Compat version of {@link java.util.function.Predicate}
@@ -32,5 +32,6 @@
* @return {@code true} if the input argument matches the predicate,
* otherwise {@code false}
*/
- boolean test(@NonNull T t);
+ @SuppressLint("UnknownNullness")
+ boolean test(T t);
}
diff --git a/core/core/src/main/java/androidx/core/view/accessibility/AccessibilityNodeInfoCompat.java b/core/core/src/main/java/androidx/core/view/accessibility/AccessibilityNodeInfoCompat.java
index 48bee26..5671074 100644
--- a/core/core/src/main/java/androidx/core/view/accessibility/AccessibilityNodeInfoCompat.java
+++ b/core/core/src/main/java/androidx/core/view/accessibility/AccessibilityNodeInfoCompat.java
@@ -4026,12 +4026,27 @@
builder.append("; scrollable: " + isScrollable());
builder.append("; [");
- for (int actionBits = getActions(); actionBits != 0;) {
- final int action = 1 << Integer.numberOfTrailingZeros(actionBits);
- actionBits &= ~action;
- builder.append(getActionSymbolicName(action));
- if (actionBits != 0) {
- builder.append(", ");
+ if (Build.VERSION.SDK_INT >= 21) {
+ List<AccessibilityActionCompat> actions = getActionList();
+ for (int i = 0; i < actions.size(); i++) {
+ AccessibilityActionCompat action = actions.get(i);
+ String actionName = getActionSymbolicName(action.getId());
+ if (actionName.equals("ACTION_UNKNOWN") && action.getLabel() != null) {
+ actionName = action.getLabel().toString();
+ }
+ builder.append(actionName);
+ if (i != actions.size() - 1) {
+ builder.append(", ");
+ }
+ }
+ } else {
+ for (int actionBits = getActions(); actionBits != 0;) {
+ final int action = 1 << Integer.numberOfTrailingZeros(actionBits);
+ actionBits &= ~action;
+ builder.append(getActionSymbolicName(action));
+ if (actionBits != 0) {
+ builder.append(", ");
+ }
}
}
builder.append("]");
@@ -4093,6 +4108,22 @@
return "ACTION_PASTE";
case ACTION_SET_SELECTION:
return "ACTION_SET_SELECTION";
+ case android.R.id.accessibilityActionScrollUp:
+ return "ACTION_SCROLL_UP";
+ case android.R.id.accessibilityActionScrollLeft:
+ return "ACTION_SCROLL_LEFT";
+ case android.R.id.accessibilityActionScrollDown:
+ return "ACTION_SCROLL_DOWN";
+ case android.R.id.accessibilityActionScrollRight:
+ return "ACTION_SCROLL_RIGHT";
+ case android.R.id.accessibilityActionPageDown:
+ return "ACTION_PAGE_DOWN";
+ case android.R.id.accessibilityActionPageUp:
+ return "ACTION_PAGE_UP";
+ case android.R.id.accessibilityActionPageLeft:
+ return "ACTION_PAGE_LEFT";
+ case android.R.id.accessibilityActionPageRight:
+ return "ACTION_PAGE_RIGHT";
default:
return"ACTION_UNKNOWN";
}
diff --git a/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java b/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java
index ff7c541..1a37b72 100644
--- a/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java
+++ b/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java
@@ -2927,7 +2927,7 @@
// Names for the data formats for debugging purpose.
static final String[] IFD_FORMAT_NAMES = new String[] {
"", "BYTE", "STRING", "USHORT", "ULONG", "URATIONAL", "SBYTE", "UNDEFINED", "SSHORT",
- "SLONG", "SRATIONAL", "SINGLE", "DOUBLE"
+ "SLONG", "SRATIONAL", "SINGLE", "DOUBLE", "IFD"
};
// Sizes of the components of each IFD value format
static final int[] IFD_FORMAT_BYTES_PER_FORMAT = new int[] {
diff --git a/fragment/fragment-ktx/api/1.2.0-alpha02.txt b/fragment/fragment-ktx/api/1.2.0-alpha02.txt
index dd42d83..ab3b3b7 100644
--- a/fragment/fragment-ktx/api/1.2.0-alpha02.txt
+++ b/fragment/fragment-ktx/api/1.2.0-alpha02.txt
@@ -22,5 +22,10 @@
method @MainThread public static inline <reified VM extends androidx.lifecycle.ViewModel> kotlin.Lazy<VM> viewModels(androidx.fragment.app.Fragment, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelStoreOwner> ownerProducer = { this }, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>? factoryProducer = null);
}
+ public final class ViewKt {
+ ctor public ViewKt();
+ method public static <F extends androidx.fragment.app.Fragment> F findFragment(android.view.View);
+ }
+
}
diff --git a/fragment/fragment-ktx/api/api_lint.ignore b/fragment/fragment-ktx/api/api_lint.ignore
deleted file mode 100644
index cd75382..0000000
--- a/fragment/fragment-ktx/api/api_lint.ignore
+++ /dev/null
@@ -1,3 +0,0 @@
-// Baseline format: 1.0
-DocumentExceptions: androidx.fragment.app.FragmentViewModelLazyKt#createViewModelLazy(androidx.fragment.app.Fragment, kotlin.reflect.KClass<VM>, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelStore>, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>):
- Method FragmentViewModelLazyKt.createViewModelLazy appears to be throwing java.lang.IllegalStateException; this should be listed in the documentation; see https://android.github.io/kotlin-guides/interop.html#document-exceptions
diff --git a/fragment/fragment-ktx/api/current.txt b/fragment/fragment-ktx/api/current.txt
index dd42d83..ab3b3b7 100644
--- a/fragment/fragment-ktx/api/current.txt
+++ b/fragment/fragment-ktx/api/current.txt
@@ -22,5 +22,10 @@
method @MainThread public static inline <reified VM extends androidx.lifecycle.ViewModel> kotlin.Lazy<VM> viewModels(androidx.fragment.app.Fragment, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelStoreOwner> ownerProducer = { this }, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>? factoryProducer = null);
}
+ public final class ViewKt {
+ ctor public ViewKt();
+ method public static <F extends androidx.fragment.app.Fragment> F findFragment(android.view.View);
+ }
+
}
diff --git a/fragment/fragment-ktx/api/restricted_1.2.0-alpha02.txt b/fragment/fragment-ktx/api/restricted_1.2.0-alpha02.txt
index dd42d83..ab3b3b7 100644
--- a/fragment/fragment-ktx/api/restricted_1.2.0-alpha02.txt
+++ b/fragment/fragment-ktx/api/restricted_1.2.0-alpha02.txt
@@ -22,5 +22,10 @@
method @MainThread public static inline <reified VM extends androidx.lifecycle.ViewModel> kotlin.Lazy<VM> viewModels(androidx.fragment.app.Fragment, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelStoreOwner> ownerProducer = { this }, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>? factoryProducer = null);
}
+ public final class ViewKt {
+ ctor public ViewKt();
+ method public static <F extends androidx.fragment.app.Fragment> F findFragment(android.view.View);
+ }
+
}
diff --git a/fragment/fragment-ktx/api/restricted_current.txt b/fragment/fragment-ktx/api/restricted_current.txt
index dd42d83..ab3b3b7 100644
--- a/fragment/fragment-ktx/api/restricted_current.txt
+++ b/fragment/fragment-ktx/api/restricted_current.txt
@@ -22,5 +22,10 @@
method @MainThread public static inline <reified VM extends androidx.lifecycle.ViewModel> kotlin.Lazy<VM> viewModels(androidx.fragment.app.Fragment, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelStoreOwner> ownerProducer = { this }, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>? factoryProducer = null);
}
+ public final class ViewKt {
+ ctor public ViewKt();
+ method public static <F extends androidx.fragment.app.Fragment> F findFragment(android.view.View);
+ }
+
}
diff --git a/fragment/fragment-ktx/src/androidTest/java/androidx/fragment/app/ViewTest.kt b/fragment/fragment-ktx/src/androidTest/java/androidx/fragment/app/ViewTest.kt
new file mode 100644
index 0000000..d1eb53a
--- /dev/null
+++ b/fragment/fragment-ktx/src/androidTest/java/androidx/fragment/app/ViewTest.kt
@@ -0,0 +1,77 @@
+/*
+ * 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.fragment.app
+
+import android.content.Context
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.test.annotation.UiThreadTest
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.rule.ActivityTestRule
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Assert.fail
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ViewTest {
+ @get:Rule val activityRule = ActivityTestRule<TestActivity>(TestActivity::class.java)
+ private val fragmentManager get() = activityRule.activity.supportFragmentManager
+
+ @UiThreadTest
+ @Test
+ fun findFragment() {
+ val fragment = ViewFragment()
+ fragmentManager.commitNow {
+ add(android.R.id.content, fragment)
+ }
+
+ val foundFragment = fragment.requireView().findFragment<ViewFragment>()
+ assertWithMessage("View should have Fragment set")
+ .that(foundFragment)
+ .isSameInstanceAs(fragment)
+ }
+
+ @Test
+ fun findFragmentNull() {
+ val view = View(ApplicationProvider.getApplicationContext() as Context)
+ try {
+ view.findFragment<Fragment>()
+ fail("findFragment should throw IllegalStateException if a Fragment was not set")
+ } catch (e: IllegalStateException) {
+ assertThat(e)
+ .hasMessageThat().contains("View $view does not have a Fragment set")
+ }
+ }
+}
+
+class ViewFragment : Fragment() {
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ return View(context)
+ }
+}
diff --git a/fragment/fragment-ktx/src/main/java/androidx/fragment/app/FragmentViewModelLazy.kt b/fragment/fragment-ktx/src/main/java/androidx/fragment/app/FragmentViewModelLazy.kt
index f67b19a..b9bf7b5 100644
--- a/fragment/fragment-ktx/src/main/java/androidx/fragment/app/FragmentViewModelLazy.kt
+++ b/fragment/fragment-ktx/src/main/java/androidx/fragment/app/FragmentViewModelLazy.kt
@@ -20,7 +20,6 @@
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelLazy
import androidx.lifecycle.ViewModelProvider
-import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory
import androidx.lifecycle.ViewModelProvider.Factory
import androidx.lifecycle.ViewModelStore
import androidx.lifecycle.ViewModelStoreOwner
@@ -61,7 +60,9 @@
/**
* Returns a property delegate to access parent activity's [ViewModel],
* if [factoryProducer] is specified then [ViewModelProvider.Factory]
- * returned by it will be used to create [ViewModel] first time.
+ * returned by it will be used to create [ViewModel] first time. Otherwise, the activity's
+ * [androidx.activity.ComponentActivity.getDefaultViewModelProviderFactory](default factory)
+ * will be used.
*
* ```
* class MyFragment : Fragment() {
@@ -75,7 +76,8 @@
@MainThread
inline fun <reified VM : ViewModel> Fragment.activityViewModels(
noinline factoryProducer: (() -> Factory)? = null
-) = createViewModelLazy(VM::class, { requireActivity().viewModelStore }, factoryProducer)
+) = createViewModelLazy(VM::class, { requireActivity().viewModelStore },
+ factoryProducer ?: { requireActivity().defaultViewModelProviderFactory })
/**
* Helper method for creation of [ViewModelLazy], that resolves `null` passed as [factoryProducer]
@@ -88,10 +90,7 @@
factoryProducer: (() -> Factory)? = null
): Lazy<VM> {
val factoryPromise = factoryProducer ?: {
- val application = activity?.application ?: throw IllegalStateException(
- "ViewModel can be accessed only when Fragment is attached"
- )
- AndroidViewModelFactory.getInstance(application)
+ defaultViewModelProviderFactory
}
return ViewModelLazy(viewModelClass, storeProducer, factoryPromise)
-}
+}
\ No newline at end of file
diff --git a/fragment/fragment-ktx/src/main/java/androidx/fragment/app/View.kt b/fragment/fragment-ktx/src/main/java/androidx/fragment/app/View.kt
new file mode 100644
index 0000000..63ae98f
--- /dev/null
+++ b/fragment/fragment-ktx/src/main/java/androidx/fragment/app/View.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.fragment.app
+
+import android.view.View
+
+/**
+ * Find a [Fragment] associated with a [View].
+ *
+ * This method will locate the [Fragment] associated with this view. This is automatically
+ * populated for the View returned by [Fragment.onCreateView] and its children.
+ *
+ * Calling this on a View that does not have a Fragment set will result in an
+ * [IllegalStateException]
+ */
+fun <F : Fragment> View.findFragment(): F = FragmentManager.findFragment(this)
diff --git a/fragment/fragment/api/1.2.0-alpha02.txt b/fragment/fragment/api/1.2.0-alpha02.txt
index 1bebde6..672cb24 100644
--- a/fragment/fragment/api/1.2.0-alpha02.txt
+++ b/fragment/fragment/api/1.2.0-alpha02.txt
@@ -25,7 +25,7 @@
field public static final int STYLE_NO_TITLE = 1; // 0x1
}
- public class Fragment implements android.content.ComponentCallbacks androidx.lifecycle.LifecycleOwner androidx.savedstate.SavedStateRegistryOwner android.view.View.OnCreateContextMenuListener androidx.lifecycle.ViewModelStoreOwner {
+ public class Fragment implements android.content.ComponentCallbacks androidx.lifecycle.HasDefaultViewModelProviderFactory androidx.lifecycle.LifecycleOwner androidx.savedstate.SavedStateRegistryOwner android.view.View.OnCreateContextMenuListener androidx.lifecycle.ViewModelStoreOwner {
ctor public Fragment();
ctor @ContentView public Fragment(@LayoutRes int);
method public void dump(String, java.io.FileDescriptor?, java.io.PrintWriter, String![]?);
@@ -36,6 +36,7 @@
method public final android.os.Bundle? getArguments();
method public final androidx.fragment.app.FragmentManager getChildFragmentManager();
method public android.content.Context? getContext();
+ method public androidx.lifecycle.ViewModelProvider.Factory getDefaultViewModelProviderFactory();
method public Object? getEnterTransition();
method public Object? getExitTransition();
method public final androidx.fragment.app.FragmentManager? getFragmentManager();
@@ -269,6 +270,7 @@
method public void dump(String, java.io.FileDescriptor?, java.io.PrintWriter, String![]?);
method public static void enableDebugLogging(boolean);
method public boolean executePendingTransactions();
+ method public static <F extends androidx.fragment.app.Fragment> F findFragment(android.view.View);
method public androidx.fragment.app.Fragment? findFragmentById(@IdRes int);
method public androidx.fragment.app.Fragment? findFragmentByTag(String?);
method public androidx.fragment.app.FragmentManager.BackStackEntry getBackStackEntryAt(int);
diff --git a/fragment/fragment/api/current.txt b/fragment/fragment/api/current.txt
index 1bebde6..672cb24 100644
--- a/fragment/fragment/api/current.txt
+++ b/fragment/fragment/api/current.txt
@@ -25,7 +25,7 @@
field public static final int STYLE_NO_TITLE = 1; // 0x1
}
- public class Fragment implements android.content.ComponentCallbacks androidx.lifecycle.LifecycleOwner androidx.savedstate.SavedStateRegistryOwner android.view.View.OnCreateContextMenuListener androidx.lifecycle.ViewModelStoreOwner {
+ public class Fragment implements android.content.ComponentCallbacks androidx.lifecycle.HasDefaultViewModelProviderFactory androidx.lifecycle.LifecycleOwner androidx.savedstate.SavedStateRegistryOwner android.view.View.OnCreateContextMenuListener androidx.lifecycle.ViewModelStoreOwner {
ctor public Fragment();
ctor @ContentView public Fragment(@LayoutRes int);
method public void dump(String, java.io.FileDescriptor?, java.io.PrintWriter, String![]?);
@@ -36,6 +36,7 @@
method public final android.os.Bundle? getArguments();
method public final androidx.fragment.app.FragmentManager getChildFragmentManager();
method public android.content.Context? getContext();
+ method public androidx.lifecycle.ViewModelProvider.Factory getDefaultViewModelProviderFactory();
method public Object? getEnterTransition();
method public Object? getExitTransition();
method public final androidx.fragment.app.FragmentManager? getFragmentManager();
@@ -269,6 +270,7 @@
method public void dump(String, java.io.FileDescriptor?, java.io.PrintWriter, String![]?);
method public static void enableDebugLogging(boolean);
method public boolean executePendingTransactions();
+ method public static <F extends androidx.fragment.app.Fragment> F findFragment(android.view.View);
method public androidx.fragment.app.Fragment? findFragmentById(@IdRes int);
method public androidx.fragment.app.Fragment? findFragmentByTag(String?);
method public androidx.fragment.app.FragmentManager.BackStackEntry getBackStackEntryAt(int);
diff --git a/fragment/fragment/api/restricted_1.2.0-alpha02.txt b/fragment/fragment/api/restricted_1.2.0-alpha02.txt
index a0aa372..b1f0d5f 100644
--- a/fragment/fragment/api/restricted_1.2.0-alpha02.txt
+++ b/fragment/fragment/api/restricted_1.2.0-alpha02.txt
@@ -26,7 +26,7 @@
field public static final int STYLE_NO_TITLE = 1; // 0x1
}
- public class Fragment implements android.content.ComponentCallbacks androidx.lifecycle.LifecycleOwner androidx.savedstate.SavedStateRegistryOwner android.view.View.OnCreateContextMenuListener androidx.lifecycle.ViewModelStoreOwner {
+ public class Fragment implements android.content.ComponentCallbacks androidx.lifecycle.HasDefaultViewModelProviderFactory androidx.lifecycle.LifecycleOwner androidx.savedstate.SavedStateRegistryOwner android.view.View.OnCreateContextMenuListener androidx.lifecycle.ViewModelStoreOwner {
ctor public Fragment();
ctor @ContentView public Fragment(@LayoutRes int);
method public void dump(String, java.io.FileDescriptor?, java.io.PrintWriter, String![]?);
@@ -37,6 +37,7 @@
method public final android.os.Bundle? getArguments();
method public final androidx.fragment.app.FragmentManager getChildFragmentManager();
method public android.content.Context? getContext();
+ method public androidx.lifecycle.ViewModelProvider.Factory getDefaultViewModelProviderFactory();
method public Object? getEnterTransition();
method public Object? getExitTransition();
method public final androidx.fragment.app.FragmentManager? getFragmentManager();
@@ -274,6 +275,7 @@
method public void dump(String, java.io.FileDescriptor?, java.io.PrintWriter, String![]?);
method public static void enableDebugLogging(boolean);
method public boolean executePendingTransactions();
+ method public static <F extends androidx.fragment.app.Fragment> F findFragment(android.view.View);
method public androidx.fragment.app.Fragment? findFragmentById(@IdRes int);
method public androidx.fragment.app.Fragment? findFragmentByTag(String?);
method public androidx.fragment.app.FragmentManager.BackStackEntry getBackStackEntryAt(int);
diff --git a/fragment/fragment/api/restricted_current.txt b/fragment/fragment/api/restricted_current.txt
index a0aa372..b1f0d5f 100644
--- a/fragment/fragment/api/restricted_current.txt
+++ b/fragment/fragment/api/restricted_current.txt
@@ -26,7 +26,7 @@
field public static final int STYLE_NO_TITLE = 1; // 0x1
}
- public class Fragment implements android.content.ComponentCallbacks androidx.lifecycle.LifecycleOwner androidx.savedstate.SavedStateRegistryOwner android.view.View.OnCreateContextMenuListener androidx.lifecycle.ViewModelStoreOwner {
+ public class Fragment implements android.content.ComponentCallbacks androidx.lifecycle.HasDefaultViewModelProviderFactory androidx.lifecycle.LifecycleOwner androidx.savedstate.SavedStateRegistryOwner android.view.View.OnCreateContextMenuListener androidx.lifecycle.ViewModelStoreOwner {
ctor public Fragment();
ctor @ContentView public Fragment(@LayoutRes int);
method public void dump(String, java.io.FileDescriptor?, java.io.PrintWriter, String![]?);
@@ -37,6 +37,7 @@
method public final android.os.Bundle? getArguments();
method public final androidx.fragment.app.FragmentManager getChildFragmentManager();
method public android.content.Context? getContext();
+ method public androidx.lifecycle.ViewModelProvider.Factory getDefaultViewModelProviderFactory();
method public Object? getEnterTransition();
method public Object? getExitTransition();
method public final androidx.fragment.app.FragmentManager? getFragmentManager();
@@ -274,6 +275,7 @@
method public void dump(String, java.io.FileDescriptor?, java.io.PrintWriter, String![]?);
method public static void enableDebugLogging(boolean);
method public boolean executePendingTransactions();
+ method public static <F extends androidx.fragment.app.Fragment> F findFragment(android.view.View);
method public androidx.fragment.app.Fragment? findFragmentById(@IdRes int);
method public androidx.fragment.app.Fragment? findFragmentByTag(String?);
method public androidx.fragment.app.FragmentManager.BackStackEntry getBackStackEntryAt(int);
diff --git a/fragment/fragment/build.gradle b/fragment/fragment/build.gradle
index c199145..669429a 100644
--- a/fragment/fragment/build.gradle
+++ b/fragment/fragment/build.gradle
@@ -25,6 +25,7 @@
api(project(":activity:activity"))
api(project(":lifecycle:lifecycle-livedata-core"))
api(project(":lifecycle:lifecycle-viewmodel"))
+ api(project(":lifecycle:lifecycle-viewmodel-savedstate"))
androidTestImplementation(KOTLIN_STDLIB)
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
diff --git a/fragment/fragment/lint-baseline.xml b/fragment/fragment/lint-baseline.xml
deleted file mode 100644
index 8a8c65d..0000000
--- a/fragment/fragment/lint-baseline.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
- 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.
- -->
-
-<issues format="5" by="lint 3.5.0-beta04" client="gradle" variant="debug" version="3.5.0-beta04">
-
- <issue
- id="KotlinPropertyAccess"
- message="This method should be called `getHasOptionsMenu` such that `hasOptionsMenu` can be accessed as a property from Kotlin; see https://android.github.io/kotlin-guides/interop.html#property-prefixes"
- errorLine1=" final public boolean hasOptionsMenu() {"
- errorLine2=" ~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/fragment/app/Fragment.java"
- line="1018"
- column="26"/>
- </issue>
-
-</issues>
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimationTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimationTest.kt
index 5d7c1ac..83fd843 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimationTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimationTest.kt
@@ -69,7 +69,7 @@
waitForAnimationReady()
val fm = activityRule.activity.supportFragmentManager
- val fragment = AnimatorFragment()
+ val fragment = AnimationFragment()
fm.beginTransaction()
.setCustomAnimations(ENTER, EXIT, POP_ENTER, POP_EXIT)
.add(R.id.fragmentContainer, fragment)
@@ -86,7 +86,7 @@
waitForAnimationReady()
val fm = activityRule.activity.supportFragmentManager
- val fragment = AnimatorFragment()
+ val fragment = AnimationFragment()
fm.beginTransaction().add(R.id.fragmentContainer, fragment, "1").commit()
activityRule.waitForExecution()
@@ -106,7 +106,7 @@
waitForAnimationReady()
val fm = activityRule.activity.supportFragmentManager
- val fragment = AnimatorFragment()
+ val fragment = AnimationFragment()
fm.beginTransaction().add(R.id.fragmentContainer, fragment).hide(fragment).commit()
activityRule.waitForExecution()
@@ -126,7 +126,7 @@
waitForAnimationReady()
val fm = activityRule.activity.supportFragmentManager
- val fragment = AnimatorFragment()
+ val fragment = AnimationFragment()
fm.beginTransaction().add(R.id.fragmentContainer, fragment, "1").commit()
activityRule.waitForExecution()
@@ -146,7 +146,7 @@
waitForAnimationReady()
val fm = activityRule.activity.supportFragmentManager
- val fragment = AnimatorFragment()
+ val fragment = AnimationFragment()
fm.beginTransaction().add(R.id.fragmentContainer, fragment).detach(fragment).commit()
activityRule.waitForExecution()
@@ -166,7 +166,7 @@
waitForAnimationReady()
val fm = activityRule.activity.supportFragmentManager
- val fragment = AnimatorFragment()
+ val fragment = AnimationFragment()
fm.beginTransaction().add(R.id.fragmentContainer, fragment, "1").commit()
activityRule.waitForExecution()
@@ -187,15 +187,15 @@
waitForAnimationReady()
val fm = activityRule.activity.supportFragmentManager
- val fragment1 = AnimatorFragment()
- val fragment2 = AnimatorFragment()
+ val fragment1 = AnimationFragment()
+ val fragment2 = AnimationFragment()
fm.beginTransaction()
.add(R.id.fragmentContainer, fragment1, "1")
.add(R.id.fragmentContainer, fragment2, "2")
.commit()
activityRule.waitForExecution()
- val fragment3 = AnimatorFragment()
+ val fragment3 = AnimationFragment()
fm.beginTransaction()
.setCustomAnimations(ENTER, EXIT, POP_ENTER, POP_EXIT)
.replace(R.id.fragmentContainer, fragment3)
@@ -211,8 +211,8 @@
activityRule.waitForExecution()
assertFragmentAnimation(fragment3, 2, false, POP_EXIT)
- val replacement1 = fm.findFragmentByTag("1") as AnimatorFragment?
- val replacement2 = fm.findFragmentByTag("1") as AnimatorFragment?
+ val replacement1 = fm.findFragmentByTag("1") as AnimationFragment?
+ val replacement2 = fm.findFragmentByTag("1") as AnimationFragment?
val expectedAnimations = if (replacement1 === fragment1) 2 else 1
assertFragmentAnimation(replacement1!!, expectedAnimations, true, POP_ENTER)
assertFragmentAnimation(replacement2!!, expectedAnimations, true, POP_ENTER)
@@ -224,14 +224,14 @@
waitForAnimationReady()
val fm = activityRule.activity.supportFragmentManager
- val parent = AnimatorFragment(R.layout.simple_container)
+ val parent = AnimationFragment(R.layout.simple_container)
fm.beginTransaction()
.setCustomAnimations(ENTER, EXIT, POP_ENTER, POP_EXIT)
.add(R.id.fragmentContainer, parent, "parent")
.commit()
activityRule.executePendingTransactions()
- val child = AnimatorFragment()
+ val child = AnimationFragment()
parent.childFragmentManager.beginTransaction()
.add(R.id.fragmentContainer, child, "child")
.commit()
@@ -242,7 +242,7 @@
fm.beginTransaction()
.setCustomAnimations(ENTER, EXIT, POP_ENTER, POP_EXIT)
- .replace(R.id.fragmentContainer, AnimatorFragment(), "other")
+ .replace(R.id.fragmentContainer, AnimationFragment(), "other")
.commit()
activityRule.executePendingTransactions()
@@ -256,20 +256,20 @@
waitForAnimationReady()
val fm = activityRule.activity.supportFragmentManager
- val parent = AnimatorFragment(R.layout.simple_container)
+ val parent = AnimationFragment(R.layout.simple_container)
fm.beginTransaction()
.setCustomAnimations(ENTER, EXIT, POP_ENTER, POP_EXIT)
.add(R.id.fragmentContainer, parent, "parent")
.commit()
activityRule.executePendingTransactions()
- val child = AnimatorFragment(R.layout.simple_container)
+ val child = AnimationFragment(R.layout.simple_container)
parent.childFragmentManager.beginTransaction()
.add(R.id.fragmentContainer, child, "child")
.commit()
activityRule.executePendingTransactions(parent.childFragmentManager)
- val grandChild = AnimatorFragment()
+ val grandChild = AnimationFragment()
child.childFragmentManager.beginTransaction()
.add(R.id.fragmentContainer, grandChild, "grandChild")
.commit()
@@ -283,7 +283,7 @@
fm.beginTransaction()
.setCustomAnimations(ENTER, EXIT, POP_ENTER, POP_EXIT)
- .replace(R.id.fragmentContainer, AnimatorFragment(), "other")
+ .replace(R.id.fragmentContainer, AnimationFragment(), "other")
.commit()
activityRule.executePendingTransactions()
@@ -299,7 +299,7 @@
waitForAnimationReady()
val fm = activityRule.activity.supportFragmentManager
- val fragment = AnimatorFragment()
+ val fragment = AnimationFragment()
fragment.postponeEnterTransition()
fm.beginTransaction()
.setCustomAnimations(ENTER, EXIT, POP_ENTER, POP_EXIT)
@@ -323,7 +323,7 @@
waitForAnimationReady()
val fm = activityRule.activity.supportFragmentManager
- val fragment = AnimatorFragment()
+ val fragment = AnimationFragment()
fm.beginTransaction().add(R.id.fragmentContainer, fragment, "1").commit()
activityRule.waitForExecution()
@@ -345,7 +345,7 @@
waitForAnimationReady()
val fm = activityRule.activity.supportFragmentManager
- val fragment1 = AnimatorFragment()
+ val fragment1 = AnimationFragment()
fm.beginTransaction()
.add(R.id.fragmentContainer, fragment1)
.addToBackStack(null)
@@ -353,7 +353,7 @@
.commit()
activityRule.waitForExecution()
- val fragment2 = AnimatorFragment()
+ val fragment2 = AnimationFragment()
fragment2.postponeEnterTransition()
fm.beginTransaction()
@@ -383,7 +383,7 @@
waitForAnimationReady()
val fm = activityRule.activity.supportFragmentManager
- val fragment1 = AnimatorFragment()
+ val fragment1 = AnimationFragment()
fm.beginTransaction()
.add(R.id.fragmentContainer, fragment1)
.setReorderingAllowed(true)
@@ -391,7 +391,7 @@
activityRule.waitForExecution()
assertThat(fragment1.numAnimators).isEqualTo(0)
- val fragment2 = AnimatorFragment()
+ val fragment2 = AnimationFragment()
fragment2.postponeEnterTransition()
fm.beginTransaction()
@@ -433,26 +433,26 @@
val fm1 = fc1.supportFragmentManager
- val fragment1 = StrictViewFragment(R.layout.scene1)
+ val fragment1 = AnimationListenerFragment(R.layout.scene1)
fm1.beginTransaction()
.add(R.id.fragmentContainer, fragment1, "1")
.commit()
activityRule.waitForExecution()
- val fragment2 = StrictViewFragment()
+ val fragment2 = AnimationListenerFragment()
fm1.beginTransaction()
.setCustomAnimations(0, 0, 0, R.anim.long_fade_out)
.replace(R.id.fragmentContainer, fragment2, "2")
.addToBackStack(null)
.commit()
- instrumentation.runOnMainSync { fm1.executePendingTransactions() }
- activityRule.waitForExecution()
+ activityRule.executePendingTransactions(fm1)
fm1.popBackStack()
-
- instrumentation.runOnMainSync { fm1.executePendingTransactions() }
- activityRule.waitForExecution()
+ activityRule.executePendingTransactions(fm1)
+ // ensure the animation was started
+ assertThat(fragment2.startAnimationLatch.await(1000, TimeUnit.MILLISECONDS))
+ .isTrue()
// Now fragment2 should be animating away
assertThat(fragment2.isAdded).isFalse()
// still exists because it is animating
@@ -616,7 +616,7 @@
}
}
- private fun assertEnterPopExit(fragment: AnimatorFragment) {
+ private fun assertEnterPopExit(fragment: AnimationFragment) {
assertFragmentAnimation(fragment, 1, true, ENTER)
val fm = activityRule.activity.supportFragmentManager
@@ -626,21 +626,21 @@
assertFragmentAnimation(fragment, 2, false, POP_EXIT)
}
- private fun assertExitPopEnter(fragment: AnimatorFragment) {
+ private fun assertExitPopEnter(fragment: AnimationFragment) {
assertFragmentAnimation(fragment, 1, false, EXIT)
val fm = activityRule.activity.supportFragmentManager
fm.popBackStack()
activityRule.waitForExecution()
- val replacement = fm.findFragmentByTag("1") as AnimatorFragment?
+ val replacement = fm.findFragmentByTag("1") as AnimationFragment?
val isSameFragment = replacement === fragment
val expectedAnimators = if (isSameFragment) 2 else 1
assertFragmentAnimation(replacement!!, expectedAnimators, true, POP_ENTER)
}
- private fun assertExitPostponedPopEnter(fragment: AnimatorFragment) {
+ private fun assertExitPostponedPopEnter(fragment: AnimationFragment) {
assertFragmentAnimation(fragment, 1, false, EXIT)
fragment.postponeEnterTransition()
@@ -654,7 +654,7 @@
}
private fun assertFragmentAnimation(
- fragment: AnimatorFragment,
+ fragment: AnimationFragment,
numAnimators: Int,
isEnter: Boolean,
animatorResourceId: Int
@@ -668,7 +668,7 @@
assertThat(fragment.nextAnim).isEqualTo(0)
}
- private fun assertPostponed(fragment: AnimatorFragment, expectedAnimators: Int) {
+ private fun assertPostponed(fragment: AnimationFragment, expectedAnimators: Int) {
assertThat(fragment.onCreateViewCalled).isTrue()
assertThat(fragment.requireView().visibility).isEqualTo(View.VISIBLE)
assertThat(fragment.requireView().alpha).isWithin(0f).of(0f)
@@ -697,7 +697,7 @@
}
}
- class AnimatorFragment(@LayoutRes contentLayoutId: Int = R.layout.strict_view_fragment) :
+ class AnimationFragment(@LayoutRes contentLayoutId: Int = R.layout.strict_view_fragment) :
StrictViewFragment(contentLayoutId) {
var numAnimators: Int = 0
var animation: Animation? = null
@@ -718,7 +718,9 @@
}
}
- class AnimationListenerFragment : StrictViewFragment() {
+ class AnimationListenerFragment(
+ @LayoutRes contentLayoutId: Int = R.layout.strict_view_fragment
+ ) : StrictViewFragment(contentLayoutId) {
lateinit var createdView: View
var forceRunOnHwLayer: Boolean = false
var repeat: Boolean = false
@@ -728,6 +730,7 @@
var exitStartCount = 0
var exitRepeatCount = 0
var exitEndCount = 0
+ val startAnimationLatch = CountDownLatch(1)
val enterLatch = CountDownLatch(1)
val exitLatch = CountDownLatch(1)
@@ -770,6 +773,7 @@
} else {
exitStartCount++
}
+ startAnimationLatch.countDown()
}
override fun onAnimationEnd(animation: Animation) {
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimatorTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimatorTest.kt
index c74f1f3..1478ed2 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimatorTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimatorTest.kt
@@ -22,6 +22,7 @@
import android.os.Build
import android.view.View
import androidx.annotation.AnimatorRes
+import androidx.annotation.LayoutRes
import androidx.annotation.RequiresApi
import androidx.core.view.ViewCompat
import androidx.fragment.app.test.FragmentTestActivity
@@ -452,14 +453,14 @@
val fm1 = fc1.supportFragmentManager
- val fragment1 = StrictViewFragment(R.layout.scene1)
+ val fragment1 = AnimatorFragment(R.layout.scene1)
fm1.beginTransaction()
.add(R.id.fragmentContainer, fragment1, "1")
.setReorderingAllowed(true)
.commit()
activityRule.waitForExecution()
- val fragment2 = StrictViewFragment()
+ val fragment2 = AnimatorFragment()
fm1.beginTransaction()
.setCustomAnimations(0, 0, 0, R.animator.slow_fade_out)
@@ -468,12 +469,12 @@
.setReorderingAllowed(true)
.commit()
activityRule.executePendingTransactions(fm1)
- activityRule.waitForExecution()
fm1.popBackStack()
activityRule.executePendingTransactions(fm1)
- activityRule.waitForExecution()
+ // ensure the animation was started
+ assertThat(fragment2.wasStarted).isTrue()
// Now fragment2 should be animating away
assertThat(fragment2.isAdded).isFalse()
assertThat(fm1.findFragmentByTag("2"))
@@ -548,7 +549,8 @@
assertThat(fragment.numAnimators).isEqualTo(expectedAnimators)
}
- class AnimatorFragment : StrictViewFragment() {
+ class AnimatorFragment(@LayoutRes contentLayoutId: Int = R.layout.strict_view_fragment) :
+ StrictViewFragment(contentLayoutId) {
var numAnimators: Int = 0
lateinit var baseAnimator: Animator
var baseEnter: Boolean = false
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 eabe5c4..021ea3c 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentContainerViewTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentContainerViewTest.kt
@@ -35,6 +35,7 @@
import androidx.testutils.waitForExecution
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Assert.fail
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -46,10 +47,12 @@
class FragmentContainerViewTest {
@get:Rule
var activityRule = ActivityTestRule(FragmentTestActivity::class.java)
+ lateinit var context: Context
@Before
fun setupContainer() {
activityRule.setContentView(R.layout.fragment_container_view)
+ context = activityRule.activity.applicationContext
}
@Test
@@ -99,8 +102,6 @@
@SdkSuppress(minSdkVersion = 29) // WindowInsets.Builder requires API 29
@Test
fun windowInsetsDispatchToChildren() {
- val context = activityRule.activity.applicationContext
-
val parentView = FragmentContainerView(context)
val childView = FragmentContainerView(context)
@@ -118,6 +119,8 @@
insets
}
+ childView.setTag(R.id.fragment_container_view_tag, Fragment())
+
parentView.addView(childView)
parentView.dispatchApplyWindowInsets(sentInsets)
@@ -125,18 +128,63 @@
}
@Test
- fun removeViewAt() {
- val context = activityRule.activity.applicationContext
- val view = FragmentContainerView(context)
+ fun addView() {
+ val fm = activityRule.activity.supportFragmentManager
- val childView1 = FragmentContainerView(context)
+ val view = View(context)
+ val fragment = Fragment()
+ fragment.mView = view
+
+ fm.setViewTag(fragment)
+
+ val fragmentContainerView = FragmentContainerView(context)
+
+ assertWithMessage("FragmentContainerView should have no child views")
+ .that(fragmentContainerView.childCount).isEqualTo(0)
+
+ fragmentContainerView.addView(view)
+
+ assertWithMessage("FragmentContainerView should have one child view")
+ .that(fragmentContainerView.childCount).isEqualTo(1)
+ }
+
+ @Test
+ fun addViewNotAssociatedWithFragment() {
+ val view = View(context)
+
+ try {
+ FragmentContainerView(context).addView(view, 0, null)
+ fail("View without a Fragment added to FragmentContainerView should throw an exception")
+ } catch (e: IllegalStateException) {
+ assertThat(e)
+ .hasMessageThat().contains(
+ "Views added to a FragmentContainerView must be associated with a Fragment. " +
+ "View " + view + " is not associated with a Fragment."
+ )
+ }
+ }
+
+ @Test
+ fun addViewInLayoutNotAssociatedWithFragment() {
+ val view = View(context)
+
+ try {
+ FragmentContainerView(context).addViewInLayout(view, 0, null, false)
+ fail("View without a Fragment added to FragmentContainerView should throw an exception")
+ } catch (e: IllegalStateException) {
+ assertThat(e)
+ .hasMessageThat().contains(
+ "Views added to a FragmentContainerView must be associated with a Fragment. " +
+ "View " + view + " is not associated with a Fragment."
+ )
+ }
+ }
+
+ @Test
+ fun removeViewAt() {
val childView2 = FragmentContainerView(context)
- view.addView(childView1)
- view.addView(childView2)
-
- assertThat(view.childCount).isEqualTo(2)
- assertThat(view.getChildAt(1)).isEqualTo(childView2)
+ val view = setupRemoveTestsView(FragmentContainerView(context), childView2)
view.removeViewAt(0)
@@ -146,17 +194,10 @@
@Test
fun removeViewInLayout() {
- val context = activityRule.activity.applicationContext
- val view = FragmentContainerView(context)
-
val childView1 = FragmentContainerView(context)
val childView2 = FragmentContainerView(context)
- view.addView(childView1)
- view.addView(childView2)
-
- assertThat(view.childCount).isEqualTo(2)
- assertThat(view.getChildAt(1)).isEqualTo(childView2)
+ val view = setupRemoveTestsView(childView1, childView2)
view.removeViewInLayout(childView1)
@@ -166,17 +207,10 @@
@Test
fun removeView() {
- val context = activityRule.activity.applicationContext
- val view = FragmentContainerView(context)
-
val childView1 = FragmentContainerView(context)
val childView2 = FragmentContainerView(context)
- view.addView(childView1)
- view.addView(childView2)
-
- assertThat(view.childCount).isEqualTo(2)
- assertThat(view.getChildAt(1)).isEqualTo(childView2)
+ val view = setupRemoveTestsView(childView1, childView2)
view.removeView(childView1)
@@ -185,17 +219,10 @@
@Test
fun removeViews() {
- val context = activityRule.activity.applicationContext
- val view = FragmentContainerView(context)
-
- val childView1 = FragmentContainerView(context)
- val childView2 = FragmentContainerView(context)
-
- view.addView(childView1)
- view.addView(childView2)
-
- assertThat(view.childCount).isEqualTo(2)
- assertThat(view.getChildAt(1)).isEqualTo(childView2)
+ val view = setupRemoveTestsView(
+ FragmentContainerView(context),
+ FragmentContainerView(context)
+ )
view.removeViews(1, 1)
@@ -204,17 +231,10 @@
@Test
fun removeViewsInLayout() {
- val context = activityRule.activity.applicationContext
- val view = FragmentContainerView(context)
-
- val childView1 = FragmentContainerView(context)
- val childView2 = FragmentContainerView(context)
-
- view.addView(childView1)
- view.addView(childView2)
-
- assertThat(view.childCount).isEqualTo(2)
- assertThat(view.getChildAt(1)).isEqualTo(childView2)
+ val view = setupRemoveTestsView(
+ FragmentContainerView(context),
+ FragmentContainerView(context)
+ )
view.removeViewsInLayout(1, 1)
@@ -223,17 +243,10 @@
@Test
fun removeAllViewsInLayout() {
- val context = activityRule.activity.applicationContext
- val view = FragmentContainerView(context)
-
- val childView1 = FragmentContainerView(context)
- val childView2 = FragmentContainerView(context)
-
- view.addView(childView1)
- view.addView(childView2)
-
- assertThat(view.childCount).isEqualTo(2)
- assertThat(view.getChildAt(1)).isEqualTo(childView2)
+ val view = setupRemoveTestsView(
+ FragmentContainerView(context),
+ FragmentContainerView(context)
+ )
view.removeAllViewsInLayout()
@@ -243,22 +256,37 @@
// removeDetachedView should not actually remove the view
@Test
fun removeDetachedView() {
- val context = activityRule.activity.applicationContext
- val view = FragmentContainerView(context)
-
val childView1 = FragmentContainerView(context)
val childView2 = FragmentContainerView(context)
+ val view = setupRemoveTestsView(childView1, childView2)
+
+ view.removeDetachedView(childView1, false)
+
+ assertThat(view.childCount).isEqualTo(2)
+ assertThat(view.getChildAt(1)).isEqualTo(childView2)
+ }
+
+ private fun setupRemoveTestsView(
+ childView1: FragmentContainerView,
+ childView2: FragmentContainerView
+ ): FragmentContainerView {
+ val view = FragmentContainerView(context)
+ val fragment1 = Fragment()
+ val fragment2 = Fragment()
+
+ fragment1.mView = childView1
+ fragment2.mView = childView2
+
+ childView1.setTag(R.id.fragment_container_view_tag, fragment1)
+ childView2.setTag(R.id.fragment_container_view_tag, fragment2)
+
view.addView(childView1)
view.addView(childView2)
assertThat(view.childCount).isEqualTo(2)
assertThat(view.getChildAt(1)).isEqualTo(childView2)
-
- view.removeDetachedView(childView1, false)
-
- assertThat(view.childCount).isEqualTo(2)
- assertThat(view.getChildAt(1)).isEqualTo(childView2)
+ return view
}
// Disappearing child views should be drawn first before other child views.
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransitionTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransitionTest.kt
index 3df8b7e..8f852ff 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransitionTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransitionTest.kt
@@ -175,6 +175,7 @@
assertThat(onBackStackChangedTimes).isEqualTo(3)
fragment1.waitForTransition()
+ fragment2.waitForTransition()
val popBlue = findBlue()
val popGreen = findGreen()
verifyAndClearTransition(fragment1.reenterTransition, null, popBlue, popGreen)
@@ -762,6 +763,8 @@
fragment2.waitForTransition()
// It does not transition properly for ordered transactions, though.
if (reorderingAllowed) {
+ // reordering allowed fragment3 to get a transition so we should wait for it to finish
+ fragment3.waitForTransition()
verifyAndClearTransition(fragment2.returnTransition, null, midGreen, midBlue)
val endGreen = findGreen()
val endBlue = findBlue()
@@ -770,6 +773,8 @@
verifyNoOtherTransitions(fragment2)
verifyNoOtherTransitions(fragment3)
} else {
+ // The pop transition will be executed so we should wait until fragment 1 finishes
+ fragment1.waitForTransition()
// fragment3 doesn't get a transition since it conflicts with the pop transition
verifyNoOtherTransitions(fragment3)
// Everything else is just doing its best. Ordered transactions can't handle
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentViewTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentViewTest.kt
index 06d3665..e65aa30 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentViewTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentViewTest.kt
@@ -249,7 +249,7 @@
// Removing a detached fragment should do nothing to the View and popping should bring
// the Fragment back detached
@Test
- fun removeDetatchedView() {
+ fun removeDetachedView() {
activityRule.setContentView(R.layout.simple_container)
val container =
activityRule.activity.findViewById<View>(R.id.fragmentContainer) as ViewGroup
@@ -310,6 +310,46 @@
.isEqualTo(Lifecycle.State.INITIALIZED)
}
+ @Test
+ fun findFragmentNoTagSet() {
+ val view = View(activityRule.activity)
+ try {
+ FragmentManager.findFragment<Fragment>(view)
+ fail("findFragment should throw IllegalStateException if a Fragment was not set")
+ } catch (e: IllegalStateException) {
+ assertThat(e)
+ .hasMessageThat().contains("View $view does not have a Fragment set")
+ }
+ }
+
+ @Test
+ fun findFragmentAfterAdd() {
+ activityRule.setContentView(R.layout.simple_container)
+ val fm = activityRule.activity.supportFragmentManager
+
+ val fragment = StrictViewFragment()
+ fm.beginTransaction().add(R.id.fragmentContainer, fragment).commit()
+ activityRule.executePendingTransactions()
+
+ assertThat(FragmentManager.findFragment<StrictViewFragment>(fragment.requireView()))
+ .isSameInstanceAs(fragment)
+ }
+
+ @Test
+ fun findFragmentFindByIdChildView() {
+ activityRule.setContentView(R.layout.simple_container)
+ val fm = activityRule.activity.supportFragmentManager
+
+ val fragment = StrictViewFragment(R.layout.fragment_a)
+ fm.beginTransaction().add(R.id.fragmentContainer, fragment).commit()
+ activityRule.executePendingTransactions()
+
+ val view = fragment.requireView().findViewById<View>(R.id.textA)
+ assertThat(view).isNotNull()
+ assertThat(FragmentManager.findFragment<StrictViewFragment>(view))
+ .isSameInstanceAs(fragment)
+ }
+
// Hide a fragment and its View should be GONE. Then pop it and the View should be VISIBLE
@Test
fun hideFragment() {
@@ -492,7 +532,7 @@
// Detaching a detached fragment should not throw
@Test
- fun detachDetatched() {
+ fun detachDetached() {
activityRule.setContentView(R.layout.simple_container)
val fm = activityRule.activity.supportFragmentManager
val fragment = StrictViewFragment()
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/PostponedTransitionTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/PostponedTransitionTest.kt
index a9c1bee..9098d94 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/PostponedTransitionTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/PostponedTransitionTest.kt
@@ -1001,10 +1001,15 @@
assertThat(start.sharedElementReturn.targets.size).isEqualTo(0)
assertThat(end.sharedElementReturn.targets.size).isEqualTo(0)
- val blue = end.requireView().findViewById<View>(R.id.blueSquare)
- assertThat(end.sharedElementEnter.targets.contains(blue)).isTrue()
- assertThat(end.sharedElementEnter.targets[0].transitionName).isEqualTo("blueSquare")
- assertThat(end.sharedElementEnter.targets[1].transitionName).isEqualTo("blueSquare")
+ // This checks need to be done on the mainThread to ensure that the shared element draw is
+ // complete. If these checks are not on the mainThread, there is a chance that this gets
+ // checked during OnShotPreDrawListener.onPreDraw, causing the name assert to fail.
+ activityRule.runOnUiThread {
+ val blue = end.requireView().findViewById<View>(R.id.blueSquare)
+ assertThat(end.sharedElementEnter.targets.contains(blue)).isTrue()
+ assertThat(end.sharedElementEnter.targets[0].transitionName).isEqualTo("blueSquare")
+ assertThat(end.sharedElementEnter.targets[1].transitionName).isEqualTo("blueSquare")
+ }
assertNoTargets(start)
assertNoTargets(end)
@@ -1034,10 +1039,15 @@
assertThat(start.sharedElementReturn.targets.size).isEqualTo(2)
assertThat(end.sharedElementReturn.targets.size).isEqualTo(0)
- val blue = end.requireView().findViewById<View>(R.id.blueSquare)
- assertThat(start.sharedElementReturn.targets.contains(blue)).isTrue()
- assertThat(start.sharedElementReturn.targets[0].transitionName).isEqualTo("blueSquare")
- assertThat(start.sharedElementReturn.targets[1].transitionName).isEqualTo("blueSquare")
+ // This checks need to be done on the mainThread to ensure that the shared element draw is
+ // complete. If these checks are not on the mainThread, there is a chance that this gets
+ // checked during OnShotPreDrawListener.onPreDraw, causing the name assert to fail.
+ activityRule.runOnUiThread {
+ val blue = end.requireView().findViewById<View>(R.id.blueSquare)
+ assertThat(start.sharedElementReturn.targets.contains(blue)).isTrue()
+ assertThat(start.sharedElementReturn.targets[0].transitionName).isEqualTo("blueSquare")
+ assertThat(start.sharedElementReturn.targets[1].transitionName).isEqualTo("blueSquare")
+ }
assertNoTargets(end)
assertNoTargets(start)
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/PrimaryNavFragmentTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/PrimaryNavFragmentTest.kt
index 461e2d8..fc1f76d1 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/PrimaryNavFragmentTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/PrimaryNavFragmentTest.kt
@@ -17,10 +17,15 @@
package androidx.fragment.app
import android.app.Activity
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.ViewGroup
import androidx.fragment.app.test.EmptyFragmentTestActivity
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import androidx.test.rule.ActivityTestRule
+import androidx.testutils.runOnUiThreadRethrow
+import androidx.testutils.waitForExecution
import com.google.common.truth.Truth.assertWithMessage
import org.junit.Rule
import org.junit.Test
@@ -254,11 +259,86 @@
.isSameInstanceAs(strictFragment1)
}
+ @Test
+ fun replacePostponedFragment() {
+ val fm = activityRule.activity.supportFragmentManager
+ val strictFragment = spy(StrictViewFragment())
+ val postponedFragment = spy(PostponedFragment())
+ val replacementFragment = spy(StrictFragment())
+ val inOrder = inOrder(strictFragment, postponedFragment, replacementFragment)
+
+ fm.beginTransaction()
+ .add(android.R.id.content, strictFragment)
+ .setPrimaryNavigationFragment(strictFragment)
+ .setReorderingAllowed(true)
+ .commit()
+ executePendingTransactions(fm)
+
+ inOrder.verify(strictFragment).onPrimaryNavigationFragmentChanged(true)
+ assertWithMessage("new fragment is not primary nav fragment")
+ .that(fm.primaryNavigationFragment)
+ .isSameInstanceAs(strictFragment)
+
+ fm.beginTransaction()
+ .replace(android.R.id.content, postponedFragment)
+ .setPrimaryNavigationFragment(postponedFragment)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+ activityRule.waitForExecution()
+
+ inOrder.verify(strictFragment).onPrimaryNavigationFragmentChanged(false)
+ inOrder.verify(postponedFragment).onPrimaryNavigationFragmentChanged(true)
+ inOrder.verify(postponedFragment).onPrimaryNavigationFragmentChanged(false)
+ inOrder.verify(strictFragment).onPrimaryNavigationFragmentChanged(true)
+ assertWithMessage("primary nav fragment not set correctly after replace")
+ .that(fm.primaryNavigationFragment)
+ .isSameInstanceAs(strictFragment)
+
+ // Now pop the back stack and also add a replacement Fragment
+ fm.popBackStack()
+ fm.beginTransaction()
+ .replace(android.R.id.content, replacementFragment)
+ .setPrimaryNavigationFragment(replacementFragment)
+ .setReorderingAllowed(true)
+ .addToBackStack(null)
+ .commit()
+ activityRule.waitForExecution()
+
+ inOrder.verify(strictFragment).onPrimaryNavigationFragmentChanged(false)
+ inOrder.verify(replacementFragment).onPrimaryNavigationFragmentChanged(true)
+ assertWithMessage("primary nav fragment not set correctly after replace")
+ .that(fm.primaryNavigationFragment)
+ .isSameInstanceAs(replacementFragment)
+
+ // Now go back to the first Fragment
+ activityRule.onBackPressed()
+
+ inOrder.verify(replacementFragment).onPrimaryNavigationFragmentChanged(false)
+ inOrder.verify(strictFragment).onPrimaryNavigationFragmentChanged(true)
+ assertWithMessage("primary nav fragment is restored after replace")
+ .that(fm.primaryNavigationFragment)
+ .isSameInstanceAs(strictFragment)
+ assertWithMessage("Only the first Fragment should exist on the FragmentManager")
+ .that(fm.fragments)
+ .containsExactly(strictFragment)
+ }
+
private fun executePendingTransactions(fm: FragmentManager) {
activityRule.runOnUiThread { fm.executePendingTransactions() }
}
- private fun ActivityTestRule<out Activity>.onBackPressed() = runOnUiThread {
+ private fun ActivityTestRule<out Activity>.onBackPressed() = runOnUiThreadRethrow {
activity.onBackPressed()
}
+
+ open class PostponedFragment : StrictViewFragment() {
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ) = super.onCreateView(inflater, container, savedInstanceState).also {
+ postponeEnterTransition()
+ }
+ }
}
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/StrictViewFragment.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/StrictViewFragment.kt
index e58aea0..837a2caae 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/StrictViewFragment.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/StrictViewFragment.kt
@@ -39,6 +39,8 @@
): View? {
checkGetActivity()
checkState("onCreateView", State.CREATED)
+ assertWithMessage("Fragment should not have a view when calling onCreateView")
+ .that(mView).isNull()
return super.onCreateView(inflater, container, savedInstanceState).also {
onCreateViewCalled = true
}
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/TransitionFragment.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/TransitionFragment.kt
index d421abb..72dc1bb 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/TransitionFragment.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/TransitionFragment.kt
@@ -15,15 +15,12 @@
*/
package androidx.fragment.app
-import android.os.SystemClock
import android.transition.Transition
import androidx.annotation.LayoutRes
import androidx.fragment.test.R
-import org.mockito.ArgumentMatchers
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.reset
-import org.mockito.Mockito.verify
+import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
/**
* A fragment that has transitions that can be tracked.
@@ -37,8 +34,25 @@
val returnTransition = TrackingVisibility()
val sharedElementEnter = TrackingTransition()
val sharedElementReturn = TrackingTransition()
+ var startTransitionCountDownLatch = CountDownLatch(1)
+ var endTransitionCountDownLatch = CountDownLatch(1)
- private val listener = mock(Transition.TransitionListener::class.java)
+ private val listener = object : Transition.TransitionListener {
+ override fun onTransitionEnd(transition: Transition) {
+ endTransitionCountDownLatch.countDown()
+ startTransitionCountDownLatch = CountDownLatch(1)
+ }
+
+ override fun onTransitionResume(transition: Transition) {}
+
+ override fun onTransitionPause(transition: Transition) {}
+
+ override fun onTransitionCancel(transition: Transition) {}
+
+ override fun onTransitionStart(transition: Transition) {
+ startTransitionCountDownLatch.countDown()
+ }
+ }
init {
@Suppress("LeakingThis")
@@ -60,18 +74,11 @@
}
internal fun waitForTransition() {
- verify(
- listener,
- within(1000)
- ).onTransitionEnd(ArgumentMatchers.any())
- reset(listener)
+ endTransitionCountDownLatch.await()
+ endTransitionCountDownLatch = CountDownLatch(1)
}
internal fun waitForNoTransition() {
- SystemClock.sleep(250)
- verify(
- listener,
- never()
- ).onTransitionStart(ArgumentMatchers.any())
+ assertThat(startTransitionCountDownLatch.await(250, TimeUnit.MILLISECONDS)).isFalse()
}
}
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/ViewModelTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/ViewModelTest.kt
index 28e73e2..3edf294 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/ViewModelTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/ViewModelTest.kt
@@ -108,20 +108,32 @@
val fragmentModel = withActivity {
getFragment(ViewModelActivity.FRAGMENT_TAG_1).fragmentModel
}
+ val fragmentAndroidModel = withActivity {
+ getFragment(ViewModelActivity.FRAGMENT_TAG_1).androidModel
+ }
+ val fragmentSavedStateAndroidModel = withActivity {
+ getFragment(ViewModelActivity.FRAGMENT_TAG_1).savedStateModel
+ }
val backStackFragmentModel = withActivity {
getFragment(ViewModelActivity.FRAGMENT_TAG_BACK_STACK).fragmentModel
}
assertThat(fragmentModel.cleared).isFalse()
+ assertThat(fragmentAndroidModel.cleared).isFalse()
+ assertThat(fragmentSavedStateAndroidModel.cleared).isFalse()
assertThat(backStackFragmentModel.cleared).isFalse()
recreate()
// recreate shouldn't clear the ViewModels
assertThat(fragmentModel.cleared).isFalse()
+ assertThat(fragmentAndroidModel.cleared).isFalse()
+ assertThat(fragmentSavedStateAndroidModel.cleared).isFalse()
assertThat(backStackFragmentModel.cleared).isFalse()
moveToState(Lifecycle.State.DESTROYED)
// But destroying the Activity should
assertThat(fragmentModel.cleared).isTrue()
+ assertThat(fragmentAndroidModel.cleared).isTrue()
+ assertThat(fragmentSavedStateAndroidModel.cleared).isTrue()
assertThat(backStackFragmentModel.cleared).isTrue()
}
}
@@ -134,10 +146,7 @@
supportFragmentManager.beginTransaction().add(it, "temp").commitNow()
}
}
- val viewModelProvider = ViewModelProvider(
- fragment,
- ViewModelProvider.NewInstanceFactory()
- )
+ val viewModelProvider = ViewModelProvider(fragment)
val vm = viewModelProvider.get(TestViewModel::class.java)
assertThat(vm.cleared).isFalse()
onActivity { activity ->
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/ViewModelTestInTransaction.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/ViewModelTestInTransaction.kt
index 03804cc..f281002 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/ViewModelTestInTransaction.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/ViewModelTestInTransaction.kt
@@ -20,7 +20,6 @@
import androidx.fragment.app.test.EmptyFragmentTestActivity
import androidx.fragment.app.test.TestViewModel
import androidx.lifecycle.ViewModelProvider
-import androidx.lifecycle.ViewModelStoreOwner
import androidx.test.annotation.UiThreadTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -43,7 +42,7 @@
val activity = activityRule.activity
val fragment = TestFragment()
activity.supportFragmentManager.beginTransaction().add(fragment, "tag").commitNow()
- val viewModelProvider = ViewModelProvider(activity, ViewModelProvider.NewInstanceFactory())
+ val viewModelProvider = ViewModelProvider(activity)
val viewModel = viewModelProvider.get(TestViewModel::class.java)
assertThat(viewModel).isSameInstanceAs(fragment.viewModel)
}
@@ -65,7 +64,7 @@
super.onCreate(savedInstanceState)
val fragment = TestFragment()
childFragmentManager.beginTransaction().add(fragment, "tag").commitNow()
- val viewModelProvider = ViewModelProvider(this, ViewModelProvider.NewInstanceFactory())
+ val viewModelProvider = ViewModelProvider(this)
val viewModel = viewModelProvider.get(TestViewModel::class.java)
assertThat(viewModel).isSameInstanceAs(fragment.viewModel)
executed = true
@@ -79,10 +78,7 @@
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val parentFragment = parentFragment
- val provider = ViewModelProvider(
- (parentFragment ?: requireActivity()) as ViewModelStoreOwner,
- ViewModelProvider.NewInstanceFactory()
- )
+ val provider = ViewModelProvider(parentFragment ?: requireActivity())
viewModel = provider.get(TestViewModel::class.java)
assertThat(viewModel).isNotNull()
}
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/test/ViewModelActivity.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/test/ViewModelActivity.kt
index a058649..b63f54d 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/test/ViewModelActivity.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/test/ViewModelActivity.kt
@@ -16,10 +16,14 @@
package androidx.fragment.app.test
+import android.app.Application
import android.os.Bundle
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.test.R
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.SavedStateHandle
+import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelStore
@@ -48,10 +52,7 @@
.commit()
}
- val viewModelProvider = ViewModelProvider(
- this,
- ViewModelProvider.NewInstanceFactory()
- )
+ val viewModelProvider = ViewModelProvider(this)
activityModel = viewModelProvider.get(KEY_ACTIVITY_MODEL, TestViewModel::class.java)
defaultActivityModel = viewModelProvider.get(TestViewModel::class.java)
}
@@ -60,26 +61,41 @@
lateinit var fragmentModel: TestViewModel
lateinit var activityModel: TestViewModel
lateinit var defaultActivityModel: TestViewModel
+ lateinit var androidModel: TestAndroidViewModel
+ lateinit var savedStateModel: TestSavedStateViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- val viewModelProvider = ViewModelProvider(
- this,
- ViewModelProvider.NewInstanceFactory()
- )
+ val viewModelProvider = ViewModelProvider(this)
fragmentModel = viewModelProvider.get(
KEY_FRAGMENT_MODEL,
TestViewModel::class.java
)
- val activityViewModelProvider = ViewModelProvider(
- requireActivity(),
- ViewModelProvider.NewInstanceFactory()
- )
+ val activityViewModelProvider = ViewModelProvider(requireActivity())
activityModel = activityViewModelProvider.get(
ViewModelActivity.KEY_ACTIVITY_MODEL,
TestViewModel::class.java
)
defaultActivityModel = activityViewModelProvider.get(TestViewModel::class.java)
+ androidModel = viewModelProvider.get(TestAndroidViewModel::class.java)
+ savedStateModel = viewModelProvider.get(TestSavedStateViewModel::class.java)
+ }
+ }
+
+ class TestAndroidViewModel(application: Application) : AndroidViewModel(application) {
+ var cleared = false
+
+ override fun onCleared() {
+ cleared = true
+ }
+ }
+
+ @Suppress("unused")
+ class TestSavedStateViewModel(val savedStateHandle: SavedStateHandle) : ViewModel() {
+ var cleared = false
+
+ override fun onCleared() {
+ cleared = true
}
}
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 050db60..ccbc082 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/BackStackRecord.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/BackStackRecord.java
@@ -314,7 +314,7 @@
}
mCommitted = true;
if (mAddToBackStack) {
- mIndex = mManager.allocBackStackIndex(this);
+ mIndex = mManager.allocBackStackIndex();
} else {
mIndex = -1;
}
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 f6efea6..84058db 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/Fragment.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/Fragment.java
@@ -59,12 +59,15 @@
import androidx.core.app.SharedElementCallback;
import androidx.core.util.DebugUtils;
import androidx.core.view.LayoutInflaterCompat;
+import androidx.lifecycle.HasDefaultViewModelProviderFactory;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleEventObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LifecycleRegistry;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
+import androidx.lifecycle.SavedStateViewModelFactory;
+import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelStore;
import androidx.lifecycle.ViewModelStoreOwner;
import androidx.loader.app.LoaderManager;
@@ -94,7 +97,7 @@
*
*/
public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener, LifecycleOwner,
- ViewModelStoreOwner, SavedStateRegistryOwner {
+ ViewModelStoreOwner, HasDefaultViewModelProviderFactory, SavedStateRegistryOwner {
static final Object USE_DEFAULT_TRANSITION = new Object();
@@ -267,6 +270,8 @@
@Nullable FragmentViewLifecycleOwner mViewLifecycleOwner;
MutableLiveData<LifecycleOwner> mViewLifecycleOwnerLiveData = new MutableLiveData<>();
+ private ViewModelProvider.Factory mDefaultFactory;
+
SavedStateRegistryController mSavedStateRegistryController;
@LayoutRes
@@ -365,6 +370,28 @@
return mFragmentManager.getViewModelStore(this);
}
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The {@link #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() {
+ if (mFragmentManager == null) {
+ throw new IllegalStateException("Can't access ViewModels from detached fragment");
+ }
+ if (mDefaultFactory == null) {
+ mDefaultFactory = new SavedStateViewModelFactory(
+ requireActivity().getApplication(),
+ this,
+ getArguments());
+ }
+ return mDefaultFactory;
+ }
+
@NonNull
@Override
public final SavedStateRegistry getSavedStateRegistry() {
@@ -1036,6 +1063,7 @@
/** @hide */
@RestrictTo(LIBRARY_GROUP_PREFIX)
+ @SuppressLint("KotlinPropertyAccess")
final public boolean hasOptionsMenu() {
return mHasMenu;
}
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 976c7e5..898a17c 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentContainerView.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentContainerView.java
@@ -19,8 +19,11 @@
import android.animation.LayoutTransition;
import android.content.Context;
import android.graphics.Canvas;
+import android.os.Bundle;
import android.util.AttributeSet;
+import android.view.LayoutInflater;
import android.view.View;
+import android.view.ViewGroup;
import android.view.WindowInsets;
import android.widget.FrameLayout;
@@ -34,6 +37,26 @@
* {@link FrameLayout}, so it can reliably handle Fragment Transactions, and it also has additional
* features to coordinate with fragment behavior.
*
+ * <p>FragmentContainerView should be used as the container for Fragments, commonly set in the
+ * xml layout of an activity, e.g.: <p>
+ *
+ * <pre class="prettyprint">
+ * <androidx.fragment.app.FragmentContainerView
+ * xmlns:android="http://schemas.android.com/apk/res/android"
+ * xmlns:app="http://schemas.android.com/apk/res-auto"
+ * android:id="@+id/fragment_container_view"
+ * android:layout_width="match_parent"
+ * android:layout_height="match_parent">
+ * </androidx.fragment.app.FragmentContainerView>
+ * </pre>
+ *
+ * <p>FragmentContainerView should not be used as a replacement for other ViewGroups (FrameLayout,
+ * LinearLayout, etc) outside of Fragment use cases.
+ *
+ * <p>FragmentContainerView will only allow views to returned by a Fragment's
+ * {@link Fragment#onCreateView(LayoutInflater, ViewGroup, Bundle)}. Attempting to add any other
+ * view will result in an {@link IllegalStateException}.
+ *
* <p>Layout animations and transitions are disabled for FragmentContainerView. Animations should be
* done through {@link FragmentTransaction#setCustomAnimations(int, int, int, int)}. If
* animateLayoutChanges is set to <code>true</code> or
@@ -41,9 +64,7 @@
* {@link UnsupportedOperationException} will be thrown.
*
* <p>Fragments using exit animations are drawn before all others for FragmentContainerView. This
- * ensures that exiting Fragments do not appear on top of the view. When using this layout, a
- * Fragment with an enter animation is popped using {@link FragmentManager#popBackStack()}, the
- * reverse animation will not appear.
+ * ensures that exiting Fragments do not appear on top of the view.
*/
public class FragmentContainerView extends FrameLayout {
@@ -145,6 +166,41 @@
super.endViewTransition(view);
}
+ /**
+ * <p>FragmentContainerView will only allow views to returned by a Fragment's
+ * {@link Fragment#onCreateView(LayoutInflater, ViewGroup, Bundle)}. Attempting to add any
+ * other view will result in an {@link IllegalStateException}.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public void addView(@NonNull View child, int index, @Nullable ViewGroup.LayoutParams params) {
+ if (FragmentManager.getViewFragment(child) == null) {
+ throw new IllegalStateException("Views added to a FragmentContainerView must be"
+ + " associated with a Fragment. View " + child + " is not associated with a"
+ + " Fragment.");
+ }
+ super.addView(child, index, params);
+ }
+
+ /**
+ * <p>FragmentContainerView will only allow views to returned by a Fragment's
+ * {@link Fragment#onCreateView(LayoutInflater, ViewGroup, Bundle)}. Attempting to add any
+ * other view will result in an {@link IllegalStateException}.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ protected boolean addViewInLayout(@NonNull View child, int index,
+ @Nullable ViewGroup.LayoutParams params, boolean preventRequestLayout) {
+ if (FragmentManager.getViewFragment(child) == null) {
+ throw new IllegalStateException("Views added to a FragmentContainerView must be"
+ + " associated with a Fragment. View " + child + " is not associated with a"
+ + " Fragment.");
+ }
+ return super.addViewInLayout(child, index, params, preventRequestLayout);
+ }
+
@Override
public void removeViewAt(int index) {
View view = getChildAt(index);
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 c594f16..ff2d492 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
@@ -36,6 +36,7 @@
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewParent;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.AnimationUtils;
@@ -65,12 +66,12 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.atomic.AtomicInteger;
/**
* Static library support version of the framework's {@link android.app.FragmentManager}.
@@ -136,7 +137,8 @@
/**
* Return the full bread crumb title resource identifier for the entry,
* or 0 if it does not have one.
- * @deprecated Store breadcrumb titles separately from back stack entries.
+ * @deprecated Store breadcrumb titles separately from back stack entries. For example,
+ * by using an <code>android:label</code> on a fragment in a navigation graph.
*/
@Deprecated
@StringRes
@@ -145,7 +147,8 @@
/**
* Return the short bread crumb title resource identifier for the entry,
* or 0 if it does not have one.
- * @deprecated Store breadcrumb short titles separately from back stack entries.
+ * @deprecated Store breadcrumb short titles separately from back stack entries. For
+ * example, by using an <code>android:label</code> on a fragment in a navigation graph.
*/
@Deprecated
@StringRes
@@ -154,7 +157,8 @@
/**
* Return the full bread crumb title for the entry, or null if it
* does not have one.
- * @deprecated Store breadcrumb titles separately from back stack entries.
+ * @deprecated Store breadcrumb titles separately from back stack entries. For example,
+ * * by using an <code>android:label</code> on a fragment in a navigation graph.
*/
@Deprecated
@Nullable
@@ -163,7 +167,8 @@
/**
* Return the short bread crumb title for the entry, or null if it
* does not have one.
- * @deprecated Store breadcrumb short titles separately from back stack entries.
+ * @deprecated Store breadcrumb short titles separately from back stack entries. For
+ * example, by using an <code>android:label</code> on a fragment in a navigation graph.
*/
@Deprecated
@Nullable
@@ -367,9 +372,7 @@
}
};
- // Must be accessed while locked.
- private final ArrayList<BackStackRecord> mBackStackIndices = new ArrayList<>();
- private final ArrayList<Integer> mAvailBackStackIndices = new ArrayList<>();
+ private final AtomicInteger mBackStackIndex = new AtomicInteger();
private ArrayList<OnBackStackChangedListener> mBackStackChangeListeners;
private final CopyOnWriteArrayList<FragmentLifecycleCallbacksHolder>
@@ -755,6 +758,69 @@
}
/**
+ * Find a {@link Fragment} associated with the given {@link View}.
+ *
+ * This method will locate the {@link Fragment} associated with this view. This is automatically
+ * populated for the View returned by {@link Fragment#onCreateView} and its children.
+ *
+ * @param view the view to search from
+ * @return the locally scoped {@link Fragment} to the given view
+ * @throws IllegalStateException if the given view does not correspond with a
+ * {@link Fragment}.
+ */
+ @NonNull
+ @SuppressWarnings("unchecked") // We should throw a ClassCast exception if the type is wrong
+ public static <F extends Fragment> F findFragment(@NonNull View view) {
+ Fragment fragment = findViewFragment(view);
+ if (fragment == null) {
+ throw new IllegalStateException("View " + view + " does not have a Fragment set");
+ }
+ return (F) fragment;
+ }
+
+ /**
+ * Recurse up the view hierarchy, looking for the Fragment
+ * @param view the view to search from
+ * @return the locally scoped {@link Fragment} to the given view, if found
+ */
+ @Nullable
+ private static Fragment findViewFragment(@NonNull View view) {
+ while (view != null) {
+ Fragment fragment = getViewFragment(view);
+ if (fragment != null) {
+ return fragment;
+ }
+ ViewParent parent = view.getParent();
+ view = parent instanceof View ? (View) parent : null;
+ }
+ return null;
+ }
+
+ /**
+ * Check if this view has an associated Fragment
+ * @param view the view to search from
+ * @return the locally scoped {@link Fragment} to the given view, if found
+ */
+ @Nullable
+ static Fragment getViewFragment(@NonNull View view) {
+ Object tag = view.getTag(R.id.fragment_container_view_tag);
+ if (tag instanceof Fragment) {
+ return (Fragment) tag;
+ }
+ return null;
+ }
+
+ /**
+ * Used to store the Fragment inside of its view's tag. This is done after the fragment's view
+ * is created, but before the view is added to the container.
+ *
+ * @param fragment The fragment to be set as a tag on its view
+ */
+ void setViewTag(@NonNull Fragment fragment) {
+ fragment.mView.setTag(R.id.fragment_container_view_tag, fragment);
+ }
+
+ /**
* Get a list of all fragments that are currently added to the FragmentManager.
* This may include those that are hidden as well as those that are shown.
* This will not include any fragments only in the back stack, or fragments that
@@ -961,26 +1027,8 @@
}
}
- synchronized (mBackStackIndices) {
- count = mBackStackIndices.size();
- if (count > 0) {
- writer.print(prefix); writer.println("Back Stack Indices:");
- for (int i = 0; i < count; i++) {
- BackStackRecord bs = mBackStackIndices.get(i);
- writer.print(prefix);
- writer.print(" #");
- writer.print(i);
- writer.print(": ");
- writer.println(bs);
- }
- }
-
- if (!mAvailBackStackIndices.isEmpty()) {
- writer.print(prefix);
- writer.print("mAvailBackStackIndices: ");
- writer.println(Arrays.toString(mAvailBackStackIndices.toArray()));
- }
- }
+ writer.print(prefix);
+ writer.println("Back Stack Index: " + mBackStackIndex.get());
synchronized (mPendingActions) {
count = mPendingActions.size();
@@ -1279,6 +1327,7 @@
if (f.mView != null) {
f.mInnerView = f.mView;
f.mView.setSaveFromParentEnabled(false);
+ setViewTag(f);
if (container != null) {
container.addView(f.mView);
}
@@ -2016,48 +2065,8 @@
}
}
- int allocBackStackIndex(BackStackRecord bse) {
- synchronized (mBackStackIndices) {
- if (mAvailBackStackIndices.isEmpty()) {
- int index = mBackStackIndices.size();
- if (DEBUG) Log.v(TAG, "Setting back stack index " + index + " to " + bse);
- mBackStackIndices.add(bse);
- return index;
-
- } else {
- int index = mAvailBackStackIndices.remove(mAvailBackStackIndices.size() - 1);
- if (DEBUG) Log.v(TAG, "Adding back stack index " + index + " with " + bse);
- mBackStackIndices.set(index, bse);
- return index;
- }
- }
- }
-
- private void setBackStackIndex(int index, BackStackRecord bse) {
- synchronized (mBackStackIndices) {
- int count = mBackStackIndices.size();
- if (index < count) {
- if (DEBUG) Log.v(TAG, "Setting back stack index " + index + " to " + bse);
- mBackStackIndices.set(index, bse);
- } else {
- while (count < index) {
- mBackStackIndices.add(null);
- if (DEBUG) Log.v(TAG, "Adding available back stack index " + count);
- mAvailBackStackIndices.add(count);
- count++;
- }
- if (DEBUG) Log.v(TAG, "Adding back stack index " + index + " with " + bse);
- mBackStackIndices.add(bse);
- }
- }
- }
-
- private void freeBackStackIndex(int index) {
- synchronized (mBackStackIndices) {
- mBackStackIndices.set(index, null);
- if (DEBUG) Log.v(TAG, "Freeing back stack index " + index);
- mAvailBackStackIndices.add(index);
- }
+ int allocBackStackIndex() {
+ return mBackStackIndex.getAndIncrement();
}
/**
@@ -2161,6 +2170,9 @@
if (records != null && !listener.mIsBack) {
int index = records.indexOf(listener.mRecord);
if (index != -1 && isRecordPop.get(index)) {
+ mPostponedTransactions.remove(i);
+ i--;
+ numPostponed--;
listener.cancelTransaction();
continue;
}
@@ -2296,7 +2308,6 @@
final BackStackRecord record = records.get(recordNum);
final boolean isPop = isRecordPop.get(recordNum);
if (isPop && record.mIndex >= 0) {
- freeBackStackIndex(record.mIndex);
record.mIndex = -1;
}
record.runOnCommitRunnables();
@@ -2830,6 +2841,7 @@
fms.mActive = active;
fms.mAdded = added;
fms.mBackStack = backStack;
+ fms.mBackStackIndex = mBackStackIndex.get();
if (mPrimaryNav != null) {
fms.mPrimaryNavActiveWho = mPrimaryNav.mWho;
}
@@ -2941,13 +2953,11 @@
pw.close();
}
mBackStack.add(bse);
- if (bse.mIndex >= 0) {
- setBackStackIndex(bse.mIndex, bse);
- }
}
} else {
mBackStack = null;
}
+ mBackStackIndex.set(fms.mBackStackIndex);
if (fms.mPrimaryNavActiveWho != null) {
mPrimaryNav = mActive.get(fms.mPrimaryNavActiveWho);
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManagerState.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManagerState.java
index 4eec63b..a3dbe08 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManagerState.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManagerState.java
@@ -27,6 +27,7 @@
ArrayList<FragmentState> mActive;
ArrayList<String> mAdded;
BackStackState[] mBackStack;
+ int mBackStackIndex;
String mPrimaryNavActiveWho = null;
public FragmentManagerState() {
@@ -36,6 +37,7 @@
mActive = in.createTypedArrayList(FragmentState.CREATOR);
mAdded = in.createStringArrayList();
mBackStack = in.createTypedArray(BackStackState.CREATOR);
+ mBackStackIndex = in.readInt();
mPrimaryNavActiveWho = in.readString();
}
@@ -49,6 +51,7 @@
dest.writeTypedList(mActive);
dest.writeStringList(mAdded);
dest.writeTypedArray(mBackStack, flags);
+ dest.writeInt(mBackStackIndex);
dest.writeString(mPrimaryNavActiveWho);
}
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 f92b54d..a66adf2 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentTransaction.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentTransaction.java
@@ -655,7 +655,8 @@
* is on the back stack.
*
* @param res A string resource containing the title.
- * @deprecated Store breadcrumb titles separately from fragment transactions.
+ * @deprecated Store breadcrumb titles separately from fragment transactions. For
+ * example, by using an <code>android:label</code> on a fragment in a navigation graph.
*/
@Deprecated
@NonNull
@@ -669,7 +670,8 @@
* Like {@link #setBreadCrumbTitle(int)} but taking a raw string; this
* method is <em>not</em> recommended, as the string can not be changed
* later if the locale changes.
- * @deprecated Store breadcrumb titles separately from fragment transactions.
+ * @deprecated Store breadcrumb titles separately from fragment transactions. For
+ * example, by using an <code>android:label</code> on a fragment in a navigation graph.
*/
@Deprecated
@NonNull
@@ -684,7 +686,8 @@
* is on the back stack.
*
* @param res A string resource containing the title.
- * @deprecated Store breadcrumb short titles separately from fragment transactions.
+ * @deprecated Store breadcrumb short titles separately from fragment transactions. For
+ * example, by using an <code>android:label</code> on a fragment in a navigation graph.
*/
@Deprecated
@NonNull
@@ -698,7 +701,8 @@
* Like {@link #setBreadCrumbShortTitle(int)} but taking a raw string; this
* method is <em>not</em> recommended, as the string can not be changed
* later if the locale changes.
- * @deprecated Store breadcrumb short titles separately from fragment transactions.
+ * @deprecated Store breadcrumb short titles separately from fragment transactions. For
+ * example, by using an <code>android:label</code> on a fragment in a navigation graph.
*/
@Deprecated
@NonNull
diff --git a/fragment/fragment/src/main/res/anim-v21/fast_out_extra_slow_in.xml b/fragment/fragment/src/main/res/anim-v21/fragment_fast_out_extra_slow_in.xml
similarity index 100%
rename from fragment/fragment/src/main/res/anim-v21/fast_out_extra_slow_in.xml
rename to fragment/fragment/src/main/res/anim-v21/fragment_fast_out_extra_slow_in.xml
diff --git a/fragment/fragment/src/main/res/anim/fragment_close_enter.xml b/fragment/fragment/src/main/res/anim/fragment_close_enter.xml
index 0a2d2b9..3b799e1 100644
--- a/fragment/fragment/src/main/res/anim/fragment_close_enter.xml
+++ b/fragment/fragment/src/main/res/anim/fragment_close_enter.xml
@@ -28,6 +28,6 @@
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
- android:interpolator="@anim/fast_out_extra_slow_in"
+ android:interpolator="@anim/fragment_fast_out_extra_slow_in"
android:duration="400"/>
</set>
\ No newline at end of file
diff --git a/fragment/fragment/src/main/res/anim/fragment_close_exit.xml b/fragment/fragment/src/main/res/anim/fragment_close_exit.xml
index 012c886..c540f93 100644
--- a/fragment/fragment/src/main/res/anim/fragment_close_exit.xml
+++ b/fragment/fragment/src/main/res/anim/fragment_close_exit.xml
@@ -38,6 +38,6 @@
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
- android:interpolator="@anim/fast_out_extra_slow_in"
+ android:interpolator="@anim/fragment_fast_out_extra_slow_in"
android:duration="400"/>
</set>
\ No newline at end of file
diff --git a/fragment/fragment/src/main/res/anim/fast_out_extra_slow_in.xml b/fragment/fragment/src/main/res/anim/fragment_fast_out_extra_slow_in.xml
similarity index 100%
rename from fragment/fragment/src/main/res/anim/fast_out_extra_slow_in.xml
rename to fragment/fragment/src/main/res/anim/fragment_fast_out_extra_slow_in.xml
diff --git a/fragment/fragment/src/main/res/anim/fragment_open_enter.xml b/fragment/fragment/src/main/res/anim/fragment_open_enter.xml
index e667dc7..e74d0e2 100644
--- a/fragment/fragment/src/main/res/anim/fragment_open_enter.xml
+++ b/fragment/fragment/src/main/res/anim/fragment_open_enter.xml
@@ -38,7 +38,7 @@
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
- android:pathData="@anim/fast_out_extra_slow_in"
+ android:pathData="@anim/fragment_fast_out_extra_slow_in"
android:duration="400"
tools:targetApi="lollipop" />
</set>
\ No newline at end of file
diff --git a/fragment/fragment/src/main/res/anim/fragment_open_exit.xml b/fragment/fragment/src/main/res/anim/fragment_open_exit.xml
index 4ea1ea7..ac1a760 100644
--- a/fragment/fragment/src/main/res/anim/fragment_open_exit.xml
+++ b/fragment/fragment/src/main/res/anim/fragment_open_exit.xml
@@ -38,6 +38,6 @@
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
- android:interpolator="@anim/fast_out_extra_slow_in"
+ android:interpolator="@anim/fragment_fast_out_extra_slow_in"
android:duration="400"/>
</set>
\ No newline at end of file
diff --git a/benchmark/src/androidTest/AndroidManifest.xml b/fragment/fragment/src/main/res/values/ids.xml
similarity index 65%
copy from benchmark/src/androidTest/AndroidManifest.xml
copy to fragment/fragment/src/main/res/values/ids.xml
index f5ec776..db4ce6e 100644
--- a/benchmark/src/androidTest/AndroidManifest.xml
+++ b/fragment/fragment/src/main/res/values/ids.xml
@@ -14,13 +14,7 @@
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.benchmark.test">
- <application
- android:name="androidx.benchmark.ArgumentInjectingApplication">
- <activity android:name="android.app.Activity"/>
- </application>
-</manifest>
\ No newline at end of file
+<resources>
+ <item type="id" name="fragment_container_view_tag" />
+</resources>
\ No newline at end of file
diff --git a/gradlew b/gradlew
index 1cf59b6..5e8daf4 100755
--- a/gradlew
+++ b/gradlew
@@ -9,6 +9,8 @@
# --------- androidx specific code needed for build server. ------------------
if [ -n "$OUT_DIR" ] ; then
+ mkdir -p "$OUT_DIR"
+ OUT_DIR="$(cd $OUT_DIR && pwd)"
export GRADLE_USER_HOME="$OUT_DIR/.gradle"
export LINT_PRINT_STACKTRACE=true
else
diff --git a/jetifier/jetifier/migration.config b/jetifier/jetifier/migration.config
index 03e7d2a..16ac67b 100644
--- a/jetifier/jetifier/migration.config
+++ b/jetifier/jetifier/migration.config
@@ -614,6 +614,10 @@
"to": "ignore"
},
{
+ "from": "androidx/benchmark/(.*)",
+ "to": "ignore"
+ },
+ {
"from": "androidx/camera/(.*)",
"to": "ignore"
},
@@ -972,6 +976,10 @@
"to": "androidx/sharetarget"
},
{
+ "from": "androidx/benchmark",
+ "to": "androidx/benchmark"
+ },
+ {
"from": "androidx/camera",
"to": "androidx/camera"
},
@@ -3023,6 +3031,30 @@
},
{
"from": {
+ "groupId": "androidx.benchmark",
+ "artifactId": "benchmark-common",
+ "version": "{newBenchmarkVersion}"
+ },
+ "to": {
+ "groupId": "androidx.benchmark",
+ "artifactId": "benchmark-common",
+ "version": "{newBenchmarkVersion}"
+ }
+ },
+ {
+ "from": {
+ "groupId": "androidx.benchmark",
+ "artifactId": "benchmark-junit4",
+ "version": "{newBenchmarkVersion}"
+ },
+ "to": {
+ "groupId": "androidx.benchmark",
+ "artifactId": "benchmark-junit4",
+ "version": "{newBenchmarkVersion}"
+ }
+ },
+ {
+ "from": {
"groupId": "androidx.camera",
"artifactId": "camera-core",
"version": "{newCameraVersion}"
@@ -3133,6 +3165,7 @@
"newBiometricVersion": "1.0.0-alpha03",
"newDataBindingVersion": "undefined",
"newWorkManagerVersion": "2.0.0",
+ "newBenchmarkVersion": "1.0.0-alpha04",
"newCameraVersion": "1.0.0-alpha01"
}
},
diff --git a/leanback/api/api_lint.ignore b/leanback/api/api_lint.ignore
index b8d6059..47e6f64 100644
--- a/leanback/api/api_lint.ignore
+++ b/leanback/api/api_lint.ignore
@@ -49,22 +49,8 @@
Fractions must use floats, was `androidx.leanback.widget.Parallax.PropertyMarkerValue` in `atFraction`
-KotlinOperator: androidx.leanback.widget.ArrayObjectAdapter#get(int):
- Method can be invoked with an indexing operator from Kotlin: `get` (this is usually desirable; just make sure it makes sense for this type of object)
-KotlinOperator: androidx.leanback.widget.CursorObjectAdapter#get(int):
- Method can be invoked with an indexing operator from Kotlin: `get` (this is usually desirable; just make sure it makes sense for this type of object)
KotlinOperator: androidx.leanback.widget.ObjectAdapter#get(int):
Method can be invoked with an indexing operator from Kotlin: `get` (this is usually desirable; just make sure it makes sense for this type of object)
-KotlinOperator: androidx.leanback.widget.Parallax.FloatProperty#get(androidx.leanback.widget.Parallax):
- Method can be invoked with an indexing operator from Kotlin: `get` (this is usually desirable; just make sure it makes sense for this type of object)
-KotlinOperator: androidx.leanback.widget.Parallax.FloatProperty#set(androidx.leanback.widget.Parallax, Float):
- Method can be invoked with an indexing operator from Kotlin: `set` (this is usually desirable; just make sure it makes sense for this type of object)
-KotlinOperator: androidx.leanback.widget.Parallax.IntProperty#get(androidx.leanback.widget.Parallax):
- Method can be invoked with an indexing operator from Kotlin: `get` (this is usually desirable; just make sure it makes sense for this type of object)
-KotlinOperator: androidx.leanback.widget.Parallax.IntProperty#set(androidx.leanback.widget.Parallax, Integer):
- Method can be invoked with an indexing operator from Kotlin: `set` (this is usually desirable; just make sure it makes sense for this type of object)
-KotlinOperator: androidx.leanback.widget.SparseArrayObjectAdapter#get(int):
- Method can be invoked with an indexing operator from Kotlin: `get` (this is usually desirable; just make sure it makes sense for this type of object)
KotlinOperator: androidx.leanback.widget.SparseArrayObjectAdapter#set(int, Object):
Method can be invoked with an indexing operator from Kotlin: `set` (this is usually desirable; just make sure it makes sense for this type of object)
diff --git a/lifecycle/lifecycle-extensions/api/2.2.0-alpha03.txt b/lifecycle/lifecycle-extensions/api/2.2.0-alpha03.txt
index c85f1ab..273ffbf 100644
--- a/lifecycle/lifecycle-extensions/api/2.2.0-alpha03.txt
+++ b/lifecycle/lifecycle-extensions/api/2.2.0-alpha03.txt
@@ -1,12 +1,12 @@
// Signature format: 3.0
package androidx.lifecycle {
- public class ViewModelProviders {
+ @Deprecated public class ViewModelProviders {
ctor @Deprecated public ViewModelProviders();
- method @MainThread public static androidx.lifecycle.ViewModelProvider of(androidx.fragment.app.Fragment);
- method @MainThread public static androidx.lifecycle.ViewModelProvider of(androidx.fragment.app.FragmentActivity);
- method @MainThread public static androidx.lifecycle.ViewModelProvider of(androidx.fragment.app.Fragment, androidx.lifecycle.ViewModelProvider.Factory?);
- method @MainThread public static androidx.lifecycle.ViewModelProvider of(androidx.fragment.app.FragmentActivity, androidx.lifecycle.ViewModelProvider.Factory?);
+ method @Deprecated @MainThread public static androidx.lifecycle.ViewModelProvider of(androidx.fragment.app.Fragment);
+ method @Deprecated @MainThread public static androidx.lifecycle.ViewModelProvider of(androidx.fragment.app.FragmentActivity);
+ method @Deprecated @MainThread public static androidx.lifecycle.ViewModelProvider of(androidx.fragment.app.Fragment, androidx.lifecycle.ViewModelProvider.Factory?);
+ method @Deprecated @MainThread public static androidx.lifecycle.ViewModelProvider of(androidx.fragment.app.FragmentActivity, androidx.lifecycle.ViewModelProvider.Factory?);
}
@Deprecated public static class ViewModelProviders.DefaultFactory extends androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory {
diff --git a/lifecycle/lifecycle-extensions/api/current.txt b/lifecycle/lifecycle-extensions/api/current.txt
index c85f1ab..273ffbf 100644
--- a/lifecycle/lifecycle-extensions/api/current.txt
+++ b/lifecycle/lifecycle-extensions/api/current.txt
@@ -1,12 +1,12 @@
// Signature format: 3.0
package androidx.lifecycle {
- public class ViewModelProviders {
+ @Deprecated public class ViewModelProviders {
ctor @Deprecated public ViewModelProviders();
- method @MainThread public static androidx.lifecycle.ViewModelProvider of(androidx.fragment.app.Fragment);
- method @MainThread public static androidx.lifecycle.ViewModelProvider of(androidx.fragment.app.FragmentActivity);
- method @MainThread public static androidx.lifecycle.ViewModelProvider of(androidx.fragment.app.Fragment, androidx.lifecycle.ViewModelProvider.Factory?);
- method @MainThread public static androidx.lifecycle.ViewModelProvider of(androidx.fragment.app.FragmentActivity, androidx.lifecycle.ViewModelProvider.Factory?);
+ method @Deprecated @MainThread public static androidx.lifecycle.ViewModelProvider of(androidx.fragment.app.Fragment);
+ method @Deprecated @MainThread public static androidx.lifecycle.ViewModelProvider of(androidx.fragment.app.FragmentActivity);
+ method @Deprecated @MainThread public static androidx.lifecycle.ViewModelProvider of(androidx.fragment.app.Fragment, androidx.lifecycle.ViewModelProvider.Factory?);
+ method @Deprecated @MainThread public static androidx.lifecycle.ViewModelProvider of(androidx.fragment.app.FragmentActivity, androidx.lifecycle.ViewModelProvider.Factory?);
}
@Deprecated public static class ViewModelProviders.DefaultFactory extends androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory {
diff --git a/lifecycle/lifecycle-extensions/api/restricted_2.2.0-alpha03.txt b/lifecycle/lifecycle-extensions/api/restricted_2.2.0-alpha03.txt
index c85f1ab..273ffbf 100644
--- a/lifecycle/lifecycle-extensions/api/restricted_2.2.0-alpha03.txt
+++ b/lifecycle/lifecycle-extensions/api/restricted_2.2.0-alpha03.txt
@@ -1,12 +1,12 @@
// Signature format: 3.0
package androidx.lifecycle {
- public class ViewModelProviders {
+ @Deprecated public class ViewModelProviders {
ctor @Deprecated public ViewModelProviders();
- method @MainThread public static androidx.lifecycle.ViewModelProvider of(androidx.fragment.app.Fragment);
- method @MainThread public static androidx.lifecycle.ViewModelProvider of(androidx.fragment.app.FragmentActivity);
- method @MainThread public static androidx.lifecycle.ViewModelProvider of(androidx.fragment.app.Fragment, androidx.lifecycle.ViewModelProvider.Factory?);
- method @MainThread public static androidx.lifecycle.ViewModelProvider of(androidx.fragment.app.FragmentActivity, androidx.lifecycle.ViewModelProvider.Factory?);
+ method @Deprecated @MainThread public static androidx.lifecycle.ViewModelProvider of(androidx.fragment.app.Fragment);
+ method @Deprecated @MainThread public static androidx.lifecycle.ViewModelProvider of(androidx.fragment.app.FragmentActivity);
+ method @Deprecated @MainThread public static androidx.lifecycle.ViewModelProvider of(androidx.fragment.app.Fragment, androidx.lifecycle.ViewModelProvider.Factory?);
+ method @Deprecated @MainThread public static androidx.lifecycle.ViewModelProvider of(androidx.fragment.app.FragmentActivity, androidx.lifecycle.ViewModelProvider.Factory?);
}
@Deprecated public static class ViewModelProviders.DefaultFactory extends androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory {
diff --git a/lifecycle/lifecycle-extensions/api/restricted_current.txt b/lifecycle/lifecycle-extensions/api/restricted_current.txt
index c85f1ab..273ffbf 100644
--- a/lifecycle/lifecycle-extensions/api/restricted_current.txt
+++ b/lifecycle/lifecycle-extensions/api/restricted_current.txt
@@ -1,12 +1,12 @@
// Signature format: 3.0
package androidx.lifecycle {
- public class ViewModelProviders {
+ @Deprecated public class ViewModelProviders {
ctor @Deprecated public ViewModelProviders();
- method @MainThread public static androidx.lifecycle.ViewModelProvider of(androidx.fragment.app.Fragment);
- method @MainThread public static androidx.lifecycle.ViewModelProvider of(androidx.fragment.app.FragmentActivity);
- method @MainThread public static androidx.lifecycle.ViewModelProvider of(androidx.fragment.app.Fragment, androidx.lifecycle.ViewModelProvider.Factory?);
- method @MainThread public static androidx.lifecycle.ViewModelProvider of(androidx.fragment.app.FragmentActivity, androidx.lifecycle.ViewModelProvider.Factory?);
+ method @Deprecated @MainThread public static androidx.lifecycle.ViewModelProvider of(androidx.fragment.app.Fragment);
+ method @Deprecated @MainThread public static androidx.lifecycle.ViewModelProvider of(androidx.fragment.app.FragmentActivity);
+ method @Deprecated @MainThread public static androidx.lifecycle.ViewModelProvider of(androidx.fragment.app.Fragment, androidx.lifecycle.ViewModelProvider.Factory?);
+ method @Deprecated @MainThread public static androidx.lifecycle.ViewModelProvider of(androidx.fragment.app.FragmentActivity, androidx.lifecycle.ViewModelProvider.Factory?);
}
@Deprecated public static class ViewModelProviders.DefaultFactory extends androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory {
diff --git a/lifecycle/lifecycle-extensions/src/androidTest/java/androidx/lifecycle/ViewModelProvidersFragmentTest.kt b/lifecycle/lifecycle-extensions/src/androidTest/java/androidx/lifecycle/ViewModelProvidersFragmentTest.kt
index 696c0c9..9cfd0c9 100644
--- a/lifecycle/lifecycle-extensions/src/androidTest/java/androidx/lifecycle/ViewModelProvidersFragmentTest.kt
+++ b/lifecycle/lifecycle-extensions/src/androidTest/java/androidx/lifecycle/ViewModelProvidersFragmentTest.kt
@@ -25,6 +25,7 @@
import org.junit.Test
import org.junit.runner.RunWith
+@Suppress("DEPRECATION")
@MediumTest
@RunWith(AndroidJUnit4::class)
class ViewModelProvidersFragmentTest {
diff --git a/lifecycle/lifecycle-extensions/src/main/java/androidx/lifecycle/ViewModelProviders.java b/lifecycle/lifecycle-extensions/src/main/java/androidx/lifecycle/ViewModelProviders.java
index 0b83b92..2bf899e 100644
--- a/lifecycle/lifecycle-extensions/src/main/java/androidx/lifecycle/ViewModelProviders.java
+++ b/lifecycle/lifecycle-extensions/src/main/java/androidx/lifecycle/ViewModelProviders.java
@@ -16,7 +16,6 @@
package androidx.lifecycle;
-import android.app.Activity;
import android.app.Application;
import androidx.annotation.MainThread;
@@ -28,7 +27,10 @@
/**
* Utilities methods for {@link ViewModelStore} class.
+ *
+ * @deprecated Use the constructors for {@link ViewModelProvider} directly.
*/
+@Deprecated
public class ViewModelProviders {
/**
@@ -38,51 +40,44 @@
public ViewModelProviders() {
}
- private static Application checkApplication(Activity activity) {
- Application application = activity.getApplication();
- if (application == null) {
- throw new IllegalStateException("Your activity/fragment is not yet attached to "
- + "Application. You can't request ViewModel before onCreate call.");
- }
- return application;
- }
-
- private static Activity checkActivity(Fragment fragment) {
- Activity activity = fragment.getActivity();
- if (activity == null) {
- throw new IllegalStateException("Can't create ViewModelProvider for detached fragment");
- }
- return activity;
- }
-
/**
* Creates a {@link ViewModelProvider}, which retains ViewModels while a scope of given
* {@code fragment} is alive. More detailed explanation is in {@link ViewModel}.
* <p>
- * It uses {@link ViewModelProvider.AndroidViewModelFactory} to instantiate new ViewModels.
+ * It uses the {@link Fragment#getDefaultViewModelProviderFactory() default factory}
+ * to instantiate new ViewModels.
*
* @param fragment a fragment, in whose scope ViewModels should be retained
* @return a ViewModelProvider instance
+ * @deprecated Use the 'by viewModels()' Kotlin property delegate or
+ * {@link ViewModelProvider#ViewModelProvider(ViewModelStoreOwner)},
+ * passing in the fragment.
*/
+ @Deprecated
@NonNull
@MainThread
public static ViewModelProvider of(@NonNull Fragment fragment) {
- return of(fragment, null);
+ return new ViewModelProvider(fragment);
}
/**
* Creates a {@link ViewModelProvider}, which retains ViewModels while a scope of given Activity
* is alive. More detailed explanation is in {@link ViewModel}.
* <p>
- * It uses {@link ViewModelProvider.AndroidViewModelFactory} to instantiate new ViewModels.
+ * It uses the {@link FragmentActivity#getDefaultViewModelProviderFactory() default factory}
+ * to instantiate new ViewModels.
*
* @param activity an activity, in whose scope ViewModels should be retained
* @return a ViewModelProvider instance
+ * @deprecated Use the 'by viewModels()' Kotlin property delegate or
+ * {@link ViewModelProvider#ViewModelProvider(ViewModelStoreOwner)},
+ * passing in the activity.
*/
+ @Deprecated
@NonNull
@MainThread
public static ViewModelProvider of(@NonNull FragmentActivity activity) {
- return of(activity, null);
+ return new ViewModelProvider(activity);
}
/**
@@ -94,13 +89,16 @@
* @param fragment a fragment, in whose scope ViewModels should be retained
* @param factory a {@code Factory} to instantiate new ViewModels
* @return a ViewModelProvider instance
+ * @deprecated Use the 'by viewModels()' Kotlin property delegate or
+ * {@link ViewModelProvider#ViewModelProvider(ViewModelStoreOwner, Factory)},
+ * passing in the fragment and factory.
*/
+ @Deprecated
@NonNull
@MainThread
public static ViewModelProvider of(@NonNull Fragment fragment, @Nullable Factory factory) {
- Application application = checkApplication(checkActivity(fragment));
if (factory == null) {
- factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
+ factory = fragment.getDefaultViewModelProviderFactory();
}
return new ViewModelProvider(fragment.getViewModelStore(), factory);
}
@@ -114,14 +112,17 @@
* @param activity an activity, in whose scope ViewModels should be retained
* @param factory a {@code Factory} to instantiate new ViewModels
* @return a ViewModelProvider instance
+ * @deprecated Use the 'by viewModels()' Kotlin property delegate or
+ * {@link ViewModelProvider#ViewModelProvider(ViewModelStoreOwner, Factory)},
+ * passing in the activity and factory.
*/
+ @Deprecated
@NonNull
@MainThread
public static ViewModelProvider of(@NonNull FragmentActivity activity,
@Nullable Factory factory) {
- Application application = checkApplication(activity);
if (factory == null) {
- factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
+ factory = activity.getDefaultViewModelProviderFactory();
}
return new ViewModelProvider(activity.getViewModelStore(), factory);
}
diff --git a/lifecycle/lifecycle-extensions/src/test/java/androidx/lifecycle/ViewModelProvidersTest.java b/lifecycle/lifecycle-extensions/src/test/java/androidx/lifecycle/ViewModelProvidersTest.java
index e21f5c1..1dc35ae 100644
--- a/lifecycle/lifecycle-extensions/src/test/java/androidx/lifecycle/ViewModelProvidersTest.java
+++ b/lifecycle/lifecycle-extensions/src/test/java/androidx/lifecycle/ViewModelProvidersTest.java
@@ -23,6 +23,7 @@
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+@SuppressWarnings("deprecation")
@RunWith(JUnit4.class)
public class ViewModelProvidersTest {
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/api/1.0.0-alpha03.txt b/lifecycle/lifecycle-viewmodel-savedstate/api/1.0.0-alpha03.txt
index 53339c9..8bd77f2 100644
--- a/lifecycle/lifecycle-viewmodel-savedstate/api/1.0.0-alpha03.txt
+++ b/lifecycle/lifecycle-viewmodel-savedstate/api/1.0.0-alpha03.txt
@@ -20,10 +20,7 @@
}
public final class SavedStateViewModelFactory extends androidx.lifecycle.AbstractSavedStateViewModelFactory implements androidx.lifecycle.ViewModelProvider.Factory {
- ctor public SavedStateViewModelFactory(androidx.fragment.app.Fragment);
- ctor public SavedStateViewModelFactory(androidx.fragment.app.Fragment, android.os.Bundle?);
- ctor public SavedStateViewModelFactory(androidx.fragment.app.FragmentActivity);
- ctor public SavedStateViewModelFactory(androidx.fragment.app.FragmentActivity, android.os.Bundle?);
+ ctor public SavedStateViewModelFactory(android.app.Application, androidx.savedstate.SavedStateRegistryOwner);
ctor public SavedStateViewModelFactory(android.app.Application, androidx.savedstate.SavedStateRegistryOwner, android.os.Bundle?);
method protected <T extends androidx.lifecycle.ViewModel> T create(String, Class<T!>, androidx.lifecycle.SavedStateHandle);
}
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/api/current.txt b/lifecycle/lifecycle-viewmodel-savedstate/api/current.txt
index 53339c9..8bd77f2 100644
--- a/lifecycle/lifecycle-viewmodel-savedstate/api/current.txt
+++ b/lifecycle/lifecycle-viewmodel-savedstate/api/current.txt
@@ -20,10 +20,7 @@
}
public final class SavedStateViewModelFactory extends androidx.lifecycle.AbstractSavedStateViewModelFactory implements androidx.lifecycle.ViewModelProvider.Factory {
- ctor public SavedStateViewModelFactory(androidx.fragment.app.Fragment);
- ctor public SavedStateViewModelFactory(androidx.fragment.app.Fragment, android.os.Bundle?);
- ctor public SavedStateViewModelFactory(androidx.fragment.app.FragmentActivity);
- ctor public SavedStateViewModelFactory(androidx.fragment.app.FragmentActivity, android.os.Bundle?);
+ ctor public SavedStateViewModelFactory(android.app.Application, androidx.savedstate.SavedStateRegistryOwner);
ctor public SavedStateViewModelFactory(android.app.Application, androidx.savedstate.SavedStateRegistryOwner, android.os.Bundle?);
method protected <T extends androidx.lifecycle.ViewModel> T create(String, Class<T!>, androidx.lifecycle.SavedStateHandle);
}
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/api/restricted_1.0.0-alpha03.txt b/lifecycle/lifecycle-viewmodel-savedstate/api/restricted_1.0.0-alpha03.txt
index b023830b..efd5ff3 100644
--- a/lifecycle/lifecycle-viewmodel-savedstate/api/restricted_1.0.0-alpha03.txt
+++ b/lifecycle/lifecycle-viewmodel-savedstate/api/restricted_1.0.0-alpha03.txt
@@ -21,10 +21,7 @@
}
public final class SavedStateViewModelFactory extends androidx.lifecycle.AbstractSavedStateViewModelFactory implements androidx.lifecycle.ViewModelProvider.Factory {
- ctor public SavedStateViewModelFactory(androidx.fragment.app.Fragment);
- ctor public SavedStateViewModelFactory(androidx.fragment.app.Fragment, android.os.Bundle?);
- ctor public SavedStateViewModelFactory(androidx.fragment.app.FragmentActivity);
- ctor public SavedStateViewModelFactory(androidx.fragment.app.FragmentActivity, android.os.Bundle?);
+ ctor public SavedStateViewModelFactory(android.app.Application, androidx.savedstate.SavedStateRegistryOwner);
ctor public SavedStateViewModelFactory(android.app.Application, androidx.savedstate.SavedStateRegistryOwner, android.os.Bundle?);
method protected <T extends androidx.lifecycle.ViewModel> T create(String, Class<T!>, androidx.lifecycle.SavedStateHandle);
}
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/api/restricted_current.txt b/lifecycle/lifecycle-viewmodel-savedstate/api/restricted_current.txt
index b023830b..efd5ff3 100644
--- a/lifecycle/lifecycle-viewmodel-savedstate/api/restricted_current.txt
+++ b/lifecycle/lifecycle-viewmodel-savedstate/api/restricted_current.txt
@@ -21,10 +21,7 @@
}
public final class SavedStateViewModelFactory extends androidx.lifecycle.AbstractSavedStateViewModelFactory implements androidx.lifecycle.ViewModelProvider.Factory {
- ctor public SavedStateViewModelFactory(androidx.fragment.app.Fragment);
- ctor public SavedStateViewModelFactory(androidx.fragment.app.Fragment, android.os.Bundle?);
- ctor public SavedStateViewModelFactory(androidx.fragment.app.FragmentActivity);
- ctor public SavedStateViewModelFactory(androidx.fragment.app.FragmentActivity, android.os.Bundle?);
+ ctor public SavedStateViewModelFactory(android.app.Application, androidx.savedstate.SavedStateRegistryOwner);
ctor public SavedStateViewModelFactory(android.app.Application, androidx.savedstate.SavedStateRegistryOwner, android.os.Bundle?);
method protected <T extends androidx.lifecycle.ViewModel> T create(String, Class<T!>, androidx.lifecycle.SavedStateHandle);
}
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/build.gradle b/lifecycle/lifecycle-viewmodel-savedstate/build.gradle
index 2d58931..ad09591 100644
--- a/lifecycle/lifecycle-viewmodel-savedstate/build.gradle
+++ b/lifecycle/lifecycle-viewmodel-savedstate/build.gradle
@@ -38,10 +38,9 @@
api(project(":lifecycle:lifecycle-livedata-core"))
api(project(":lifecycle:lifecycle-viewmodel"))
- api project(":fragment:fragment"), {
- exclude group: 'androidx.lifecycle', module: 'lifecycle-livedata-core'
+ androidTestImplementation project(":fragment:fragment"), {
+ exclude group: 'androidx.lifecycle', module: 'lifecycle-viewmodel-savedstate'
}
-
androidTestImplementation(TRUTH)
androidTestImplementation(KOTLIN_STDLIB)
androidTestImplementation(ESPRESSO_CORE)
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/src/androidTest/java/androidx/lifecycle/viewmodel/savedstate/SavedStateFactoryTest.kt b/lifecycle/lifecycle-viewmodel-savedstate/src/androidTest/java/androidx/lifecycle/viewmodel/savedstate/SavedStateFactoryTest.kt
index 0277b01..2da4e17 100644
--- a/lifecycle/lifecycle-viewmodel-savedstate/src/androidTest/java/androidx/lifecycle/viewmodel/savedstate/SavedStateFactoryTest.kt
+++ b/lifecycle/lifecycle-viewmodel-savedstate/src/androidTest/java/androidx/lifecycle/viewmodel/savedstate/SavedStateFactoryTest.kt
@@ -41,8 +41,9 @@
@Test
fun testCreateAndroidVM() {
- val savedStateVMFactory =
- SavedStateViewModelFactory(activityRule.activity)
+ val savedStateVMFactory = SavedStateViewModelFactory(
+ activityRule.activity.application,
+ activityRule.activity)
val vm = ViewModelProvider(ViewModelStore(), savedStateVMFactory)
assertThat(vm.get(MyAndroidViewModel::class.java).handle).isNotNull()
assertThat(vm.get(MyViewModel::class.java).handle).isNotNull()
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/src/androidTest/java/androidx/lifecycle/viewmodel/savedstate/ViewModelsWithStateTests.java b/lifecycle/lifecycle-viewmodel-savedstate/src/androidTest/java/androidx/lifecycle/viewmodel/savedstate/ViewModelsWithStateTests.java
index 5b2b458..8f0a31d 100644
--- a/lifecycle/lifecycle-viewmodel-savedstate/src/androidTest/java/androidx/lifecycle/viewmodel/savedstate/ViewModelsWithStateTests.java
+++ b/lifecycle/lifecycle-viewmodel-savedstate/src/androidTest/java/androidx/lifecycle/viewmodel/savedstate/ViewModelsWithStateTests.java
@@ -143,14 +143,17 @@
private ViewModelProvider vmProvider(FakingSavedStateActivity activity) {
if (FRAGMENT_MODE.equals(mode)) {
Fragment fragment = activity.getFragment();
- return new ViewModelProvider(fragment, new SavedStateViewModelFactory(fragment));
+ return new ViewModelProvider(fragment, new SavedStateViewModelFactory(
+ fragment.requireActivity().getApplication(), fragment));
}
- return new ViewModelProvider(activity, new SavedStateViewModelFactory(activity));
+ return new ViewModelProvider(activity, new SavedStateViewModelFactory(
+ activity.getApplication(), activity));
}
// copy copy copy paste
@SuppressWarnings("unchecked")
- private static <T extends Activity> T recreateActivity(final T activity, ActivityTestRule rule)
+ private static <T extends Activity> T recreateActivity(final T activity,
+ ActivityTestRule<?> rule)
throws Throwable {
Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor(
activity.getClass().getCanonicalName(), null, false);
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/src/main/java/androidx/lifecycle/SavedStateViewModelFactory.java b/lifecycle/lifecycle-viewmodel-savedstate/src/main/java/androidx/lifecycle/SavedStateViewModelFactory.java
index a4c48dc..59a3039 100644
--- a/lifecycle/lifecycle-viewmodel-savedstate/src/main/java/androidx/lifecycle/SavedStateViewModelFactory.java
+++ b/lifecycle/lifecycle-viewmodel-savedstate/src/main/java/androidx/lifecycle/SavedStateViewModelFactory.java
@@ -16,14 +16,12 @@
package androidx.lifecycle;
-import android.app.Activity;
+import android.annotation.SuppressLint;
import android.app.Application;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentActivity;
import androidx.savedstate.SavedStateRegistryOwner;
import java.lang.reflect.Constructor;
@@ -33,8 +31,7 @@
/**
* {@link androidx.lifecycle.ViewModelProvider.Factory} that can create ViewModels accessing and
* contributing to a saved state via {@link SavedStateHandle} received in a constructor.
- * If {@code defaultArgs} bundle was passed in {@link #SavedStateViewModelFactory(Fragment, Bundle)}
- * or {@link #SavedStateViewModelFactory(FragmentActivity, Bundle)}, it will provide default
+ * If {@code defaultArgs} bundle was passed into the constructor, it will provide default
* values in {@code SavedStateHandle}.
* <p>
* If ViewModel is instance of {@link androidx.lifecycle.AndroidViewModel}, it looks for a
@@ -49,55 +46,15 @@
* Creates {@link SavedStateViewModelFactory}.
* <p>
* {@link androidx.lifecycle.ViewModel} created with this factory can access to saved state
- * scoped to the given {@code fragment}.
- *
- * @param fragment scope of this fragment will be used for state saving
- */
- public SavedStateViewModelFactory(@NonNull Fragment fragment) {
- this(fragment, null);
- }
-
- /**
- * Creates {@link SavedStateViewModelFactory}.
- * <p>
- * {@link androidx.lifecycle.ViewModel} created with this factory can access to saved state
- * scoped to the given {@code fragment}.
- *
- * @param fragment scope of this fragment will be used for state saving
- * @param defaultArgs values from this {@code Bundle} will be used as defaults by
- * {@link SavedStateHandle} if there is no previously saved state or previously saved state
- * miss a value by such key.
- */
- public SavedStateViewModelFactory(@NonNull Fragment fragment, @Nullable Bundle defaultArgs) {
- this(checkApplication(checkActivity(fragment)), fragment, defaultArgs);
- }
-
- /**
- * Creates {@link SavedStateViewModelFactory}.
- * <p>
- * {@link androidx.lifecycle.ViewModel} created with this factory can access to saved state
* scoped to the given {@code activity}.
*
- * @param activity scope of this activity will be used for state saving
+ * @param application an application
+ * @param owner {@link SavedStateRegistryOwner} that will provide restored state for created
+ * {@link androidx.lifecycle.ViewModel ViewModels}
*/
- public SavedStateViewModelFactory(@NonNull FragmentActivity activity) {
- this(activity, null);
- }
-
- /**
- * Creates {@link SavedStateViewModelFactory}.
- * <p>
- * {@link androidx.lifecycle.ViewModel} created with this factory can access to saved state
- * scoped to the given {@code activity}.
- *
- * @param activity scope of this activity will be used for state saving
- * @param defaultArgs values from this {@code Bundle} will be used as defaults by
- * {@link SavedStateHandle} if there is no previously saved state or previously saved state
- * misses a value by such key.
- */
- public SavedStateViewModelFactory(@NonNull FragmentActivity activity,
- @Nullable Bundle defaultArgs) {
- this(checkApplication(activity), activity, defaultArgs);
+ public SavedStateViewModelFactory(@NonNull Application application,
+ @NonNull SavedStateRegistryOwner owner) {
+ this(application, owner, null);
}
/**
@@ -113,6 +70,7 @@
* {@link SavedStateHandle} if there is no previously saved state or previously saved state
* misses a value by such key.
*/
+ @SuppressLint("LambdaLast")
public SavedStateViewModelFactory(@NonNull Application application,
@NonNull SavedStateRegistryOwner owner,
@Nullable Bundle defaultArgs) {
@@ -166,23 +124,4 @@
}
return null;
}
-
- private static Application checkApplication(Activity activity) {
- Application application = activity.getApplication();
- if (application == null) {
- throw new IllegalStateException("Your activity/fragment is not yet attached to "
- + "Application. You can't request ViewModelsWithStateFactory "
- + "before onCreate call.");
- }
- return application;
- }
-
- private static Activity checkActivity(Fragment fragment) {
- Activity activity = fragment.getActivity();
- if (activity == null) {
- throw new IllegalStateException("Can't create ViewModelsWithStateFactory"
- + " for detached fragment");
- }
- return activity;
- }
}
diff --git a/lifecycle/lifecycle-viewmodel/api/2.2.0-alpha03.txt b/lifecycle/lifecycle-viewmodel/api/2.2.0-alpha03.txt
index 1c69a2a..07a8cb5 100644
--- a/lifecycle/lifecycle-viewmodel/api/2.2.0-alpha03.txt
+++ b/lifecycle/lifecycle-viewmodel/api/2.2.0-alpha03.txt
@@ -6,12 +6,17 @@
method public <T extends android.app.Application> T getApplication();
}
+ public interface HasDefaultViewModelProviderFactory {
+ method public androidx.lifecycle.ViewModelProvider.Factory getDefaultViewModelProviderFactory();
+ }
+
public abstract class ViewModel {
ctor public ViewModel();
method protected void onCleared();
}
public class ViewModelProvider {
+ ctor public ViewModelProvider(androidx.lifecycle.ViewModelStoreOwner);
ctor public ViewModelProvider(androidx.lifecycle.ViewModelStoreOwner, androidx.lifecycle.ViewModelProvider.Factory);
ctor public ViewModelProvider(androidx.lifecycle.ViewModelStore, androidx.lifecycle.ViewModelProvider.Factory);
method @MainThread public <T extends androidx.lifecycle.ViewModel> T get(Class<T!>);
diff --git a/lifecycle/lifecycle-viewmodel/api/current.txt b/lifecycle/lifecycle-viewmodel/api/current.txt
index 1c69a2a..07a8cb5 100644
--- a/lifecycle/lifecycle-viewmodel/api/current.txt
+++ b/lifecycle/lifecycle-viewmodel/api/current.txt
@@ -6,12 +6,17 @@
method public <T extends android.app.Application> T getApplication();
}
+ public interface HasDefaultViewModelProviderFactory {
+ method public androidx.lifecycle.ViewModelProvider.Factory getDefaultViewModelProviderFactory();
+ }
+
public abstract class ViewModel {
ctor public ViewModel();
method protected void onCleared();
}
public class ViewModelProvider {
+ ctor public ViewModelProvider(androidx.lifecycle.ViewModelStoreOwner);
ctor public ViewModelProvider(androidx.lifecycle.ViewModelStoreOwner, androidx.lifecycle.ViewModelProvider.Factory);
ctor public ViewModelProvider(androidx.lifecycle.ViewModelStore, androidx.lifecycle.ViewModelProvider.Factory);
method @MainThread public <T extends androidx.lifecycle.ViewModel> T get(Class<T!>);
diff --git a/lifecycle/lifecycle-viewmodel/api/restricted_2.2.0-alpha03.txt b/lifecycle/lifecycle-viewmodel/api/restricted_2.2.0-alpha03.txt
index 1c69a2a..07a8cb5 100644
--- a/lifecycle/lifecycle-viewmodel/api/restricted_2.2.0-alpha03.txt
+++ b/lifecycle/lifecycle-viewmodel/api/restricted_2.2.0-alpha03.txt
@@ -6,12 +6,17 @@
method public <T extends android.app.Application> T getApplication();
}
+ public interface HasDefaultViewModelProviderFactory {
+ method public androidx.lifecycle.ViewModelProvider.Factory getDefaultViewModelProviderFactory();
+ }
+
public abstract class ViewModel {
ctor public ViewModel();
method protected void onCleared();
}
public class ViewModelProvider {
+ ctor public ViewModelProvider(androidx.lifecycle.ViewModelStoreOwner);
ctor public ViewModelProvider(androidx.lifecycle.ViewModelStoreOwner, androidx.lifecycle.ViewModelProvider.Factory);
ctor public ViewModelProvider(androidx.lifecycle.ViewModelStore, androidx.lifecycle.ViewModelProvider.Factory);
method @MainThread public <T extends androidx.lifecycle.ViewModel> T get(Class<T!>);
diff --git a/lifecycle/lifecycle-viewmodel/api/restricted_current.txt b/lifecycle/lifecycle-viewmodel/api/restricted_current.txt
index 1c69a2a..07a8cb5 100644
--- a/lifecycle/lifecycle-viewmodel/api/restricted_current.txt
+++ b/lifecycle/lifecycle-viewmodel/api/restricted_current.txt
@@ -6,12 +6,17 @@
method public <T extends android.app.Application> T getApplication();
}
+ public interface HasDefaultViewModelProviderFactory {
+ method public androidx.lifecycle.ViewModelProvider.Factory getDefaultViewModelProviderFactory();
+ }
+
public abstract class ViewModel {
ctor public ViewModel();
method protected void onCleared();
}
public class ViewModelProvider {
+ ctor public ViewModelProvider(androidx.lifecycle.ViewModelStoreOwner);
ctor public ViewModelProvider(androidx.lifecycle.ViewModelStoreOwner, androidx.lifecycle.ViewModelProvider.Factory);
ctor public ViewModelProvider(androidx.lifecycle.ViewModelStore, androidx.lifecycle.ViewModelProvider.Factory);
method @MainThread public <T extends androidx.lifecycle.ViewModel> T get(Class<T!>);
diff --git a/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/HasDefaultViewModelProviderFactory.java b/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/HasDefaultViewModelProviderFactory.java
new file mode 100644
index 0000000..1db6658
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/HasDefaultViewModelProviderFactory.java
@@ -0,0 +1,36 @@
+/*
+ * 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.lifecycle;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Interface that marks a {@link ViewModelStoreOwner} as having a default
+ * {@link ViewModelProvider.Factory} for use with
+ * {@link ViewModelProvider#ViewModelProvider(ViewModelStoreOwner)}.
+ */
+public interface HasDefaultViewModelProviderFactory {
+ /**
+ * Returns the default {@link ViewModelProvider.Factory} that should be
+ * used when no custom {@code Factory} is provided to the
+ * {@link ViewModelProvider} constructors.
+ *
+ * @return a {@code ViewModelProvider.Factory}
+ */
+ @NonNull
+ ViewModelProvider.Factory getDefaultViewModelProviderFactory();
+}
diff --git a/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/ViewModel.java b/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/ViewModel.java
index 0709327..259430f 100644
--- a/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/ViewModel.java
+++ b/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/ViewModel.java
@@ -54,7 +54,7 @@
* protected void onCreate(Bundle savedInstanceState) {
* super.onCreate(savedInstanceState);
* setContentView(R.layout.user_activity_layout);
- * final UserModel viewModel = ViewModelProviders.of(this).get(UserModel.class);
+ * final UserModel viewModel = new ViewModelProvider(this).get(UserModel.class);
* viewModel.userLiveData.observer(this, new Observer<User>() {
* {@literal @}Override
* public void onChanged(@Nullable User data) {
@@ -99,7 +99,7 @@
* <pre>
* public class MyFragment extends Fragment {
* public void onStart() {
- * UserModel userModel = ViewModelProviders.of(getActivity()).get(UserModel.class);
+ * UserModel userModel = new ViewModelProvider(requireActivity()).get(UserModel.class);
* }
* }
* </pre>
diff --git a/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/ViewModelProvider.java b/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/ViewModelProvider.java
index 56d9f1b..e66da15 100644
--- a/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/ViewModelProvider.java
+++ b/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/ViewModelProvider.java
@@ -27,7 +27,7 @@
* An utility class that provides {@code ViewModels} for a scope.
* <p>
* Default {@code ViewModelProvider} for an {@code Activity} or a {@code Fragment} can be obtained
- * from {@link androidx.lifecycle.ViewModelProviders} class.
+ * by passing it to {@link ViewModelProvider#ViewModelProvider(ViewModelStoreOwner)}.
*/
@SuppressWarnings("WeakerAccess")
public class ViewModelProvider {
@@ -82,6 +82,21 @@
private final ViewModelStore mViewModelStore;
/**
+ * Creates {@code ViewModelProvider}. This will create {@code ViewModels}
+ * and retain them in a store of the given {@code ViewModelStoreOwner}.
+ * <p>
+ * This method will use the
+ * {@link HasDefaultViewModelProviderFactory#getDefaultViewModelProviderFactory() default factory}
+ * if the owner implements {@link HasDefaultViewModelProviderFactory}. Otherwise, a
+ * {@link NewInstanceFactory} will be used.
+ */
+ public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
+ this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
+ ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
+ : NewInstanceFactory.getInstance());
+ }
+
+ /**
* Creates {@code ViewModelProvider}, which will create {@code ViewModels} via the given
* {@code Factory} and retain them in a store of the given {@code ViewModelStoreOwner}.
*
@@ -172,6 +187,21 @@
*/
public static class NewInstanceFactory implements Factory {
+ private static NewInstanceFactory sInstance;
+
+ /**
+ * Retrieve a singleton instance of NewInstanceFactory.
+ *
+ * @return A valid {@link NewInstanceFactory}
+ */
+ @NonNull
+ static NewInstanceFactory getInstance() {
+ if (sInstance == null) {
+ sInstance = new NewInstanceFactory();
+ }
+ return sInstance;
+ }
+
@SuppressWarnings("ClassNewInstance")
@NonNull
@Override
diff --git a/lifecycle/lifecycle-viewmodel/src/test/java/androidx/lifecycle/ViewModelProviderTest.java b/lifecycle/lifecycle-viewmodel/src/test/java/androidx/lifecycle/ViewModelProviderTest.java
index dd9470f..0d5783c 100644
--- a/lifecycle/lifecycle-viewmodel/src/test/java/androidx/lifecycle/ViewModelProviderTest.java
+++ b/lifecycle/lifecycle-viewmodel/src/test/java/androidx/lifecycle/ViewModelProviderTest.java
@@ -85,6 +85,17 @@
}
@Test
+ public void testCustomDefaultFactory() {
+ final ViewModelStore store = new ViewModelStore();
+ final CountingFactory factory = new CountingFactory();
+ ViewModelStoreOwnerWithFactory owner = new ViewModelStoreOwnerWithFactory(store, factory);
+ ViewModelProvider provider = new ViewModelProvider(owner);
+ ViewModel1 viewModel = provider.get(ViewModel1.class);
+ assertThat(viewModel, is(provider.get(ViewModel1.class)));
+ assertThat(factory.mCalled, is(1));
+ }
+
+ @Test
public void testKeyedFactory() {
final ViewModelStore store = new ViewModelStore();
ViewModelStoreOwner owner = new ViewModelStoreOwner() {
@@ -107,6 +118,30 @@
provider.get("customkey", ViewModel1.class);
}
+ public static class ViewModelStoreOwnerWithFactory implements
+ ViewModelStoreOwner, HasDefaultViewModelProviderFactory {
+ private final ViewModelStore mStore;
+ private final ViewModelProvider.Factory mFactory;
+
+ ViewModelStoreOwnerWithFactory(@NonNull ViewModelStore store,
+ @NonNull ViewModelProvider.Factory factory) {
+ mStore = store;
+ mFactory = factory;
+ }
+
+ @NonNull
+ @Override
+ public ViewModelStore getViewModelStore() {
+ return mStore;
+ }
+
+ @NonNull
+ @Override
+ public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
+ return mFactory;
+ }
+ }
+
public static class ViewModel1 extends ViewModel {
boolean mCleared;
@@ -118,4 +153,15 @@
public static class ViewModel2 extends ViewModel {
}
+
+ public static class CountingFactory extends NewInstanceFactory {
+ int mCalled = 0;
+
+ @Override
+ @NonNull
+ public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
+ mCalled++;
+ return super.create(modelClass);
+ }
+ }
}
diff --git a/localbroadcastmanager/src/main/java/androidx/localbroadcastmanager/content/LocalBroadcastManager.java b/localbroadcastmanager/src/main/java/androidx/localbroadcastmanager/content/LocalBroadcastManager.java
index 04f2f7f..8323b05 100644
--- a/localbroadcastmanager/src/main/java/androidx/localbroadcastmanager/content/LocalBroadcastManager.java
+++ b/localbroadcastmanager/src/main/java/androidx/localbroadcastmanager/content/LocalBroadcastManager.java
@@ -212,7 +212,7 @@
* @see #registerReceiver
*
* @return Returns true if the intent has been scheduled for delivery to one or more
- * broadcast receivers. (Note tha delivery may not ultimately take place if one of those
+ * broadcast receivers. (Note that delivery may not ultimately take place if one of those
* receivers is unregistered before it is dispatched.)
*/
public boolean sendBroadcast(@NonNull Intent intent) {
diff --git a/media/api/restricted_1.1.0-rc01.txt b/media/api/restricted_1.1.0-rc01.txt
new file mode 100644
index 0000000..b48a150
--- /dev/null
+++ b/media/api/restricted_1.1.0-rc01.txt
@@ -0,0 +1,702 @@
+// Signature format: 3.0
+package android.support.v4.media {
+
+ public final class MediaBrowserCompat {
+ ctor public MediaBrowserCompat(android.content.Context!, android.content.ComponentName!, android.support.v4.media.MediaBrowserCompat.ConnectionCallback!, android.os.Bundle!);
+ method public void connect();
+ method public void disconnect();
+ method public android.os.Bundle? getExtras();
+ method public void getItem(String, android.support.v4.media.MediaBrowserCompat.ItemCallback);
+ method public String getRoot();
+ method public android.content.ComponentName getServiceComponent();
+ method public android.support.v4.media.session.MediaSessionCompat.Token getSessionToken();
+ method public boolean isConnected();
+ method public void search(String, android.os.Bundle!, android.support.v4.media.MediaBrowserCompat.SearchCallback);
+ method public void sendCustomAction(String, android.os.Bundle!, android.support.v4.media.MediaBrowserCompat.CustomActionCallback?);
+ method public void subscribe(String, android.support.v4.media.MediaBrowserCompat.SubscriptionCallback);
+ method public void subscribe(String, android.os.Bundle, android.support.v4.media.MediaBrowserCompat.SubscriptionCallback);
+ method public void unsubscribe(String);
+ method public void unsubscribe(String, android.support.v4.media.MediaBrowserCompat.SubscriptionCallback);
+ field public static final String CUSTOM_ACTION_DOWNLOAD = "android.support.v4.media.action.DOWNLOAD";
+ field public static final String CUSTOM_ACTION_REMOVE_DOWNLOADED_FILE = "android.support.v4.media.action.REMOVE_DOWNLOADED_FILE";
+ field public static final String EXTRA_DOWNLOAD_PROGRESS = "android.media.browse.extra.DOWNLOAD_PROGRESS";
+ field public static final String EXTRA_MEDIA_ID = "android.media.browse.extra.MEDIA_ID";
+ field public static final String EXTRA_PAGE = "android.media.browse.extra.PAGE";
+ field public static final String EXTRA_PAGE_SIZE = "android.media.browse.extra.PAGE_SIZE";
+ }
+
+ public static class MediaBrowserCompat.ConnectionCallback {
+ ctor public MediaBrowserCompat.ConnectionCallback();
+ method public void onConnected();
+ method public void onConnectionFailed();
+ method public void onConnectionSuspended();
+ }
+
+ public abstract static class MediaBrowserCompat.CustomActionCallback {
+ ctor public MediaBrowserCompat.CustomActionCallback();
+ method public void onError(String!, android.os.Bundle!, android.os.Bundle!);
+ method public void onProgressUpdate(String!, android.os.Bundle!, android.os.Bundle!);
+ method public void onResult(String!, android.os.Bundle!, android.os.Bundle!);
+ }
+
+ public abstract static class MediaBrowserCompat.ItemCallback {
+ ctor public MediaBrowserCompat.ItemCallback();
+ method public void onError(String);
+ method public void onItemLoaded(android.support.v4.media.MediaBrowserCompat.MediaItem!);
+ }
+
+ public static class MediaBrowserCompat.MediaItem implements android.os.Parcelable {
+ ctor public MediaBrowserCompat.MediaItem(android.support.v4.media.MediaDescriptionCompat, int);
+ method public int describeContents();
+ method public static android.support.v4.media.MediaBrowserCompat.MediaItem! fromMediaItem(Object!);
+ method public static java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem!>! fromMediaItemList(java.util.List<?>!);
+ method public android.support.v4.media.MediaDescriptionCompat getDescription();
+ method public int getFlags();
+ method public String? getMediaId();
+ method public boolean isBrowsable();
+ method public boolean isPlayable();
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.MediaBrowserCompat.MediaItem!>! CREATOR;
+ field public static final int FLAG_BROWSABLE = 1; // 0x1
+ field public static final int FLAG_PLAYABLE = 2; // 0x2
+ }
+
+ public abstract static class MediaBrowserCompat.SearchCallback {
+ ctor public MediaBrowserCompat.SearchCallback();
+ method public void onError(String, android.os.Bundle!);
+ method public void onSearchResult(String, android.os.Bundle!, java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem!>);
+ }
+
+ public abstract static class MediaBrowserCompat.SubscriptionCallback {
+ ctor public MediaBrowserCompat.SubscriptionCallback();
+ method public void onChildrenLoaded(String, java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem!>);
+ method public void onChildrenLoaded(String, java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem!>, android.os.Bundle);
+ method public void onError(String);
+ method public void onError(String, android.os.Bundle);
+ }
+
+ public final class MediaDescriptionCompat implements android.os.Parcelable {
+ method public int describeContents();
+ method public static android.support.v4.media.MediaDescriptionCompat! fromMediaDescription(Object!);
+ method public CharSequence? getDescription();
+ method public android.os.Bundle? getExtras();
+ method public android.graphics.Bitmap? getIconBitmap();
+ method public android.net.Uri? getIconUri();
+ method public Object! getMediaDescription();
+ method public String? getMediaId();
+ method public android.net.Uri? getMediaUri();
+ method public CharSequence? getSubtitle();
+ method public CharSequence? getTitle();
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final long BT_FOLDER_TYPE_ALBUMS = 2L; // 0x2L
+ field public static final long BT_FOLDER_TYPE_ARTISTS = 3L; // 0x3L
+ field public static final long BT_FOLDER_TYPE_GENRES = 4L; // 0x4L
+ field public static final long BT_FOLDER_TYPE_MIXED = 0L; // 0x0L
+ field public static final long BT_FOLDER_TYPE_PLAYLISTS = 5L; // 0x5L
+ field public static final long BT_FOLDER_TYPE_TITLES = 1L; // 0x1L
+ field public static final long BT_FOLDER_TYPE_YEARS = 6L; // 0x6L
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.MediaDescriptionCompat!>! CREATOR;
+ field public static final String EXTRA_BT_FOLDER_TYPE = "android.media.extra.BT_FOLDER_TYPE";
+ field public static final String EXTRA_DOWNLOAD_STATUS = "android.media.extra.DOWNLOAD_STATUS";
+ field public static final long STATUS_DOWNLOADED = 2L; // 0x2L
+ field public static final long STATUS_DOWNLOADING = 1L; // 0x1L
+ field public static final long STATUS_NOT_DOWNLOADED = 0L; // 0x0L
+ }
+
+ public static final class MediaDescriptionCompat.Builder {
+ ctor public MediaDescriptionCompat.Builder();
+ method public android.support.v4.media.MediaDescriptionCompat! build();
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setDescription(CharSequence?);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setExtras(android.os.Bundle?);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setIconBitmap(android.graphics.Bitmap?);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setIconUri(android.net.Uri?);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setMediaId(String?);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setMediaUri(android.net.Uri?);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setSubtitle(CharSequence?);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setTitle(CharSequence?);
+ }
+
+ public final class MediaMetadataCompat implements android.os.Parcelable {
+ method public boolean containsKey(String!);
+ method public int describeContents();
+ method public static android.support.v4.media.MediaMetadataCompat! fromMediaMetadata(Object!);
+ method public android.graphics.Bitmap! getBitmap(String!);
+ method public android.os.Bundle! getBundle();
+ method public android.support.v4.media.MediaDescriptionCompat! getDescription();
+ method public long getLong(String!);
+ method public Object! getMediaMetadata();
+ method public android.support.v4.media.RatingCompat! getRating(String!);
+ method public String! getString(String!);
+ method public CharSequence! getText(String!);
+ method public java.util.Set<java.lang.String!>! keySet();
+ method public int size();
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.MediaMetadataCompat!>! CREATOR;
+ field public static final String METADATA_KEY_ADVERTISEMENT = "android.media.metadata.ADVERTISEMENT";
+ field public static final String METADATA_KEY_ALBUM = "android.media.metadata.ALBUM";
+ field public static final String METADATA_KEY_ALBUM_ART = "android.media.metadata.ALBUM_ART";
+ field public static final String METADATA_KEY_ALBUM_ARTIST = "android.media.metadata.ALBUM_ARTIST";
+ field public static final String METADATA_KEY_ALBUM_ART_URI = "android.media.metadata.ALBUM_ART_URI";
+ field public static final String METADATA_KEY_ART = "android.media.metadata.ART";
+ field public static final String METADATA_KEY_ARTIST = "android.media.metadata.ARTIST";
+ field public static final String METADATA_KEY_ART_URI = "android.media.metadata.ART_URI";
+ field public static final String METADATA_KEY_AUTHOR = "android.media.metadata.AUTHOR";
+ field public static final String METADATA_KEY_BT_FOLDER_TYPE = "android.media.metadata.BT_FOLDER_TYPE";
+ field public static final String METADATA_KEY_COMPILATION = "android.media.metadata.COMPILATION";
+ field public static final String METADATA_KEY_COMPOSER = "android.media.metadata.COMPOSER";
+ field public static final String METADATA_KEY_DATE = "android.media.metadata.DATE";
+ field public static final String METADATA_KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER";
+ field public static final String METADATA_KEY_DISPLAY_DESCRIPTION = "android.media.metadata.DISPLAY_DESCRIPTION";
+ field public static final String METADATA_KEY_DISPLAY_ICON = "android.media.metadata.DISPLAY_ICON";
+ field public static final String METADATA_KEY_DISPLAY_ICON_URI = "android.media.metadata.DISPLAY_ICON_URI";
+ field public static final String METADATA_KEY_DISPLAY_SUBTITLE = "android.media.metadata.DISPLAY_SUBTITLE";
+ field public static final String METADATA_KEY_DISPLAY_TITLE = "android.media.metadata.DISPLAY_TITLE";
+ field public static final String METADATA_KEY_DOWNLOAD_STATUS = "android.media.metadata.DOWNLOAD_STATUS";
+ field public static final String METADATA_KEY_DURATION = "android.media.metadata.DURATION";
+ field public static final String METADATA_KEY_GENRE = "android.media.metadata.GENRE";
+ field public static final String METADATA_KEY_MEDIA_ID = "android.media.metadata.MEDIA_ID";
+ field public static final String METADATA_KEY_MEDIA_URI = "android.media.metadata.MEDIA_URI";
+ field public static final String METADATA_KEY_NUM_TRACKS = "android.media.metadata.NUM_TRACKS";
+ field public static final String METADATA_KEY_RATING = "android.media.metadata.RATING";
+ field public static final String METADATA_KEY_TITLE = "android.media.metadata.TITLE";
+ field public static final String METADATA_KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER";
+ field public static final String METADATA_KEY_USER_RATING = "android.media.metadata.USER_RATING";
+ field public static final String METADATA_KEY_WRITER = "android.media.metadata.WRITER";
+ field public static final String METADATA_KEY_YEAR = "android.media.metadata.YEAR";
+ }
+
+ public static final class MediaMetadataCompat.Builder {
+ ctor public MediaMetadataCompat.Builder();
+ ctor public MediaMetadataCompat.Builder(android.support.v4.media.MediaMetadataCompat!);
+ method public android.support.v4.media.MediaMetadataCompat! build();
+ method public android.support.v4.media.MediaMetadataCompat.Builder! putBitmap(String!, android.graphics.Bitmap!);
+ method public android.support.v4.media.MediaMetadataCompat.Builder! putLong(String!, long);
+ method public android.support.v4.media.MediaMetadataCompat.Builder! putRating(String!, android.support.v4.media.RatingCompat!);
+ method public android.support.v4.media.MediaMetadataCompat.Builder! putString(String!, String!);
+ method public android.support.v4.media.MediaMetadataCompat.Builder! putText(String!, CharSequence!);
+ }
+
+ public final class RatingCompat implements android.os.Parcelable {
+ method public int describeContents();
+ method public static android.support.v4.media.RatingCompat! fromRating(Object!);
+ method public float getPercentRating();
+ method public Object! getRating();
+ method public int getRatingStyle();
+ method public float getStarRating();
+ method public boolean hasHeart();
+ method public boolean isRated();
+ method public boolean isThumbUp();
+ method public static android.support.v4.media.RatingCompat! newHeartRating(boolean);
+ method public static android.support.v4.media.RatingCompat! newPercentageRating(float);
+ method public static android.support.v4.media.RatingCompat! newStarRating(int, float);
+ method public static android.support.v4.media.RatingCompat! newThumbRating(boolean);
+ method public static android.support.v4.media.RatingCompat! newUnratedRating(int);
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.RatingCompat!>! CREATOR;
+ field public static final int RATING_3_STARS = 3; // 0x3
+ field public static final int RATING_4_STARS = 4; // 0x4
+ field public static final int RATING_5_STARS = 5; // 0x5
+ field public static final int RATING_HEART = 1; // 0x1
+ field public static final int RATING_NONE = 0; // 0x0
+ field public static final int RATING_PERCENTAGE = 6; // 0x6
+ field public static final int RATING_THUMB_UP_DOWN = 2; // 0x2
+ }
+
+}
+
+package android.support.v4.media.session {
+
+ public final class MediaControllerCompat {
+ ctor public MediaControllerCompat(android.content.Context!, android.support.v4.media.session.MediaSessionCompat);
+ ctor public MediaControllerCompat(android.content.Context!, android.support.v4.media.session.MediaSessionCompat.Token) throws android.os.RemoteException;
+ method public void addQueueItem(android.support.v4.media.MediaDescriptionCompat!);
+ method public void addQueueItem(android.support.v4.media.MediaDescriptionCompat!, int);
+ method public void adjustVolume(int, int);
+ method public boolean dispatchMediaButtonEvent(android.view.KeyEvent!);
+ method public android.os.Bundle! getExtras();
+ method public long getFlags();
+ method public static android.support.v4.media.session.MediaControllerCompat! getMediaController(android.app.Activity);
+ method public Object! getMediaController();
+ method public android.support.v4.media.MediaMetadataCompat! getMetadata();
+ method public String! getPackageName();
+ method public android.support.v4.media.session.MediaControllerCompat.PlaybackInfo! getPlaybackInfo();
+ method public android.support.v4.media.session.PlaybackStateCompat! getPlaybackState();
+ method public java.util.List<android.support.v4.media.session.MediaSessionCompat.QueueItem!>! getQueue();
+ method public CharSequence! getQueueTitle();
+ method public int getRatingType();
+ method public int getRepeatMode();
+ method public android.app.PendingIntent! getSessionActivity();
+ method public android.support.v4.media.session.MediaSessionCompat.Token! getSessionToken();
+ method public int getShuffleMode();
+ method public android.support.v4.media.session.MediaControllerCompat.TransportControls! getTransportControls();
+ method public boolean isCaptioningEnabled();
+ method public boolean isSessionReady();
+ method public void registerCallback(android.support.v4.media.session.MediaControllerCompat.Callback);
+ method public void registerCallback(android.support.v4.media.session.MediaControllerCompat.Callback, android.os.Handler!);
+ method public void removeQueueItem(android.support.v4.media.MediaDescriptionCompat!);
+ method @Deprecated public void removeQueueItemAt(int);
+ method public void sendCommand(String, android.os.Bundle?, android.os.ResultReceiver?);
+ method public static void setMediaController(android.app.Activity, android.support.v4.media.session.MediaControllerCompat!);
+ method public void setVolumeTo(int, int);
+ method public void unregisterCallback(android.support.v4.media.session.MediaControllerCompat.Callback);
+ }
+
+ public abstract static class MediaControllerCompat.Callback implements android.os.IBinder.DeathRecipient {
+ ctor public MediaControllerCompat.Callback();
+ method public void binderDied();
+ method public void onAudioInfoChanged(android.support.v4.media.session.MediaControllerCompat.PlaybackInfo!);
+ method public void onCaptioningEnabledChanged(boolean);
+ method public void onExtrasChanged(android.os.Bundle!);
+ method public void onMetadataChanged(android.support.v4.media.MediaMetadataCompat!);
+ method public void onPlaybackStateChanged(android.support.v4.media.session.PlaybackStateCompat!);
+ method public void onQueueChanged(java.util.List<android.support.v4.media.session.MediaSessionCompat.QueueItem!>!);
+ method public void onQueueTitleChanged(CharSequence!);
+ method public void onRepeatModeChanged(int);
+ method public void onSessionDestroyed();
+ method public void onSessionEvent(String!, android.os.Bundle!);
+ method public void onSessionReady();
+ method public void onShuffleModeChanged(int);
+ }
+
+ public static final class MediaControllerCompat.PlaybackInfo {
+ method public androidx.media.AudioAttributesCompat getAudioAttributes();
+ method @Deprecated public int getAudioStream();
+ method public int getCurrentVolume();
+ method public int getMaxVolume();
+ method public int getPlaybackType();
+ method public int getVolumeControl();
+ field public static final int PLAYBACK_TYPE_LOCAL = 1; // 0x1
+ field public static final int PLAYBACK_TYPE_REMOTE = 2; // 0x2
+ }
+
+ public abstract static class MediaControllerCompat.TransportControls {
+ method public abstract void fastForward();
+ method public abstract void pause();
+ method public abstract void play();
+ method public abstract void playFromMediaId(String!, android.os.Bundle!);
+ method public abstract void playFromSearch(String!, android.os.Bundle!);
+ method public abstract void playFromUri(android.net.Uri!, android.os.Bundle!);
+ method public abstract void prepare();
+ method public abstract void prepareFromMediaId(String!, android.os.Bundle!);
+ method public abstract void prepareFromSearch(String!, android.os.Bundle!);
+ method public abstract void prepareFromUri(android.net.Uri!, android.os.Bundle!);
+ method public abstract void rewind();
+ method public abstract void seekTo(long);
+ method public abstract void sendCustomAction(android.support.v4.media.session.PlaybackStateCompat.CustomAction!, android.os.Bundle!);
+ method public abstract void sendCustomAction(String!, android.os.Bundle!);
+ method public abstract void setCaptioningEnabled(boolean);
+ method public abstract void setRating(android.support.v4.media.RatingCompat!);
+ method public abstract void setRating(android.support.v4.media.RatingCompat!, android.os.Bundle!);
+ method public abstract void setRepeatMode(int);
+ method public abstract void setShuffleMode(int);
+ method public abstract void skipToNext();
+ method public abstract void skipToPrevious();
+ method public abstract void skipToQueueItem(long);
+ method public abstract void stop();
+ field public static final String EXTRA_LEGACY_STREAM_TYPE = "android.media.session.extra.LEGACY_STREAM_TYPE";
+ }
+
+ public class MediaSessionCompat {
+ ctor public MediaSessionCompat(android.content.Context, String);
+ ctor public MediaSessionCompat(android.content.Context, String, android.content.ComponentName?, android.app.PendingIntent?);
+ method public void addOnActiveChangeListener(android.support.v4.media.session.MediaSessionCompat.OnActiveChangeListener!);
+ method public static android.support.v4.media.session.MediaSessionCompat! fromMediaSession(android.content.Context!, Object!);
+ method public android.support.v4.media.session.MediaControllerCompat! getController();
+ method public final androidx.media.MediaSessionManager.RemoteUserInfo getCurrentControllerInfo();
+ method public Object! getMediaSession();
+ method public Object! getRemoteControlClient();
+ method public android.support.v4.media.session.MediaSessionCompat.Token! getSessionToken();
+ method public boolean isActive();
+ method public void release();
+ method public void removeOnActiveChangeListener(android.support.v4.media.session.MediaSessionCompat.OnActiveChangeListener!);
+ method public void sendSessionEvent(String!, android.os.Bundle!);
+ method public void setActive(boolean);
+ method public void setCallback(android.support.v4.media.session.MediaSessionCompat.Callback!);
+ method public void setCallback(android.support.v4.media.session.MediaSessionCompat.Callback!, android.os.Handler!);
+ method public void setCaptioningEnabled(boolean);
+ method public void setExtras(android.os.Bundle!);
+ method public void setFlags(int);
+ method public void setMediaButtonReceiver(android.app.PendingIntent!);
+ method public void setMetadata(android.support.v4.media.MediaMetadataCompat!);
+ method public void setPlaybackState(android.support.v4.media.session.PlaybackStateCompat!);
+ method public void setPlaybackToLocal(int);
+ method public void setPlaybackToRemote(androidx.media.VolumeProviderCompat!);
+ method public void setQueue(java.util.List<android.support.v4.media.session.MediaSessionCompat.QueueItem!>!);
+ method public void setQueueTitle(CharSequence!);
+ method public void setRatingType(int);
+ method public void setRepeatMode(int);
+ method public void setSessionActivity(android.app.PendingIntent!);
+ method public void setShuffleMode(int);
+ field public static final String ACTION_FLAG_AS_INAPPROPRIATE = "android.support.v4.media.session.action.FLAG_AS_INAPPROPRIATE";
+ field public static final String ACTION_FOLLOW = "android.support.v4.media.session.action.FOLLOW";
+ field public static final String ACTION_SKIP_AD = "android.support.v4.media.session.action.SKIP_AD";
+ field public static final String ACTION_UNFOLLOW = "android.support.v4.media.session.action.UNFOLLOW";
+ field public static final String ARGUMENT_MEDIA_ATTRIBUTE = "android.support.v4.media.session.ARGUMENT_MEDIA_ATTRIBUTE";
+ field public static final String ARGUMENT_MEDIA_ATTRIBUTE_VALUE = "android.support.v4.media.session.ARGUMENT_MEDIA_ATTRIBUTE_VALUE";
+ field @Deprecated public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1; // 0x1
+ field public static final int FLAG_HANDLES_QUEUE_COMMANDS = 4; // 0x4
+ field @Deprecated public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 2; // 0x2
+ field public static final int MEDIA_ATTRIBUTE_ALBUM = 1; // 0x1
+ field public static final int MEDIA_ATTRIBUTE_ARTIST = 0; // 0x0
+ field public static final int MEDIA_ATTRIBUTE_PLAYLIST = 2; // 0x2
+ }
+
+ public abstract static class MediaSessionCompat.Callback {
+ ctor public MediaSessionCompat.Callback();
+ method public void onAddQueueItem(android.support.v4.media.MediaDescriptionCompat!);
+ method public void onAddQueueItem(android.support.v4.media.MediaDescriptionCompat!, int);
+ method public void onCommand(String!, android.os.Bundle!, android.os.ResultReceiver!);
+ method public void onCustomAction(String!, android.os.Bundle!);
+ method public void onFastForward();
+ method public boolean onMediaButtonEvent(android.content.Intent!);
+ method public void onPause();
+ method public void onPlay();
+ method public void onPlayFromMediaId(String!, android.os.Bundle!);
+ method public void onPlayFromSearch(String!, android.os.Bundle!);
+ method public void onPlayFromUri(android.net.Uri!, android.os.Bundle!);
+ method public void onPrepare();
+ method public void onPrepareFromMediaId(String!, android.os.Bundle!);
+ method public void onPrepareFromSearch(String!, android.os.Bundle!);
+ method public void onPrepareFromUri(android.net.Uri!, android.os.Bundle!);
+ method public void onRemoveQueueItem(android.support.v4.media.MediaDescriptionCompat!);
+ method @Deprecated public void onRemoveQueueItemAt(int);
+ method public void onRewind();
+ method public void onSeekTo(long);
+ method public void onSetCaptioningEnabled(boolean);
+ method public void onSetRating(android.support.v4.media.RatingCompat!);
+ method public void onSetRating(android.support.v4.media.RatingCompat!, android.os.Bundle!);
+ method public void onSetRepeatMode(int);
+ method public void onSetShuffleMode(int);
+ method public void onSkipToNext();
+ method public void onSkipToPrevious();
+ method public void onSkipToQueueItem(long);
+ method public void onStop();
+ }
+
+ public static interface MediaSessionCompat.OnActiveChangeListener {
+ method public void onActiveChanged();
+ }
+
+ public static final class MediaSessionCompat.QueueItem implements android.os.Parcelable {
+ ctor public MediaSessionCompat.QueueItem(android.support.v4.media.MediaDescriptionCompat!, long);
+ method public int describeContents();
+ method public static android.support.v4.media.session.MediaSessionCompat.QueueItem! fromQueueItem(Object!);
+ method public static java.util.List<android.support.v4.media.session.MediaSessionCompat.QueueItem!>! fromQueueItemList(java.util.List<?>!);
+ method public android.support.v4.media.MediaDescriptionCompat! getDescription();
+ method public long getQueueId();
+ method public Object! getQueueItem();
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.session.MediaSessionCompat.QueueItem!>! CREATOR;
+ field public static final int UNKNOWN_ID = -1; // 0xffffffff
+ }
+
+ public static final class MediaSessionCompat.Token implements android.os.Parcelable {
+ method public int describeContents();
+ method public static android.support.v4.media.session.MediaSessionCompat.Token! fromToken(Object!);
+ method public Object! getToken();
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.session.MediaSessionCompat.Token!>! CREATOR;
+ }
+
+ public class ParcelableVolumeInfo implements android.os.Parcelable {
+ ctor public ParcelableVolumeInfo(int, int, int, int, int);
+ ctor public ParcelableVolumeInfo(android.os.Parcel!);
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.session.ParcelableVolumeInfo!>! CREATOR;
+ field public int audioStream;
+ field public int controlType;
+ field public int currentVolume;
+ field public int maxVolume;
+ field public int volumeType;
+ }
+
+ public final class PlaybackStateCompat implements android.os.Parcelable {
+ method public int describeContents();
+ method public static android.support.v4.media.session.PlaybackStateCompat! fromPlaybackState(Object!);
+ method public long getActions();
+ method public long getActiveQueueItemId();
+ method public long getBufferedPosition();
+ method public java.util.List<android.support.v4.media.session.PlaybackStateCompat.CustomAction!>! getCustomActions();
+ method public int getErrorCode();
+ method public CharSequence! getErrorMessage();
+ method public android.os.Bundle? getExtras();
+ method public long getLastPositionUpdateTime();
+ method public float getPlaybackSpeed();
+ method public Object! getPlaybackState();
+ method public long getPosition();
+ method public int getState();
+ method public static int toKeyCode(long);
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final long ACTION_FAST_FORWARD = 64L; // 0x40L
+ field public static final long ACTION_PAUSE = 2L; // 0x2L
+ field public static final long ACTION_PLAY = 4L; // 0x4L
+ field public static final long ACTION_PLAY_FROM_MEDIA_ID = 1024L; // 0x400L
+ field public static final long ACTION_PLAY_FROM_SEARCH = 2048L; // 0x800L
+ field public static final long ACTION_PLAY_FROM_URI = 8192L; // 0x2000L
+ field public static final long ACTION_PLAY_PAUSE = 512L; // 0x200L
+ field public static final long ACTION_PREPARE = 16384L; // 0x4000L
+ field public static final long ACTION_PREPARE_FROM_MEDIA_ID = 32768L; // 0x8000L
+ field public static final long ACTION_PREPARE_FROM_SEARCH = 65536L; // 0x10000L
+ field public static final long ACTION_PREPARE_FROM_URI = 131072L; // 0x20000L
+ field public static final long ACTION_REWIND = 8L; // 0x8L
+ field public static final long ACTION_SEEK_TO = 256L; // 0x100L
+ field public static final long ACTION_SET_CAPTIONING_ENABLED = 1048576L; // 0x100000L
+ field public static final long ACTION_SET_RATING = 128L; // 0x80L
+ field public static final long ACTION_SET_REPEAT_MODE = 262144L; // 0x40000L
+ field public static final long ACTION_SET_SHUFFLE_MODE = 2097152L; // 0x200000L
+ field @Deprecated public static final long ACTION_SET_SHUFFLE_MODE_ENABLED = 524288L; // 0x80000L
+ field public static final long ACTION_SKIP_TO_NEXT = 32L; // 0x20L
+ field public static final long ACTION_SKIP_TO_PREVIOUS = 16L; // 0x10L
+ field public static final long ACTION_SKIP_TO_QUEUE_ITEM = 4096L; // 0x1000L
+ field public static final long ACTION_STOP = 1L; // 0x1L
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.session.PlaybackStateCompat!>! CREATOR;
+ field public static final int ERROR_CODE_ACTION_ABORTED = 10; // 0xa
+ field public static final int ERROR_CODE_APP_ERROR = 1; // 0x1
+ field public static final int ERROR_CODE_AUTHENTICATION_EXPIRED = 3; // 0x3
+ field public static final int ERROR_CODE_CONCURRENT_STREAM_LIMIT = 5; // 0x5
+ field public static final int ERROR_CODE_CONTENT_ALREADY_PLAYING = 8; // 0x8
+ field public static final int ERROR_CODE_END_OF_QUEUE = 11; // 0xb
+ field public static final int ERROR_CODE_NOT_AVAILABLE_IN_REGION = 7; // 0x7
+ field public static final int ERROR_CODE_NOT_SUPPORTED = 2; // 0x2
+ field public static final int ERROR_CODE_PARENTAL_CONTROL_RESTRICTED = 6; // 0x6
+ field public static final int ERROR_CODE_PREMIUM_ACCOUNT_REQUIRED = 4; // 0x4
+ field public static final int ERROR_CODE_SKIP_LIMIT_REACHED = 9; // 0x9
+ field public static final int ERROR_CODE_UNKNOWN_ERROR = 0; // 0x0
+ field public static final long PLAYBACK_POSITION_UNKNOWN = -1L; // 0xffffffffffffffffL
+ field public static final int REPEAT_MODE_ALL = 2; // 0x2
+ field public static final int REPEAT_MODE_GROUP = 3; // 0x3
+ field public static final int REPEAT_MODE_INVALID = -1; // 0xffffffff
+ field public static final int REPEAT_MODE_NONE = 0; // 0x0
+ field public static final int REPEAT_MODE_ONE = 1; // 0x1
+ field public static final int SHUFFLE_MODE_ALL = 1; // 0x1
+ field public static final int SHUFFLE_MODE_GROUP = 2; // 0x2
+ field public static final int SHUFFLE_MODE_INVALID = -1; // 0xffffffff
+ field public static final int SHUFFLE_MODE_NONE = 0; // 0x0
+ field public static final int STATE_BUFFERING = 6; // 0x6
+ field public static final int STATE_CONNECTING = 8; // 0x8
+ field public static final int STATE_ERROR = 7; // 0x7
+ field public static final int STATE_FAST_FORWARDING = 4; // 0x4
+ field public static final int STATE_NONE = 0; // 0x0
+ field public static final int STATE_PAUSED = 2; // 0x2
+ field public static final int STATE_PLAYING = 3; // 0x3
+ field public static final int STATE_REWINDING = 5; // 0x5
+ field public static final int STATE_SKIPPING_TO_NEXT = 10; // 0xa
+ field public static final int STATE_SKIPPING_TO_PREVIOUS = 9; // 0x9
+ field public static final int STATE_SKIPPING_TO_QUEUE_ITEM = 11; // 0xb
+ field public static final int STATE_STOPPED = 1; // 0x1
+ }
+
+ public static final class PlaybackStateCompat.Builder {
+ ctor public PlaybackStateCompat.Builder();
+ ctor public PlaybackStateCompat.Builder(android.support.v4.media.session.PlaybackStateCompat!);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! addCustomAction(String!, String!, int);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! addCustomAction(android.support.v4.media.session.PlaybackStateCompat.CustomAction!);
+ method public android.support.v4.media.session.PlaybackStateCompat! build();
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! setActions(long);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! setActiveQueueItemId(long);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! setBufferedPosition(long);
+ method @Deprecated public android.support.v4.media.session.PlaybackStateCompat.Builder! setErrorMessage(CharSequence!);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! setErrorMessage(int, CharSequence!);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! setExtras(android.os.Bundle!);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! setState(int, long, float);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! setState(int, long, float, long);
+ }
+
+ public static final class PlaybackStateCompat.CustomAction implements android.os.Parcelable {
+ method public int describeContents();
+ method public static android.support.v4.media.session.PlaybackStateCompat.CustomAction! fromCustomAction(Object!);
+ method public String! getAction();
+ method public Object! getCustomAction();
+ method public android.os.Bundle! getExtras();
+ method public int getIcon();
+ method public CharSequence! getName();
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.session.PlaybackStateCompat.CustomAction!>! CREATOR;
+ }
+
+ public static final class PlaybackStateCompat.CustomAction.Builder {
+ ctor public PlaybackStateCompat.CustomAction.Builder(String!, CharSequence!, int);
+ method public android.support.v4.media.session.PlaybackStateCompat.CustomAction! build();
+ method public android.support.v4.media.session.PlaybackStateCompat.CustomAction.Builder! setExtras(android.os.Bundle!);
+ }
+
+}
+
+package androidx.media {
+
+ public class AudioAttributesCompat implements androidx.versionedparcelable.VersionedParcelable {
+ method public int getContentType();
+ method public int getFlags();
+ method public int getLegacyStreamType();
+ method public int getUsage();
+ method public int getVolumeControlStream();
+ method public Object? unwrap();
+ method public static androidx.media.AudioAttributesCompat? wrap(Object);
+ field public static final int CONTENT_TYPE_MOVIE = 3; // 0x3
+ field public static final int CONTENT_TYPE_MUSIC = 2; // 0x2
+ field public static final int CONTENT_TYPE_SONIFICATION = 4; // 0x4
+ field public static final int CONTENT_TYPE_SPEECH = 1; // 0x1
+ field public static final int CONTENT_TYPE_UNKNOWN = 0; // 0x0
+ field public static final int FLAG_AUDIBILITY_ENFORCED = 1; // 0x1
+ field public static final int FLAG_HW_AV_SYNC = 16; // 0x10
+ field public static final int USAGE_ALARM = 4; // 0x4
+ field public static final int USAGE_ASSISTANCE_ACCESSIBILITY = 11; // 0xb
+ field public static final int USAGE_ASSISTANCE_NAVIGATION_GUIDANCE = 12; // 0xc
+ field public static final int USAGE_ASSISTANCE_SONIFICATION = 13; // 0xd
+ field public static final int USAGE_ASSISTANT = 16; // 0x10
+ field public static final int USAGE_GAME = 14; // 0xe
+ field public static final int USAGE_MEDIA = 1; // 0x1
+ field public static final int USAGE_NOTIFICATION = 5; // 0x5
+ field public static final int USAGE_NOTIFICATION_COMMUNICATION_DELAYED = 9; // 0x9
+ field public static final int USAGE_NOTIFICATION_COMMUNICATION_INSTANT = 8; // 0x8
+ field public static final int USAGE_NOTIFICATION_COMMUNICATION_REQUEST = 7; // 0x7
+ field public static final int USAGE_NOTIFICATION_EVENT = 10; // 0xa
+ field public static final int USAGE_NOTIFICATION_RINGTONE = 6; // 0x6
+ field public static final int USAGE_UNKNOWN = 0; // 0x0
+ field public static final int USAGE_VOICE_COMMUNICATION = 2; // 0x2
+ field public static final int USAGE_VOICE_COMMUNICATION_SIGNALLING = 3; // 0x3
+ }
+
+ public static class AudioAttributesCompat.Builder {
+ ctor public AudioAttributesCompat.Builder();
+ ctor public AudioAttributesCompat.Builder(androidx.media.AudioAttributesCompat!);
+ method public androidx.media.AudioAttributesCompat! build();
+ method public androidx.media.AudioAttributesCompat.Builder! setContentType(int);
+ method public androidx.media.AudioAttributesCompat.Builder! setFlags(int);
+ method public androidx.media.AudioAttributesCompat.Builder! setLegacyStreamType(int);
+ method public androidx.media.AudioAttributesCompat.Builder! setUsage(int);
+ }
+
+ public class AudioFocusRequestCompat {
+ method public androidx.media.AudioAttributesCompat getAudioAttributesCompat();
+ method public android.os.Handler getFocusChangeHandler();
+ method public int getFocusGain();
+ method public android.media.AudioManager.OnAudioFocusChangeListener getOnAudioFocusChangeListener();
+ method public boolean willPauseWhenDucked();
+ }
+
+ public static final class AudioFocusRequestCompat.Builder {
+ ctor public AudioFocusRequestCompat.Builder(int);
+ ctor public AudioFocusRequestCompat.Builder(androidx.media.AudioFocusRequestCompat);
+ method public androidx.media.AudioFocusRequestCompat! build();
+ method public androidx.media.AudioFocusRequestCompat.Builder setAudioAttributes(androidx.media.AudioAttributesCompat);
+ method public androidx.media.AudioFocusRequestCompat.Builder setFocusGain(int);
+ method public androidx.media.AudioFocusRequestCompat.Builder setOnAudioFocusChangeListener(android.media.AudioManager.OnAudioFocusChangeListener);
+ method public androidx.media.AudioFocusRequestCompat.Builder setOnAudioFocusChangeListener(android.media.AudioManager.OnAudioFocusChangeListener, android.os.Handler);
+ method public androidx.media.AudioFocusRequestCompat.Builder setWillPauseWhenDucked(boolean);
+ }
+
+ public final class AudioManagerCompat {
+ method public static int abandonAudioFocusRequest(android.media.AudioManager, androidx.media.AudioFocusRequestCompat);
+ method public static int requestAudioFocus(android.media.AudioManager, androidx.media.AudioFocusRequestCompat);
+ field public static final int AUDIOFOCUS_GAIN = 1; // 0x1
+ field public static final int AUDIOFOCUS_GAIN_TRANSIENT = 2; // 0x2
+ field public static final int AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE = 4; // 0x4
+ field public static final int AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK = 3; // 0x3
+ }
+
+ public abstract class MediaBrowserServiceCompat extends android.app.Service {
+ ctor public MediaBrowserServiceCompat();
+ method public void dump(java.io.FileDescriptor!, java.io.PrintWriter!, String![]!);
+ method public final android.os.Bundle! getBrowserRootHints();
+ method public final androidx.media.MediaSessionManager.RemoteUserInfo getCurrentBrowserInfo();
+ method public android.support.v4.media.session.MediaSessionCompat.Token? getSessionToken();
+ method public void notifyChildrenChanged(String);
+ method public void notifyChildrenChanged(String, android.os.Bundle);
+ method public android.os.IBinder! onBind(android.content.Intent!);
+ method public void onCustomAction(String, android.os.Bundle!, androidx.media.MediaBrowserServiceCompat.Result<android.os.Bundle!>);
+ method public abstract androidx.media.MediaBrowserServiceCompat.BrowserRoot? onGetRoot(String, int, android.os.Bundle?);
+ method public abstract void onLoadChildren(String, androidx.media.MediaBrowserServiceCompat.Result<java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem!>!>);
+ method public void onLoadChildren(String, androidx.media.MediaBrowserServiceCompat.Result<java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem!>!>, android.os.Bundle);
+ method public void onLoadItem(String!, androidx.media.MediaBrowserServiceCompat.Result<android.support.v4.media.MediaBrowserCompat.MediaItem!>);
+ method public void onSearch(String, android.os.Bundle!, androidx.media.MediaBrowserServiceCompat.Result<java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem!>!>);
+ method public void setSessionToken(android.support.v4.media.session.MediaSessionCompat.Token!);
+ field public static final String SERVICE_INTERFACE = "android.media.browse.MediaBrowserService";
+ }
+
+ public static final class MediaBrowserServiceCompat.BrowserRoot {
+ ctor public MediaBrowserServiceCompat.BrowserRoot(String, android.os.Bundle?);
+ method public android.os.Bundle! getExtras();
+ method public String! getRootId();
+ field public static final String EXTRA_OFFLINE = "android.service.media.extra.OFFLINE";
+ field public static final String EXTRA_RECENT = "android.service.media.extra.RECENT";
+ field public static final String EXTRA_SUGGESTED = "android.service.media.extra.SUGGESTED";
+ field @Deprecated public static final String EXTRA_SUGGESTION_KEYWORDS = "android.service.media.extra.SUGGESTION_KEYWORDS";
+ }
+
+ public static class MediaBrowserServiceCompat.Result<T> {
+ method public void detach();
+ method public void sendError(android.os.Bundle!);
+ method public void sendProgressUpdate(android.os.Bundle!);
+ method public void sendResult(T!);
+ }
+
+ public final class MediaSessionManager {
+ method public static androidx.media.MediaSessionManager getSessionManager(android.content.Context);
+ method public boolean isTrustedForMediaControl(androidx.media.MediaSessionManager.RemoteUserInfo);
+ }
+
+ public static final class MediaSessionManager.RemoteUserInfo {
+ ctor public MediaSessionManager.RemoteUserInfo(String, int, int);
+ method public String getPackageName();
+ method public int getPid();
+ method public int getUid();
+ field public static final String LEGACY_CONTROLLER = "android.media.session.MediaController";
+ }
+
+ public abstract class VolumeProviderCompat {
+ ctor public VolumeProviderCompat(int, int, int);
+ method public final int getCurrentVolume();
+ method public final int getMaxVolume();
+ method public final int getVolumeControl();
+ method public Object! getVolumeProvider();
+ method public void onAdjustVolume(int);
+ method public void onSetVolumeTo(int);
+ method public void setCallback(androidx.media.VolumeProviderCompat.Callback!);
+ method public final void setCurrentVolume(int);
+ field public static final int VOLUME_CONTROL_ABSOLUTE = 2; // 0x2
+ field public static final int VOLUME_CONTROL_FIXED = 0; // 0x0
+ field public static final int VOLUME_CONTROL_RELATIVE = 1; // 0x1
+ }
+
+ public abstract static class VolumeProviderCompat.Callback {
+ ctor public VolumeProviderCompat.Callback();
+ method public abstract void onVolumeChanged(androidx.media.VolumeProviderCompat!);
+ }
+
+}
+
+package androidx.media.app {
+
+ public class NotificationCompat {
+ }
+
+ public static class NotificationCompat.DecoratedMediaCustomViewStyle extends androidx.media.app.NotificationCompat.MediaStyle {
+ ctor public NotificationCompat.DecoratedMediaCustomViewStyle();
+ }
+
+ public static class NotificationCompat.MediaStyle extends androidx.core.app.NotificationCompat.Style {
+ ctor public NotificationCompat.MediaStyle();
+ ctor public NotificationCompat.MediaStyle(androidx.core.app.NotificationCompat.Builder!);
+ method public static android.support.v4.media.session.MediaSessionCompat.Token! getMediaSession(android.app.Notification!);
+ method public androidx.media.app.NotificationCompat.MediaStyle! setCancelButtonIntent(android.app.PendingIntent!);
+ method public androidx.media.app.NotificationCompat.MediaStyle! setMediaSession(android.support.v4.media.session.MediaSessionCompat.Token!);
+ method public androidx.media.app.NotificationCompat.MediaStyle! setShowActionsInCompactView(int...);
+ method public androidx.media.app.NotificationCompat.MediaStyle! setShowCancelButton(boolean);
+ }
+
+}
+
+package androidx.media.session {
+
+ public class MediaButtonReceiver extends android.content.BroadcastReceiver {
+ ctor public MediaButtonReceiver();
+ method public static android.app.PendingIntent! buildMediaButtonPendingIntent(android.content.Context!, long);
+ method public static android.app.PendingIntent! buildMediaButtonPendingIntent(android.content.Context!, android.content.ComponentName!, long);
+ method public static android.view.KeyEvent! handleIntent(android.support.v4.media.session.MediaSessionCompat!, android.content.Intent!);
+ method public void onReceive(android.content.Context!, android.content.Intent!);
+ }
+
+}
+
diff --git a/media/api/restricted_1.2.0-alpha01.txt b/media/api/restricted_1.2.0-alpha01.txt
new file mode 100644
index 0000000..9c1f0db
--- /dev/null
+++ b/media/api/restricted_1.2.0-alpha01.txt
@@ -0,0 +1,743 @@
+// Signature format: 3.0
+package android.support.v4.media {
+
+ public final class MediaBrowserCompat {
+ ctor public MediaBrowserCompat(android.content.Context!, android.content.ComponentName!, android.support.v4.media.MediaBrowserCompat.ConnectionCallback!, android.os.Bundle!);
+ method public void connect();
+ method public void disconnect();
+ method public android.os.Bundle? getExtras();
+ method public void getItem(String, android.support.v4.media.MediaBrowserCompat.ItemCallback);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.os.Bundle? getNotifyChildrenChangedOptions();
+ method public String getRoot();
+ method public android.content.ComponentName getServiceComponent();
+ method public android.support.v4.media.session.MediaSessionCompat.Token getSessionToken();
+ method public boolean isConnected();
+ method public void search(String, android.os.Bundle!, android.support.v4.media.MediaBrowserCompat.SearchCallback);
+ method public void sendCustomAction(String, android.os.Bundle!, android.support.v4.media.MediaBrowserCompat.CustomActionCallback?);
+ method public void subscribe(String, android.support.v4.media.MediaBrowserCompat.SubscriptionCallback);
+ method public void subscribe(String, android.os.Bundle, android.support.v4.media.MediaBrowserCompat.SubscriptionCallback);
+ method public void unsubscribe(String);
+ method public void unsubscribe(String, android.support.v4.media.MediaBrowserCompat.SubscriptionCallback);
+ field public static final String CUSTOM_ACTION_DOWNLOAD = "android.support.v4.media.action.DOWNLOAD";
+ field public static final String CUSTOM_ACTION_REMOVE_DOWNLOADED_FILE = "android.support.v4.media.action.REMOVE_DOWNLOADED_FILE";
+ field public static final String EXTRA_DOWNLOAD_PROGRESS = "android.media.browse.extra.DOWNLOAD_PROGRESS";
+ field public static final String EXTRA_MEDIA_ID = "android.media.browse.extra.MEDIA_ID";
+ field public static final String EXTRA_PAGE = "android.media.browse.extra.PAGE";
+ field public static final String EXTRA_PAGE_SIZE = "android.media.browse.extra.PAGE_SIZE";
+ }
+
+ public static class MediaBrowserCompat.ConnectionCallback {
+ ctor public MediaBrowserCompat.ConnectionCallback();
+ method public void onConnected();
+ method public void onConnectionFailed();
+ method public void onConnectionSuspended();
+ }
+
+ public abstract static class MediaBrowserCompat.CustomActionCallback {
+ ctor public MediaBrowserCompat.CustomActionCallback();
+ method public void onError(String!, android.os.Bundle!, android.os.Bundle!);
+ method public void onProgressUpdate(String!, android.os.Bundle!, android.os.Bundle!);
+ method public void onResult(String!, android.os.Bundle!, android.os.Bundle!);
+ }
+
+ public abstract static class MediaBrowserCompat.ItemCallback {
+ ctor public MediaBrowserCompat.ItemCallback();
+ method public void onError(String);
+ method public void onItemLoaded(android.support.v4.media.MediaBrowserCompat.MediaItem!);
+ }
+
+ public static class MediaBrowserCompat.MediaItem implements android.os.Parcelable {
+ ctor public MediaBrowserCompat.MediaItem(android.support.v4.media.MediaDescriptionCompat, int);
+ method public int describeContents();
+ method public static android.support.v4.media.MediaBrowserCompat.MediaItem! fromMediaItem(Object!);
+ method public static java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem!>! fromMediaItemList(java.util.List<?>!);
+ method public android.support.v4.media.MediaDescriptionCompat getDescription();
+ method public int getFlags();
+ method public String? getMediaId();
+ method public boolean isBrowsable();
+ method public boolean isPlayable();
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.MediaBrowserCompat.MediaItem!>! CREATOR;
+ field public static final int FLAG_BROWSABLE = 1; // 0x1
+ field public static final int FLAG_PLAYABLE = 2; // 0x2
+ }
+
+ public abstract static class MediaBrowserCompat.SearchCallback {
+ ctor public MediaBrowserCompat.SearchCallback();
+ method public void onError(String, android.os.Bundle!);
+ method public void onSearchResult(String, android.os.Bundle!, java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem!>);
+ }
+
+ public abstract static class MediaBrowserCompat.SubscriptionCallback {
+ ctor public MediaBrowserCompat.SubscriptionCallback();
+ method public void onChildrenLoaded(String, java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem!>);
+ method public void onChildrenLoaded(String, java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem!>, android.os.Bundle);
+ method public void onError(String);
+ method public void onError(String, android.os.Bundle);
+ }
+
+ public final class MediaDescriptionCompat implements android.os.Parcelable {
+ method public int describeContents();
+ method public static android.support.v4.media.MediaDescriptionCompat! fromMediaDescription(Object!);
+ method public CharSequence? getDescription();
+ method public android.os.Bundle? getExtras();
+ method public android.graphics.Bitmap? getIconBitmap();
+ method public android.net.Uri? getIconUri();
+ method public Object! getMediaDescription();
+ method public String? getMediaId();
+ method public android.net.Uri? getMediaUri();
+ method public CharSequence? getSubtitle();
+ method public CharSequence? getTitle();
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final long BT_FOLDER_TYPE_ALBUMS = 2L; // 0x2L
+ field public static final long BT_FOLDER_TYPE_ARTISTS = 3L; // 0x3L
+ field public static final long BT_FOLDER_TYPE_GENRES = 4L; // 0x4L
+ field public static final long BT_FOLDER_TYPE_MIXED = 0L; // 0x0L
+ field public static final long BT_FOLDER_TYPE_PLAYLISTS = 5L; // 0x5L
+ field public static final long BT_FOLDER_TYPE_TITLES = 1L; // 0x1L
+ field public static final long BT_FOLDER_TYPE_YEARS = 6L; // 0x6L
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.MediaDescriptionCompat!>! CREATOR;
+ field public static final String EXTRA_BT_FOLDER_TYPE = "android.media.extra.BT_FOLDER_TYPE";
+ field public static final String EXTRA_DOWNLOAD_STATUS = "android.media.extra.DOWNLOAD_STATUS";
+ field public static final long STATUS_DOWNLOADED = 2L; // 0x2L
+ field public static final long STATUS_DOWNLOADING = 1L; // 0x1L
+ field public static final long STATUS_NOT_DOWNLOADED = 0L; // 0x0L
+ }
+
+ public static final class MediaDescriptionCompat.Builder {
+ ctor public MediaDescriptionCompat.Builder();
+ method public android.support.v4.media.MediaDescriptionCompat! build();
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setDescription(CharSequence?);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setExtras(android.os.Bundle?);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setIconBitmap(android.graphics.Bitmap?);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setIconUri(android.net.Uri?);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setMediaId(String?);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setMediaUri(android.net.Uri?);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setSubtitle(CharSequence?);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setTitle(CharSequence?);
+ }
+
+ public final class MediaMetadataCompat implements android.os.Parcelable {
+ method public boolean containsKey(String!);
+ method public int describeContents();
+ method public static android.support.v4.media.MediaMetadataCompat! fromMediaMetadata(Object!);
+ method public android.graphics.Bitmap! getBitmap(String!);
+ method public android.os.Bundle! getBundle();
+ method public android.support.v4.media.MediaDescriptionCompat! getDescription();
+ method public long getLong(String!);
+ method public Object! getMediaMetadata();
+ method public android.support.v4.media.RatingCompat! getRating(String!);
+ method public String! getString(String!);
+ method public CharSequence! getText(String!);
+ method public java.util.Set<java.lang.String!>! keySet();
+ method public int size();
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.MediaMetadataCompat!>! CREATOR;
+ field public static final String METADATA_KEY_ADVERTISEMENT = "android.media.metadata.ADVERTISEMENT";
+ field public static final String METADATA_KEY_ALBUM = "android.media.metadata.ALBUM";
+ field public static final String METADATA_KEY_ALBUM_ART = "android.media.metadata.ALBUM_ART";
+ field public static final String METADATA_KEY_ALBUM_ARTIST = "android.media.metadata.ALBUM_ARTIST";
+ field public static final String METADATA_KEY_ALBUM_ART_URI = "android.media.metadata.ALBUM_ART_URI";
+ field public static final String METADATA_KEY_ART = "android.media.metadata.ART";
+ field public static final String METADATA_KEY_ARTIST = "android.media.metadata.ARTIST";
+ field public static final String METADATA_KEY_ART_URI = "android.media.metadata.ART_URI";
+ field public static final String METADATA_KEY_AUTHOR = "android.media.metadata.AUTHOR";
+ field public static final String METADATA_KEY_BT_FOLDER_TYPE = "android.media.metadata.BT_FOLDER_TYPE";
+ field public static final String METADATA_KEY_COMPILATION = "android.media.metadata.COMPILATION";
+ field public static final String METADATA_KEY_COMPOSER = "android.media.metadata.COMPOSER";
+ field public static final String METADATA_KEY_DATE = "android.media.metadata.DATE";
+ field public static final String METADATA_KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER";
+ field public static final String METADATA_KEY_DISPLAY_DESCRIPTION = "android.media.metadata.DISPLAY_DESCRIPTION";
+ field public static final String METADATA_KEY_DISPLAY_ICON = "android.media.metadata.DISPLAY_ICON";
+ field public static final String METADATA_KEY_DISPLAY_ICON_URI = "android.media.metadata.DISPLAY_ICON_URI";
+ field public static final String METADATA_KEY_DISPLAY_SUBTITLE = "android.media.metadata.DISPLAY_SUBTITLE";
+ field public static final String METADATA_KEY_DISPLAY_TITLE = "android.media.metadata.DISPLAY_TITLE";
+ field public static final String METADATA_KEY_DOWNLOAD_STATUS = "android.media.metadata.DOWNLOAD_STATUS";
+ field public static final String METADATA_KEY_DURATION = "android.media.metadata.DURATION";
+ field public static final String METADATA_KEY_GENRE = "android.media.metadata.GENRE";
+ field public static final String METADATA_KEY_MEDIA_ID = "android.media.metadata.MEDIA_ID";
+ field public static final String METADATA_KEY_MEDIA_URI = "android.media.metadata.MEDIA_URI";
+ field public static final String METADATA_KEY_NUM_TRACKS = "android.media.metadata.NUM_TRACKS";
+ field public static final String METADATA_KEY_RATING = "android.media.metadata.RATING";
+ field public static final String METADATA_KEY_TITLE = "android.media.metadata.TITLE";
+ field public static final String METADATA_KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER";
+ field public static final String METADATA_KEY_USER_RATING = "android.media.metadata.USER_RATING";
+ field public static final String METADATA_KEY_WRITER = "android.media.metadata.WRITER";
+ field public static final String METADATA_KEY_YEAR = "android.media.metadata.YEAR";
+ }
+
+ public static final class MediaMetadataCompat.Builder {
+ ctor public MediaMetadataCompat.Builder();
+ ctor public MediaMetadataCompat.Builder(android.support.v4.media.MediaMetadataCompat!);
+ method public android.support.v4.media.MediaMetadataCompat! build();
+ method public android.support.v4.media.MediaMetadataCompat.Builder! putBitmap(String!, android.graphics.Bitmap!);
+ method public android.support.v4.media.MediaMetadataCompat.Builder! putLong(String!, long);
+ method public android.support.v4.media.MediaMetadataCompat.Builder! putRating(String!, android.support.v4.media.RatingCompat!);
+ method public android.support.v4.media.MediaMetadataCompat.Builder! putString(String!, String!);
+ method public android.support.v4.media.MediaMetadataCompat.Builder! putText(String!, CharSequence!);
+ }
+
+ public final class RatingCompat implements android.os.Parcelable {
+ method public int describeContents();
+ method public static android.support.v4.media.RatingCompat! fromRating(Object!);
+ method public float getPercentRating();
+ method public Object! getRating();
+ method @android.support.v4.media.RatingCompat.Style public int getRatingStyle();
+ method public float getStarRating();
+ method public boolean hasHeart();
+ method public boolean isRated();
+ method public boolean isThumbUp();
+ method public static android.support.v4.media.RatingCompat! newHeartRating(boolean);
+ method public static android.support.v4.media.RatingCompat! newPercentageRating(float);
+ method public static android.support.v4.media.RatingCompat! newStarRating(@android.support.v4.media.RatingCompat.StarStyle int, float);
+ method public static android.support.v4.media.RatingCompat! newThumbRating(boolean);
+ method public static android.support.v4.media.RatingCompat! newUnratedRating(@android.support.v4.media.RatingCompat.Style int);
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.RatingCompat!>! CREATOR;
+ field public static final int RATING_3_STARS = 3; // 0x3
+ field public static final int RATING_4_STARS = 4; // 0x4
+ field public static final int RATING_5_STARS = 5; // 0x5
+ field public static final int RATING_HEART = 1; // 0x1
+ field public static final int RATING_NONE = 0; // 0x0
+ field public static final int RATING_PERCENTAGE = 6; // 0x6
+ field public static final int RATING_THUMB_UP_DOWN = 2; // 0x2
+ }
+
+
+ @IntDef({android.support.v4.media.RatingCompat.RATING_NONE, android.support.v4.media.RatingCompat.RATING_HEART, android.support.v4.media.RatingCompat.RATING_THUMB_UP_DOWN, android.support.v4.media.RatingCompat.RATING_3_STARS, android.support.v4.media.RatingCompat.RATING_4_STARS, android.support.v4.media.RatingCompat.RATING_5_STARS, android.support.v4.media.RatingCompat.RATING_PERCENTAGE}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface RatingCompat.Style {
+ }
+
+}
+
+package android.support.v4.media.session {
+
+ public final class MediaControllerCompat {
+ ctor public MediaControllerCompat(android.content.Context!, android.support.v4.media.session.MediaSessionCompat);
+ ctor public MediaControllerCompat(android.content.Context!, android.support.v4.media.session.MediaSessionCompat.Token);
+ method public void addQueueItem(android.support.v4.media.MediaDescriptionCompat!);
+ method public void addQueueItem(android.support.v4.media.MediaDescriptionCompat!, int);
+ method public void adjustVolume(int, int);
+ method public boolean dispatchMediaButtonEvent(android.view.KeyEvent!);
+ method public android.os.Bundle! getExtras();
+ method public long getFlags();
+ method public static android.support.v4.media.session.MediaControllerCompat! getMediaController(android.app.Activity);
+ method public Object! getMediaController();
+ method public android.support.v4.media.MediaMetadataCompat! getMetadata();
+ method public String! getPackageName();
+ method public android.support.v4.media.session.MediaControllerCompat.PlaybackInfo! getPlaybackInfo();
+ method public android.support.v4.media.session.PlaybackStateCompat! getPlaybackState();
+ method public java.util.List<android.support.v4.media.session.MediaSessionCompat.QueueItem!>! getQueue();
+ method public CharSequence! getQueueTitle();
+ method public int getRatingType();
+ method public int getRepeatMode();
+ method public android.app.PendingIntent! getSessionActivity();
+ method public android.os.Bundle getSessionInfo();
+ method public android.support.v4.media.session.MediaSessionCompat.Token! getSessionToken();
+ method public int getShuffleMode();
+ method public android.support.v4.media.session.MediaControllerCompat.TransportControls! getTransportControls();
+ method public boolean isCaptioningEnabled();
+ method public boolean isSessionReady();
+ method public void registerCallback(android.support.v4.media.session.MediaControllerCompat.Callback);
+ method public void registerCallback(android.support.v4.media.session.MediaControllerCompat.Callback, android.os.Handler!);
+ method public void removeQueueItem(android.support.v4.media.MediaDescriptionCompat!);
+ method @Deprecated public void removeQueueItemAt(int);
+ method public void sendCommand(String, android.os.Bundle?, android.os.ResultReceiver?);
+ method public static void setMediaController(android.app.Activity, android.support.v4.media.session.MediaControllerCompat!);
+ method public void setVolumeTo(int, int);
+ method public void unregisterCallback(android.support.v4.media.session.MediaControllerCompat.Callback);
+ }
+
+ public abstract static class MediaControllerCompat.Callback implements android.os.IBinder.DeathRecipient {
+ ctor public MediaControllerCompat.Callback();
+ method public void binderDied();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.support.v4.media.session.IMediaControllerCallback! getIControllerCallback();
+ method public void onAudioInfoChanged(android.support.v4.media.session.MediaControllerCompat.PlaybackInfo!);
+ method public void onCaptioningEnabledChanged(boolean);
+ method public void onExtrasChanged(android.os.Bundle!);
+ method public void onMetadataChanged(android.support.v4.media.MediaMetadataCompat!);
+ method public void onPlaybackStateChanged(android.support.v4.media.session.PlaybackStateCompat!);
+ method public void onQueueChanged(java.util.List<android.support.v4.media.session.MediaSessionCompat.QueueItem!>!);
+ method public void onQueueTitleChanged(CharSequence!);
+ method public void onRepeatModeChanged(@android.support.v4.media.session.PlaybackStateCompat.RepeatMode int);
+ method public void onSessionDestroyed();
+ method public void onSessionEvent(String!, android.os.Bundle!);
+ method public void onSessionReady();
+ method public void onShuffleModeChanged(@android.support.v4.media.session.PlaybackStateCompat.ShuffleMode int);
+ }
+
+ public static final class MediaControllerCompat.PlaybackInfo {
+ method public androidx.media.AudioAttributesCompat getAudioAttributes();
+ method @Deprecated public int getAudioStream();
+ method public int getCurrentVolume();
+ method public int getMaxVolume();
+ method public int getPlaybackType();
+ method public int getVolumeControl();
+ field public static final int PLAYBACK_TYPE_LOCAL = 1; // 0x1
+ field public static final int PLAYBACK_TYPE_REMOTE = 2; // 0x2
+ }
+
+ public abstract static class MediaControllerCompat.TransportControls {
+ method public abstract void fastForward();
+ method public abstract void pause();
+ method public abstract void play();
+ method public abstract void playFromMediaId(String!, android.os.Bundle!);
+ method public abstract void playFromSearch(String!, android.os.Bundle!);
+ method public abstract void playFromUri(android.net.Uri!, android.os.Bundle!);
+ method public abstract void prepare();
+ method public abstract void prepareFromMediaId(String!, android.os.Bundle!);
+ method public abstract void prepareFromSearch(String!, android.os.Bundle!);
+ method public abstract void prepareFromUri(android.net.Uri!, android.os.Bundle!);
+ method public abstract void rewind();
+ method public abstract void seekTo(long);
+ method public abstract void sendCustomAction(android.support.v4.media.session.PlaybackStateCompat.CustomAction!, android.os.Bundle!);
+ method public abstract void sendCustomAction(String!, android.os.Bundle!);
+ method public abstract void setCaptioningEnabled(boolean);
+ method public void setPlaybackSpeed(float);
+ method public abstract void setRating(android.support.v4.media.RatingCompat!);
+ method public abstract void setRating(android.support.v4.media.RatingCompat!, android.os.Bundle!);
+ method public abstract void setRepeatMode(@android.support.v4.media.session.PlaybackStateCompat.RepeatMode int);
+ method public abstract void setShuffleMode(@android.support.v4.media.session.PlaybackStateCompat.ShuffleMode int);
+ method public abstract void skipToNext();
+ method public abstract void skipToPrevious();
+ method public abstract void skipToQueueItem(long);
+ method public abstract void stop();
+ field public static final String EXTRA_LEGACY_STREAM_TYPE = "android.media.session.extra.LEGACY_STREAM_TYPE";
+ }
+
+ public class MediaSessionCompat {
+ ctor public MediaSessionCompat(android.content.Context, String);
+ ctor public MediaSessionCompat(android.content.Context, String, android.content.ComponentName?, android.app.PendingIntent?);
+ ctor public MediaSessionCompat(android.content.Context, String, android.content.ComponentName?, android.app.PendingIntent?, android.os.Bundle?);
+ ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public MediaSessionCompat(android.content.Context, String, android.content.ComponentName?, android.app.PendingIntent?, android.os.Bundle?, androidx.versionedparcelable.VersionedParcelable?);
+ method public void addOnActiveChangeListener(android.support.v4.media.session.MediaSessionCompat.OnActiveChangeListener!);
+ method public static android.support.v4.media.session.MediaSessionCompat! fromMediaSession(android.content.Context!, Object!);
+ method public android.support.v4.media.session.MediaControllerCompat! getController();
+ method public final androidx.media.MediaSessionManager.RemoteUserInfo getCurrentControllerInfo();
+ method public Object! getMediaSession();
+ method public Object! getRemoteControlClient();
+ method public android.support.v4.media.session.MediaSessionCompat.Token! getSessionToken();
+ method public boolean isActive();
+ method public void release();
+ method public void removeOnActiveChangeListener(android.support.v4.media.session.MediaSessionCompat.OnActiveChangeListener!);
+ method public void sendSessionEvent(String!, android.os.Bundle!);
+ method public void setActive(boolean);
+ method public void setCallback(android.support.v4.media.session.MediaSessionCompat.Callback!);
+ method public void setCallback(android.support.v4.media.session.MediaSessionCompat.Callback!, android.os.Handler!);
+ method public void setCaptioningEnabled(boolean);
+ method public void setExtras(android.os.Bundle!);
+ method public void setFlags(int);
+ method public void setMediaButtonReceiver(android.app.PendingIntent!);
+ method public void setMetadata(android.support.v4.media.MediaMetadataCompat!);
+ method public void setPlaybackState(android.support.v4.media.session.PlaybackStateCompat!);
+ method public void setPlaybackToLocal(int);
+ method public void setPlaybackToRemote(androidx.media.VolumeProviderCompat!);
+ method public void setQueue(java.util.List<android.support.v4.media.session.MediaSessionCompat.QueueItem!>!);
+ method public void setQueueTitle(CharSequence!);
+ method public void setRatingType(@android.support.v4.media.RatingCompat.Style int);
+ method public void setRepeatMode(@android.support.v4.media.session.PlaybackStateCompat.RepeatMode int);
+ method public void setSessionActivity(android.app.PendingIntent!);
+ method public void setShuffleMode(@android.support.v4.media.session.PlaybackStateCompat.ShuffleMode int);
+ field public static final String ACTION_FLAG_AS_INAPPROPRIATE = "android.support.v4.media.session.action.FLAG_AS_INAPPROPRIATE";
+ field public static final String ACTION_FOLLOW = "android.support.v4.media.session.action.FOLLOW";
+ field public static final String ACTION_SKIP_AD = "android.support.v4.media.session.action.SKIP_AD";
+ field public static final String ACTION_UNFOLLOW = "android.support.v4.media.session.action.UNFOLLOW";
+ field public static final String ARGUMENT_MEDIA_ATTRIBUTE = "android.support.v4.media.session.ARGUMENT_MEDIA_ATTRIBUTE";
+ field public static final String ARGUMENT_MEDIA_ATTRIBUTE_VALUE = "android.support.v4.media.session.ARGUMENT_MEDIA_ATTRIBUTE_VALUE";
+ field @Deprecated public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1; // 0x1
+ field public static final int FLAG_HANDLES_QUEUE_COMMANDS = 4; // 0x4
+ field @Deprecated public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 2; // 0x2
+ field public static final int MEDIA_ATTRIBUTE_ALBUM = 1; // 0x1
+ field public static final int MEDIA_ATTRIBUTE_ARTIST = 0; // 0x0
+ field public static final int MEDIA_ATTRIBUTE_PLAYLIST = 2; // 0x2
+ }
+
+ public abstract static class MediaSessionCompat.Callback {
+ ctor public MediaSessionCompat.Callback();
+ method public void onAddQueueItem(android.support.v4.media.MediaDescriptionCompat!);
+ method public void onAddQueueItem(android.support.v4.media.MediaDescriptionCompat!, int);
+ method public void onCommand(String!, android.os.Bundle!, android.os.ResultReceiver!);
+ method public void onCustomAction(String!, android.os.Bundle!);
+ method public void onFastForward();
+ method public boolean onMediaButtonEvent(android.content.Intent!);
+ method public void onPause();
+ method public void onPlay();
+ method public void onPlayFromMediaId(String!, android.os.Bundle!);
+ method public void onPlayFromSearch(String!, android.os.Bundle!);
+ method public void onPlayFromUri(android.net.Uri!, android.os.Bundle!);
+ method public void onPrepare();
+ method public void onPrepareFromMediaId(String!, android.os.Bundle!);
+ method public void onPrepareFromSearch(String!, android.os.Bundle!);
+ method public void onPrepareFromUri(android.net.Uri!, android.os.Bundle!);
+ method public void onRemoveQueueItem(android.support.v4.media.MediaDescriptionCompat!);
+ method @Deprecated public void onRemoveQueueItemAt(int);
+ method public void onRewind();
+ method public void onSeekTo(long);
+ method public void onSetCaptioningEnabled(boolean);
+ method public void onSetPlaybackSpeed(float);
+ method public void onSetRating(android.support.v4.media.RatingCompat!);
+ method public void onSetRating(android.support.v4.media.RatingCompat!, android.os.Bundle!);
+ method public void onSetRepeatMode(@android.support.v4.media.session.PlaybackStateCompat.RepeatMode int);
+ method public void onSetShuffleMode(@android.support.v4.media.session.PlaybackStateCompat.ShuffleMode int);
+ method public void onSkipToNext();
+ method public void onSkipToPrevious();
+ method public void onSkipToQueueItem(long);
+ method public void onStop();
+ }
+
+ public static interface MediaSessionCompat.OnActiveChangeListener {
+ method public void onActiveChanged();
+ }
+
+ public static final class MediaSessionCompat.QueueItem implements android.os.Parcelable {
+ ctor public MediaSessionCompat.QueueItem(android.support.v4.media.MediaDescriptionCompat!, long);
+ method public int describeContents();
+ method public static android.support.v4.media.session.MediaSessionCompat.QueueItem! fromQueueItem(Object!);
+ method public static java.util.List<android.support.v4.media.session.MediaSessionCompat.QueueItem!>! fromQueueItemList(java.util.List<?>!);
+ method public android.support.v4.media.MediaDescriptionCompat! getDescription();
+ method public long getQueueId();
+ method public Object! getQueueItem();
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.session.MediaSessionCompat.QueueItem!>! CREATOR;
+ field public static final int UNKNOWN_ID = -1; // 0xffffffff
+ }
+
+ public static final class MediaSessionCompat.Token implements android.os.Parcelable {
+ method public int describeContents();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static android.support.v4.media.session.MediaSessionCompat.Token! fromBundle(android.os.Bundle!);
+ method public static android.support.v4.media.session.MediaSessionCompat.Token! fromToken(Object!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.versionedparcelable.VersionedParcelable! getSession2Token();
+ method public Object! getToken();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setSession2Token(androidx.versionedparcelable.VersionedParcelable!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.os.Bundle! toBundle();
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.session.MediaSessionCompat.Token!>! CREATOR;
+ }
+
+ public class ParcelableVolumeInfo implements android.os.Parcelable {
+ ctor public ParcelableVolumeInfo(int, int, int, int, int);
+ ctor public ParcelableVolumeInfo(android.os.Parcel!);
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.session.ParcelableVolumeInfo!>! CREATOR;
+ field public int audioStream;
+ field public int controlType;
+ field public int currentVolume;
+ field public int maxVolume;
+ field public int volumeType;
+ }
+
+ public final class PlaybackStateCompat implements android.os.Parcelable {
+ method public int describeContents();
+ method public static android.support.v4.media.session.PlaybackStateCompat! fromPlaybackState(Object!);
+ method @android.support.v4.media.session.PlaybackStateCompat.Actions public long getActions();
+ method public long getActiveQueueItemId();
+ method public long getBufferedPosition();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public long getCurrentPosition(Long!);
+ method public java.util.List<android.support.v4.media.session.PlaybackStateCompat.CustomAction!>! getCustomActions();
+ method public int getErrorCode();
+ method public CharSequence! getErrorMessage();
+ method public android.os.Bundle? getExtras();
+ method public long getLastPositionUpdateTime();
+ method public float getPlaybackSpeed();
+ method public Object! getPlaybackState();
+ method public long getPosition();
+ method @android.support.v4.media.session.PlaybackStateCompat.State public int getState();
+ method public static int toKeyCode(@android.support.v4.media.session.PlaybackStateCompat.MediaKeyAction long);
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final long ACTION_FAST_FORWARD = 64L; // 0x40L
+ field public static final long ACTION_PAUSE = 2L; // 0x2L
+ field public static final long ACTION_PLAY = 4L; // 0x4L
+ field public static final long ACTION_PLAY_FROM_MEDIA_ID = 1024L; // 0x400L
+ field public static final long ACTION_PLAY_FROM_SEARCH = 2048L; // 0x800L
+ field public static final long ACTION_PLAY_FROM_URI = 8192L; // 0x2000L
+ field public static final long ACTION_PLAY_PAUSE = 512L; // 0x200L
+ field public static final long ACTION_PREPARE = 16384L; // 0x4000L
+ field public static final long ACTION_PREPARE_FROM_MEDIA_ID = 32768L; // 0x8000L
+ field public static final long ACTION_PREPARE_FROM_SEARCH = 65536L; // 0x10000L
+ field public static final long ACTION_PREPARE_FROM_URI = 131072L; // 0x20000L
+ field public static final long ACTION_REWIND = 8L; // 0x8L
+ field public static final long ACTION_SEEK_TO = 256L; // 0x100L
+ field public static final long ACTION_SET_CAPTIONING_ENABLED = 1048576L; // 0x100000L
+ field public static final long ACTION_SET_RATING = 128L; // 0x80L
+ field public static final long ACTION_SET_REPEAT_MODE = 262144L; // 0x40000L
+ field public static final long ACTION_SET_SHUFFLE_MODE = 2097152L; // 0x200000L
+ field @Deprecated public static final long ACTION_SET_SHUFFLE_MODE_ENABLED = 524288L; // 0x80000L
+ field public static final long ACTION_SKIP_TO_NEXT = 32L; // 0x20L
+ field public static final long ACTION_SKIP_TO_PREVIOUS = 16L; // 0x10L
+ field public static final long ACTION_SKIP_TO_QUEUE_ITEM = 4096L; // 0x1000L
+ field public static final long ACTION_STOP = 1L; // 0x1L
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.session.PlaybackStateCompat!>! CREATOR;
+ field public static final int ERROR_CODE_ACTION_ABORTED = 10; // 0xa
+ field public static final int ERROR_CODE_APP_ERROR = 1; // 0x1
+ field public static final int ERROR_CODE_AUTHENTICATION_EXPIRED = 3; // 0x3
+ field public static final int ERROR_CODE_CONCURRENT_STREAM_LIMIT = 5; // 0x5
+ field public static final int ERROR_CODE_CONTENT_ALREADY_PLAYING = 8; // 0x8
+ field public static final int ERROR_CODE_END_OF_QUEUE = 11; // 0xb
+ field public static final int ERROR_CODE_NOT_AVAILABLE_IN_REGION = 7; // 0x7
+ field public static final int ERROR_CODE_NOT_SUPPORTED = 2; // 0x2
+ field public static final int ERROR_CODE_PARENTAL_CONTROL_RESTRICTED = 6; // 0x6
+ field public static final int ERROR_CODE_PREMIUM_ACCOUNT_REQUIRED = 4; // 0x4
+ field public static final int ERROR_CODE_SKIP_LIMIT_REACHED = 9; // 0x9
+ field public static final int ERROR_CODE_UNKNOWN_ERROR = 0; // 0x0
+ field public static final long PLAYBACK_POSITION_UNKNOWN = -1L; // 0xffffffffffffffffL
+ field public static final int REPEAT_MODE_ALL = 2; // 0x2
+ field public static final int REPEAT_MODE_GROUP = 3; // 0x3
+ field public static final int REPEAT_MODE_INVALID = -1; // 0xffffffff
+ field public static final int REPEAT_MODE_NONE = 0; // 0x0
+ field public static final int REPEAT_MODE_ONE = 1; // 0x1
+ field public static final int SHUFFLE_MODE_ALL = 1; // 0x1
+ field public static final int SHUFFLE_MODE_GROUP = 2; // 0x2
+ field public static final int SHUFFLE_MODE_INVALID = -1; // 0xffffffff
+ field public static final int SHUFFLE_MODE_NONE = 0; // 0x0
+ field public static final int STATE_BUFFERING = 6; // 0x6
+ field public static final int STATE_CONNECTING = 8; // 0x8
+ field public static final int STATE_ERROR = 7; // 0x7
+ field public static final int STATE_FAST_FORWARDING = 4; // 0x4
+ field public static final int STATE_NONE = 0; // 0x0
+ field public static final int STATE_PAUSED = 2; // 0x2
+ field public static final int STATE_PLAYING = 3; // 0x3
+ field public static final int STATE_REWINDING = 5; // 0x5
+ field public static final int STATE_SKIPPING_TO_NEXT = 10; // 0xa
+ field public static final int STATE_SKIPPING_TO_PREVIOUS = 9; // 0x9
+ field public static final int STATE_SKIPPING_TO_QUEUE_ITEM = 11; // 0xb
+ field public static final int STATE_STOPPED = 1; // 0x1
+ }
+
+ @LongDef(flag=true, value={android.support.v4.media.session.PlaybackStateCompat.ACTION_STOP, android.support.v4.media.session.PlaybackStateCompat.ACTION_PAUSE, android.support.v4.media.session.PlaybackStateCompat.ACTION_PLAY, android.support.v4.media.session.PlaybackStateCompat.ACTION_REWIND, android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS, android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_NEXT, android.support.v4.media.session.PlaybackStateCompat.ACTION_FAST_FORWARD, android.support.v4.media.session.PlaybackStateCompat.ACTION_SET_RATING, android.support.v4.media.session.PlaybackStateCompat.ACTION_SEEK_TO, android.support.v4.media.session.PlaybackStateCompat.ACTION_PLAY_PAUSE, android.support.v4.media.session.PlaybackStateCompat.ACTION_PLAY_FROM_MEDIA_ID, android.support.v4.media.session.PlaybackStateCompat.ACTION_PLAY_FROM_SEARCH, android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM, android.support.v4.media.session.PlaybackStateCompat.ACTION_PLAY_FROM_URI, android.support.v4.media.session.PlaybackStateCompat.ACTION_PREPARE, android.support.v4.media.session.PlaybackStateCompat.ACTION_PREPARE_FROM_MEDIA_ID, android.support.v4.media.session.PlaybackStateCompat.ACTION_PREPARE_FROM_SEARCH, android.support.v4.media.session.PlaybackStateCompat.ACTION_PREPARE_FROM_URI, android.support.v4.media.session.PlaybackStateCompat.ACTION_SET_REPEAT_MODE, android.support.v4.media.session.PlaybackStateCompat.ACTION_SET_SHUFFLE_MODE, android.support.v4.media.session.PlaybackStateCompat.ACTION_SET_CAPTIONING_ENABLED}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PlaybackStateCompat.Actions {
+ }
+
+ public static final class PlaybackStateCompat.Builder {
+ ctor public PlaybackStateCompat.Builder();
+ ctor public PlaybackStateCompat.Builder(android.support.v4.media.session.PlaybackStateCompat!);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! addCustomAction(String!, String!, int);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! addCustomAction(android.support.v4.media.session.PlaybackStateCompat.CustomAction!);
+ method public android.support.v4.media.session.PlaybackStateCompat! build();
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! setActions(@android.support.v4.media.session.PlaybackStateCompat.Actions long);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! setActiveQueueItemId(long);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! setBufferedPosition(long);
+ method @Deprecated public android.support.v4.media.session.PlaybackStateCompat.Builder! setErrorMessage(CharSequence!);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! setErrorMessage(int, CharSequence!);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! setExtras(android.os.Bundle!);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! setState(@android.support.v4.media.session.PlaybackStateCompat.State int, long, float);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! setState(@android.support.v4.media.session.PlaybackStateCompat.State int, long, float, long);
+ }
+
+ public static final class PlaybackStateCompat.CustomAction implements android.os.Parcelable {
+ method public int describeContents();
+ method public static android.support.v4.media.session.PlaybackStateCompat.CustomAction! fromCustomAction(Object!);
+ method public String! getAction();
+ method public Object! getCustomAction();
+ method public android.os.Bundle! getExtras();
+ method public int getIcon();
+ method public CharSequence! getName();
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.session.PlaybackStateCompat.CustomAction!>! CREATOR;
+ }
+
+ public static final class PlaybackStateCompat.CustomAction.Builder {
+ ctor public PlaybackStateCompat.CustomAction.Builder(String!, CharSequence!, int);
+ method public android.support.v4.media.session.PlaybackStateCompat.CustomAction! build();
+ method public android.support.v4.media.session.PlaybackStateCompat.CustomAction.Builder! setExtras(android.os.Bundle!);
+ }
+
+
+ @IntDef({android.support.v4.media.session.PlaybackStateCompat.REPEAT_MODE_INVALID, android.support.v4.media.session.PlaybackStateCompat.REPEAT_MODE_NONE, android.support.v4.media.session.PlaybackStateCompat.REPEAT_MODE_ONE, android.support.v4.media.session.PlaybackStateCompat.REPEAT_MODE_ALL, android.support.v4.media.session.PlaybackStateCompat.REPEAT_MODE_GROUP}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PlaybackStateCompat.RepeatMode {
+ }
+
+ @IntDef({android.support.v4.media.session.PlaybackStateCompat.SHUFFLE_MODE_INVALID, android.support.v4.media.session.PlaybackStateCompat.SHUFFLE_MODE_NONE, android.support.v4.media.session.PlaybackStateCompat.SHUFFLE_MODE_ALL, android.support.v4.media.session.PlaybackStateCompat.SHUFFLE_MODE_GROUP}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PlaybackStateCompat.ShuffleMode {
+ }
+
+ @IntDef({android.support.v4.media.session.PlaybackStateCompat.STATE_NONE, android.support.v4.media.session.PlaybackStateCompat.STATE_STOPPED, android.support.v4.media.session.PlaybackStateCompat.STATE_PAUSED, android.support.v4.media.session.PlaybackStateCompat.STATE_PLAYING, android.support.v4.media.session.PlaybackStateCompat.STATE_FAST_FORWARDING, android.support.v4.media.session.PlaybackStateCompat.STATE_REWINDING, android.support.v4.media.session.PlaybackStateCompat.STATE_BUFFERING, android.support.v4.media.session.PlaybackStateCompat.STATE_ERROR, android.support.v4.media.session.PlaybackStateCompat.STATE_CONNECTING, android.support.v4.media.session.PlaybackStateCompat.STATE_SKIPPING_TO_PREVIOUS, android.support.v4.media.session.PlaybackStateCompat.STATE_SKIPPING_TO_NEXT, android.support.v4.media.session.PlaybackStateCompat.STATE_SKIPPING_TO_QUEUE_ITEM}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PlaybackStateCompat.State {
+ }
+
+}
+
+package androidx.media {
+
+ public class AudioAttributesCompat implements androidx.versionedparcelable.VersionedParcelable {
+ method public int getContentType();
+ method public int getFlags();
+ method public int getLegacyStreamType();
+ method @androidx.media.AudioAttributesCompat.AttributeUsage public int getUsage();
+ method public int getVolumeControlStream();
+ method public Object? unwrap();
+ method public static androidx.media.AudioAttributesCompat? wrap(Object);
+ field public static final int CONTENT_TYPE_MOVIE = 3; // 0x3
+ field public static final int CONTENT_TYPE_MUSIC = 2; // 0x2
+ field public static final int CONTENT_TYPE_SONIFICATION = 4; // 0x4
+ field public static final int CONTENT_TYPE_SPEECH = 1; // 0x1
+ field public static final int CONTENT_TYPE_UNKNOWN = 0; // 0x0
+ field public static final int FLAG_AUDIBILITY_ENFORCED = 1; // 0x1
+ field public static final int FLAG_HW_AV_SYNC = 16; // 0x10
+ field public static final int USAGE_ALARM = 4; // 0x4
+ field public static final int USAGE_ASSISTANCE_ACCESSIBILITY = 11; // 0xb
+ field public static final int USAGE_ASSISTANCE_NAVIGATION_GUIDANCE = 12; // 0xc
+ field public static final int USAGE_ASSISTANCE_SONIFICATION = 13; // 0xd
+ field public static final int USAGE_ASSISTANT = 16; // 0x10
+ field public static final int USAGE_GAME = 14; // 0xe
+ field public static final int USAGE_MEDIA = 1; // 0x1
+ field public static final int USAGE_NOTIFICATION = 5; // 0x5
+ field public static final int USAGE_NOTIFICATION_COMMUNICATION_DELAYED = 9; // 0x9
+ field public static final int USAGE_NOTIFICATION_COMMUNICATION_INSTANT = 8; // 0x8
+ field public static final int USAGE_NOTIFICATION_COMMUNICATION_REQUEST = 7; // 0x7
+ field public static final int USAGE_NOTIFICATION_EVENT = 10; // 0xa
+ field public static final int USAGE_NOTIFICATION_RINGTONE = 6; // 0x6
+ field public static final int USAGE_UNKNOWN = 0; // 0x0
+ field public static final int USAGE_VOICE_COMMUNICATION = 2; // 0x2
+ field public static final int USAGE_VOICE_COMMUNICATION_SIGNALLING = 3; // 0x3
+ }
+
+
+
+ public static class AudioAttributesCompat.Builder {
+ ctor public AudioAttributesCompat.Builder();
+ ctor public AudioAttributesCompat.Builder(androidx.media.AudioAttributesCompat!);
+ method public androidx.media.AudioAttributesCompat! build();
+ method public androidx.media.AudioAttributesCompat.Builder! setContentType(@androidx.media.AudioAttributesCompat.AttributeContentType int);
+ method public androidx.media.AudioAttributesCompat.Builder! setFlags(int);
+ method public androidx.media.AudioAttributesCompat.Builder! setLegacyStreamType(int);
+ method public androidx.media.AudioAttributesCompat.Builder! setUsage(@androidx.media.AudioAttributesCompat.AttributeUsage int);
+ }
+
+
+
+
+ public class AudioFocusRequestCompat {
+ method public androidx.media.AudioAttributesCompat getAudioAttributesCompat();
+ method public android.os.Handler getFocusChangeHandler();
+ method public int getFocusGain();
+ method public android.media.AudioManager.OnAudioFocusChangeListener getOnAudioFocusChangeListener();
+ method public boolean willPauseWhenDucked();
+ }
+
+ public static final class AudioFocusRequestCompat.Builder {
+ ctor public AudioFocusRequestCompat.Builder(int);
+ ctor public AudioFocusRequestCompat.Builder(androidx.media.AudioFocusRequestCompat);
+ method public androidx.media.AudioFocusRequestCompat! build();
+ method public androidx.media.AudioFocusRequestCompat.Builder setAudioAttributes(androidx.media.AudioAttributesCompat);
+ method public androidx.media.AudioFocusRequestCompat.Builder setFocusGain(int);
+ method public androidx.media.AudioFocusRequestCompat.Builder setOnAudioFocusChangeListener(android.media.AudioManager.OnAudioFocusChangeListener);
+ method public androidx.media.AudioFocusRequestCompat.Builder setOnAudioFocusChangeListener(android.media.AudioManager.OnAudioFocusChangeListener, android.os.Handler);
+ method public androidx.media.AudioFocusRequestCompat.Builder setWillPauseWhenDucked(boolean);
+ }
+
+ public final class AudioManagerCompat {
+ method public static int abandonAudioFocusRequest(android.media.AudioManager, androidx.media.AudioFocusRequestCompat);
+ method public static int requestAudioFocus(android.media.AudioManager, androidx.media.AudioFocusRequestCompat);
+ field public static final int AUDIOFOCUS_GAIN = 1; // 0x1
+ field public static final int AUDIOFOCUS_GAIN_TRANSIENT = 2; // 0x2
+ field public static final int AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE = 4; // 0x4
+ field public static final int AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK = 3; // 0x3
+ }
+
+
+
+ public abstract class MediaBrowserServiceCompat extends android.app.Service {
+ ctor public MediaBrowserServiceCompat();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void attachToBaseContext(android.content.Context!);
+ method public void dump(java.io.FileDescriptor!, java.io.PrintWriter!, String![]!);
+ method public final android.os.Bundle! getBrowserRootHints();
+ method public final androidx.media.MediaSessionManager.RemoteUserInfo getCurrentBrowserInfo();
+ method public android.support.v4.media.session.MediaSessionCompat.Token? getSessionToken();
+ method public void notifyChildrenChanged(String);
+ method public void notifyChildrenChanged(String, android.os.Bundle);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void notifyChildrenChanged(androidx.media.MediaSessionManager.RemoteUserInfo, String, android.os.Bundle);
+ method public android.os.IBinder! onBind(android.content.Intent!);
+ method public void onCustomAction(String, android.os.Bundle!, androidx.media.MediaBrowserServiceCompat.Result<android.os.Bundle!>);
+ method public abstract androidx.media.MediaBrowserServiceCompat.BrowserRoot? onGetRoot(String, int, android.os.Bundle?);
+ method public abstract void onLoadChildren(String, androidx.media.MediaBrowserServiceCompat.Result<java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem!>!>);
+ method public void onLoadChildren(String, androidx.media.MediaBrowserServiceCompat.Result<java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem!>!>, android.os.Bundle);
+ method public void onLoadItem(String!, androidx.media.MediaBrowserServiceCompat.Result<android.support.v4.media.MediaBrowserCompat.MediaItem!>);
+ method public void onSearch(String, android.os.Bundle!, androidx.media.MediaBrowserServiceCompat.Result<java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem!>!>);
+ method public void setSessionToken(android.support.v4.media.session.MediaSessionCompat.Token!);
+ field public static final String SERVICE_INTERFACE = "android.media.browse.MediaBrowserService";
+ }
+
+ public static final class MediaBrowserServiceCompat.BrowserRoot {
+ ctor public MediaBrowserServiceCompat.BrowserRoot(String, android.os.Bundle?);
+ method public android.os.Bundle! getExtras();
+ method public String! getRootId();
+ field public static final String EXTRA_OFFLINE = "android.service.media.extra.OFFLINE";
+ field public static final String EXTRA_RECENT = "android.service.media.extra.RECENT";
+ field public static final String EXTRA_SUGGESTED = "android.service.media.extra.SUGGESTED";
+ field @Deprecated public static final String EXTRA_SUGGESTION_KEYWORDS = "android.service.media.extra.SUGGESTION_KEYWORDS";
+ }
+
+ public static class MediaBrowserServiceCompat.Result<T> {
+ method public void detach();
+ method public void sendError(android.os.Bundle!);
+ method public void sendProgressUpdate(android.os.Bundle!);
+ method public void sendResult(T!);
+ }
+
+ public final class MediaSessionManager {
+ method public static androidx.media.MediaSessionManager getSessionManager(android.content.Context);
+ method public boolean isTrustedForMediaControl(androidx.media.MediaSessionManager.RemoteUserInfo);
+ }
+
+ public static final class MediaSessionManager.RemoteUserInfo {
+ ctor public MediaSessionManager.RemoteUserInfo(String, int, int);
+ method public String getPackageName();
+ method public int getPid();
+ method public int getUid();
+ field public static final String LEGACY_CONTROLLER = "android.media.session.MediaController";
+ }
+
+ public abstract class VolumeProviderCompat {
+ ctor public VolumeProviderCompat(@androidx.media.VolumeProviderCompat.ControlType int, int, int);
+ method public final int getCurrentVolume();
+ method public final int getMaxVolume();
+ method @androidx.media.VolumeProviderCompat.ControlType public final int getVolumeControl();
+ method public Object! getVolumeProvider();
+ method public void onAdjustVolume(int);
+ method public void onSetVolumeTo(int);
+ method public void setCallback(androidx.media.VolumeProviderCompat.Callback!);
+ method public final void setCurrentVolume(int);
+ field public static final int VOLUME_CONTROL_ABSOLUTE = 2; // 0x2
+ field public static final int VOLUME_CONTROL_FIXED = 0; // 0x0
+ field public static final int VOLUME_CONTROL_RELATIVE = 1; // 0x1
+ }
+
+ public abstract static class VolumeProviderCompat.Callback {
+ ctor public VolumeProviderCompat.Callback();
+ method public abstract void onVolumeChanged(androidx.media.VolumeProviderCompat!);
+ }
+
+ @IntDef({androidx.media.VolumeProviderCompat.VOLUME_CONTROL_FIXED, androidx.media.VolumeProviderCompat.VOLUME_CONTROL_RELATIVE, androidx.media.VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface VolumeProviderCompat.ControlType {
+ }
+
+}
+
+package androidx.media.app {
+
+ public class NotificationCompat {
+ }
+
+ public static class NotificationCompat.DecoratedMediaCustomViewStyle extends androidx.media.app.NotificationCompat.MediaStyle {
+ ctor public NotificationCompat.DecoratedMediaCustomViewStyle();
+ }
+
+ public static class NotificationCompat.MediaStyle extends androidx.core.app.NotificationCompat.Style {
+ ctor public NotificationCompat.MediaStyle();
+ ctor public NotificationCompat.MediaStyle(androidx.core.app.NotificationCompat.Builder!);
+ method public static android.support.v4.media.session.MediaSessionCompat.Token! getMediaSession(android.app.Notification!);
+ method public androidx.media.app.NotificationCompat.MediaStyle! setCancelButtonIntent(android.app.PendingIntent!);
+ method public androidx.media.app.NotificationCompat.MediaStyle! setMediaSession(android.support.v4.media.session.MediaSessionCompat.Token!);
+ method public androidx.media.app.NotificationCompat.MediaStyle! setShowActionsInCompactView(int...);
+ method public androidx.media.app.NotificationCompat.MediaStyle! setShowCancelButton(boolean);
+ }
+
+}
+
+package androidx.media.session {
+
+ public class MediaButtonReceiver extends android.content.BroadcastReceiver {
+ ctor public MediaButtonReceiver();
+ method public static android.app.PendingIntent! buildMediaButtonPendingIntent(android.content.Context!, @android.support.v4.media.session.PlaybackStateCompat.MediaKeyAction long);
+ method public static android.app.PendingIntent! buildMediaButtonPendingIntent(android.content.Context!, android.content.ComponentName!, @android.support.v4.media.session.PlaybackStateCompat.MediaKeyAction long);
+ method public static android.view.KeyEvent! handleIntent(android.support.v4.media.session.MediaSessionCompat!, android.content.Intent!);
+ method public void onReceive(android.content.Context!, android.content.Intent!);
+ }
+
+}
+
diff --git a/media/api/restricted_current.txt b/media/api/restricted_current.txt
new file mode 100644
index 0000000..9c1f0db
--- /dev/null
+++ b/media/api/restricted_current.txt
@@ -0,0 +1,743 @@
+// Signature format: 3.0
+package android.support.v4.media {
+
+ public final class MediaBrowserCompat {
+ ctor public MediaBrowserCompat(android.content.Context!, android.content.ComponentName!, android.support.v4.media.MediaBrowserCompat.ConnectionCallback!, android.os.Bundle!);
+ method public void connect();
+ method public void disconnect();
+ method public android.os.Bundle? getExtras();
+ method public void getItem(String, android.support.v4.media.MediaBrowserCompat.ItemCallback);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.os.Bundle? getNotifyChildrenChangedOptions();
+ method public String getRoot();
+ method public android.content.ComponentName getServiceComponent();
+ method public android.support.v4.media.session.MediaSessionCompat.Token getSessionToken();
+ method public boolean isConnected();
+ method public void search(String, android.os.Bundle!, android.support.v4.media.MediaBrowserCompat.SearchCallback);
+ method public void sendCustomAction(String, android.os.Bundle!, android.support.v4.media.MediaBrowserCompat.CustomActionCallback?);
+ method public void subscribe(String, android.support.v4.media.MediaBrowserCompat.SubscriptionCallback);
+ method public void subscribe(String, android.os.Bundle, android.support.v4.media.MediaBrowserCompat.SubscriptionCallback);
+ method public void unsubscribe(String);
+ method public void unsubscribe(String, android.support.v4.media.MediaBrowserCompat.SubscriptionCallback);
+ field public static final String CUSTOM_ACTION_DOWNLOAD = "android.support.v4.media.action.DOWNLOAD";
+ field public static final String CUSTOM_ACTION_REMOVE_DOWNLOADED_FILE = "android.support.v4.media.action.REMOVE_DOWNLOADED_FILE";
+ field public static final String EXTRA_DOWNLOAD_PROGRESS = "android.media.browse.extra.DOWNLOAD_PROGRESS";
+ field public static final String EXTRA_MEDIA_ID = "android.media.browse.extra.MEDIA_ID";
+ field public static final String EXTRA_PAGE = "android.media.browse.extra.PAGE";
+ field public static final String EXTRA_PAGE_SIZE = "android.media.browse.extra.PAGE_SIZE";
+ }
+
+ public static class MediaBrowserCompat.ConnectionCallback {
+ ctor public MediaBrowserCompat.ConnectionCallback();
+ method public void onConnected();
+ method public void onConnectionFailed();
+ method public void onConnectionSuspended();
+ }
+
+ public abstract static class MediaBrowserCompat.CustomActionCallback {
+ ctor public MediaBrowserCompat.CustomActionCallback();
+ method public void onError(String!, android.os.Bundle!, android.os.Bundle!);
+ method public void onProgressUpdate(String!, android.os.Bundle!, android.os.Bundle!);
+ method public void onResult(String!, android.os.Bundle!, android.os.Bundle!);
+ }
+
+ public abstract static class MediaBrowserCompat.ItemCallback {
+ ctor public MediaBrowserCompat.ItemCallback();
+ method public void onError(String);
+ method public void onItemLoaded(android.support.v4.media.MediaBrowserCompat.MediaItem!);
+ }
+
+ public static class MediaBrowserCompat.MediaItem implements android.os.Parcelable {
+ ctor public MediaBrowserCompat.MediaItem(android.support.v4.media.MediaDescriptionCompat, int);
+ method public int describeContents();
+ method public static android.support.v4.media.MediaBrowserCompat.MediaItem! fromMediaItem(Object!);
+ method public static java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem!>! fromMediaItemList(java.util.List<?>!);
+ method public android.support.v4.media.MediaDescriptionCompat getDescription();
+ method public int getFlags();
+ method public String? getMediaId();
+ method public boolean isBrowsable();
+ method public boolean isPlayable();
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.MediaBrowserCompat.MediaItem!>! CREATOR;
+ field public static final int FLAG_BROWSABLE = 1; // 0x1
+ field public static final int FLAG_PLAYABLE = 2; // 0x2
+ }
+
+ public abstract static class MediaBrowserCompat.SearchCallback {
+ ctor public MediaBrowserCompat.SearchCallback();
+ method public void onError(String, android.os.Bundle!);
+ method public void onSearchResult(String, android.os.Bundle!, java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem!>);
+ }
+
+ public abstract static class MediaBrowserCompat.SubscriptionCallback {
+ ctor public MediaBrowserCompat.SubscriptionCallback();
+ method public void onChildrenLoaded(String, java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem!>);
+ method public void onChildrenLoaded(String, java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem!>, android.os.Bundle);
+ method public void onError(String);
+ method public void onError(String, android.os.Bundle);
+ }
+
+ public final class MediaDescriptionCompat implements android.os.Parcelable {
+ method public int describeContents();
+ method public static android.support.v4.media.MediaDescriptionCompat! fromMediaDescription(Object!);
+ method public CharSequence? getDescription();
+ method public android.os.Bundle? getExtras();
+ method public android.graphics.Bitmap? getIconBitmap();
+ method public android.net.Uri? getIconUri();
+ method public Object! getMediaDescription();
+ method public String? getMediaId();
+ method public android.net.Uri? getMediaUri();
+ method public CharSequence? getSubtitle();
+ method public CharSequence? getTitle();
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final long BT_FOLDER_TYPE_ALBUMS = 2L; // 0x2L
+ field public static final long BT_FOLDER_TYPE_ARTISTS = 3L; // 0x3L
+ field public static final long BT_FOLDER_TYPE_GENRES = 4L; // 0x4L
+ field public static final long BT_FOLDER_TYPE_MIXED = 0L; // 0x0L
+ field public static final long BT_FOLDER_TYPE_PLAYLISTS = 5L; // 0x5L
+ field public static final long BT_FOLDER_TYPE_TITLES = 1L; // 0x1L
+ field public static final long BT_FOLDER_TYPE_YEARS = 6L; // 0x6L
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.MediaDescriptionCompat!>! CREATOR;
+ field public static final String EXTRA_BT_FOLDER_TYPE = "android.media.extra.BT_FOLDER_TYPE";
+ field public static final String EXTRA_DOWNLOAD_STATUS = "android.media.extra.DOWNLOAD_STATUS";
+ field public static final long STATUS_DOWNLOADED = 2L; // 0x2L
+ field public static final long STATUS_DOWNLOADING = 1L; // 0x1L
+ field public static final long STATUS_NOT_DOWNLOADED = 0L; // 0x0L
+ }
+
+ public static final class MediaDescriptionCompat.Builder {
+ ctor public MediaDescriptionCompat.Builder();
+ method public android.support.v4.media.MediaDescriptionCompat! build();
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setDescription(CharSequence?);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setExtras(android.os.Bundle?);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setIconBitmap(android.graphics.Bitmap?);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setIconUri(android.net.Uri?);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setMediaId(String?);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setMediaUri(android.net.Uri?);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setSubtitle(CharSequence?);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setTitle(CharSequence?);
+ }
+
+ public final class MediaMetadataCompat implements android.os.Parcelable {
+ method public boolean containsKey(String!);
+ method public int describeContents();
+ method public static android.support.v4.media.MediaMetadataCompat! fromMediaMetadata(Object!);
+ method public android.graphics.Bitmap! getBitmap(String!);
+ method public android.os.Bundle! getBundle();
+ method public android.support.v4.media.MediaDescriptionCompat! getDescription();
+ method public long getLong(String!);
+ method public Object! getMediaMetadata();
+ method public android.support.v4.media.RatingCompat! getRating(String!);
+ method public String! getString(String!);
+ method public CharSequence! getText(String!);
+ method public java.util.Set<java.lang.String!>! keySet();
+ method public int size();
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.MediaMetadataCompat!>! CREATOR;
+ field public static final String METADATA_KEY_ADVERTISEMENT = "android.media.metadata.ADVERTISEMENT";
+ field public static final String METADATA_KEY_ALBUM = "android.media.metadata.ALBUM";
+ field public static final String METADATA_KEY_ALBUM_ART = "android.media.metadata.ALBUM_ART";
+ field public static final String METADATA_KEY_ALBUM_ARTIST = "android.media.metadata.ALBUM_ARTIST";
+ field public static final String METADATA_KEY_ALBUM_ART_URI = "android.media.metadata.ALBUM_ART_URI";
+ field public static final String METADATA_KEY_ART = "android.media.metadata.ART";
+ field public static final String METADATA_KEY_ARTIST = "android.media.metadata.ARTIST";
+ field public static final String METADATA_KEY_ART_URI = "android.media.metadata.ART_URI";
+ field public static final String METADATA_KEY_AUTHOR = "android.media.metadata.AUTHOR";
+ field public static final String METADATA_KEY_BT_FOLDER_TYPE = "android.media.metadata.BT_FOLDER_TYPE";
+ field public static final String METADATA_KEY_COMPILATION = "android.media.metadata.COMPILATION";
+ field public static final String METADATA_KEY_COMPOSER = "android.media.metadata.COMPOSER";
+ field public static final String METADATA_KEY_DATE = "android.media.metadata.DATE";
+ field public static final String METADATA_KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER";
+ field public static final String METADATA_KEY_DISPLAY_DESCRIPTION = "android.media.metadata.DISPLAY_DESCRIPTION";
+ field public static final String METADATA_KEY_DISPLAY_ICON = "android.media.metadata.DISPLAY_ICON";
+ field public static final String METADATA_KEY_DISPLAY_ICON_URI = "android.media.metadata.DISPLAY_ICON_URI";
+ field public static final String METADATA_KEY_DISPLAY_SUBTITLE = "android.media.metadata.DISPLAY_SUBTITLE";
+ field public static final String METADATA_KEY_DISPLAY_TITLE = "android.media.metadata.DISPLAY_TITLE";
+ field public static final String METADATA_KEY_DOWNLOAD_STATUS = "android.media.metadata.DOWNLOAD_STATUS";
+ field public static final String METADATA_KEY_DURATION = "android.media.metadata.DURATION";
+ field public static final String METADATA_KEY_GENRE = "android.media.metadata.GENRE";
+ field public static final String METADATA_KEY_MEDIA_ID = "android.media.metadata.MEDIA_ID";
+ field public static final String METADATA_KEY_MEDIA_URI = "android.media.metadata.MEDIA_URI";
+ field public static final String METADATA_KEY_NUM_TRACKS = "android.media.metadata.NUM_TRACKS";
+ field public static final String METADATA_KEY_RATING = "android.media.metadata.RATING";
+ field public static final String METADATA_KEY_TITLE = "android.media.metadata.TITLE";
+ field public static final String METADATA_KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER";
+ field public static final String METADATA_KEY_USER_RATING = "android.media.metadata.USER_RATING";
+ field public static final String METADATA_KEY_WRITER = "android.media.metadata.WRITER";
+ field public static final String METADATA_KEY_YEAR = "android.media.metadata.YEAR";
+ }
+
+ public static final class MediaMetadataCompat.Builder {
+ ctor public MediaMetadataCompat.Builder();
+ ctor public MediaMetadataCompat.Builder(android.support.v4.media.MediaMetadataCompat!);
+ method public android.support.v4.media.MediaMetadataCompat! build();
+ method public android.support.v4.media.MediaMetadataCompat.Builder! putBitmap(String!, android.graphics.Bitmap!);
+ method public android.support.v4.media.MediaMetadataCompat.Builder! putLong(String!, long);
+ method public android.support.v4.media.MediaMetadataCompat.Builder! putRating(String!, android.support.v4.media.RatingCompat!);
+ method public android.support.v4.media.MediaMetadataCompat.Builder! putString(String!, String!);
+ method public android.support.v4.media.MediaMetadataCompat.Builder! putText(String!, CharSequence!);
+ }
+
+ public final class RatingCompat implements android.os.Parcelable {
+ method public int describeContents();
+ method public static android.support.v4.media.RatingCompat! fromRating(Object!);
+ method public float getPercentRating();
+ method public Object! getRating();
+ method @android.support.v4.media.RatingCompat.Style public int getRatingStyle();
+ method public float getStarRating();
+ method public boolean hasHeart();
+ method public boolean isRated();
+ method public boolean isThumbUp();
+ method public static android.support.v4.media.RatingCompat! newHeartRating(boolean);
+ method public static android.support.v4.media.RatingCompat! newPercentageRating(float);
+ method public static android.support.v4.media.RatingCompat! newStarRating(@android.support.v4.media.RatingCompat.StarStyle int, float);
+ method public static android.support.v4.media.RatingCompat! newThumbRating(boolean);
+ method public static android.support.v4.media.RatingCompat! newUnratedRating(@android.support.v4.media.RatingCompat.Style int);
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.RatingCompat!>! CREATOR;
+ field public static final int RATING_3_STARS = 3; // 0x3
+ field public static final int RATING_4_STARS = 4; // 0x4
+ field public static final int RATING_5_STARS = 5; // 0x5
+ field public static final int RATING_HEART = 1; // 0x1
+ field public static final int RATING_NONE = 0; // 0x0
+ field public static final int RATING_PERCENTAGE = 6; // 0x6
+ field public static final int RATING_THUMB_UP_DOWN = 2; // 0x2
+ }
+
+
+ @IntDef({android.support.v4.media.RatingCompat.RATING_NONE, android.support.v4.media.RatingCompat.RATING_HEART, android.support.v4.media.RatingCompat.RATING_THUMB_UP_DOWN, android.support.v4.media.RatingCompat.RATING_3_STARS, android.support.v4.media.RatingCompat.RATING_4_STARS, android.support.v4.media.RatingCompat.RATING_5_STARS, android.support.v4.media.RatingCompat.RATING_PERCENTAGE}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface RatingCompat.Style {
+ }
+
+}
+
+package android.support.v4.media.session {
+
+ public final class MediaControllerCompat {
+ ctor public MediaControllerCompat(android.content.Context!, android.support.v4.media.session.MediaSessionCompat);
+ ctor public MediaControllerCompat(android.content.Context!, android.support.v4.media.session.MediaSessionCompat.Token);
+ method public void addQueueItem(android.support.v4.media.MediaDescriptionCompat!);
+ method public void addQueueItem(android.support.v4.media.MediaDescriptionCompat!, int);
+ method public void adjustVolume(int, int);
+ method public boolean dispatchMediaButtonEvent(android.view.KeyEvent!);
+ method public android.os.Bundle! getExtras();
+ method public long getFlags();
+ method public static android.support.v4.media.session.MediaControllerCompat! getMediaController(android.app.Activity);
+ method public Object! getMediaController();
+ method public android.support.v4.media.MediaMetadataCompat! getMetadata();
+ method public String! getPackageName();
+ method public android.support.v4.media.session.MediaControllerCompat.PlaybackInfo! getPlaybackInfo();
+ method public android.support.v4.media.session.PlaybackStateCompat! getPlaybackState();
+ method public java.util.List<android.support.v4.media.session.MediaSessionCompat.QueueItem!>! getQueue();
+ method public CharSequence! getQueueTitle();
+ method public int getRatingType();
+ method public int getRepeatMode();
+ method public android.app.PendingIntent! getSessionActivity();
+ method public android.os.Bundle getSessionInfo();
+ method public android.support.v4.media.session.MediaSessionCompat.Token! getSessionToken();
+ method public int getShuffleMode();
+ method public android.support.v4.media.session.MediaControllerCompat.TransportControls! getTransportControls();
+ method public boolean isCaptioningEnabled();
+ method public boolean isSessionReady();
+ method public void registerCallback(android.support.v4.media.session.MediaControllerCompat.Callback);
+ method public void registerCallback(android.support.v4.media.session.MediaControllerCompat.Callback, android.os.Handler!);
+ method public void removeQueueItem(android.support.v4.media.MediaDescriptionCompat!);
+ method @Deprecated public void removeQueueItemAt(int);
+ method public void sendCommand(String, android.os.Bundle?, android.os.ResultReceiver?);
+ method public static void setMediaController(android.app.Activity, android.support.v4.media.session.MediaControllerCompat!);
+ method public void setVolumeTo(int, int);
+ method public void unregisterCallback(android.support.v4.media.session.MediaControllerCompat.Callback);
+ }
+
+ public abstract static class MediaControllerCompat.Callback implements android.os.IBinder.DeathRecipient {
+ ctor public MediaControllerCompat.Callback();
+ method public void binderDied();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.support.v4.media.session.IMediaControllerCallback! getIControllerCallback();
+ method public void onAudioInfoChanged(android.support.v4.media.session.MediaControllerCompat.PlaybackInfo!);
+ method public void onCaptioningEnabledChanged(boolean);
+ method public void onExtrasChanged(android.os.Bundle!);
+ method public void onMetadataChanged(android.support.v4.media.MediaMetadataCompat!);
+ method public void onPlaybackStateChanged(android.support.v4.media.session.PlaybackStateCompat!);
+ method public void onQueueChanged(java.util.List<android.support.v4.media.session.MediaSessionCompat.QueueItem!>!);
+ method public void onQueueTitleChanged(CharSequence!);
+ method public void onRepeatModeChanged(@android.support.v4.media.session.PlaybackStateCompat.RepeatMode int);
+ method public void onSessionDestroyed();
+ method public void onSessionEvent(String!, android.os.Bundle!);
+ method public void onSessionReady();
+ method public void onShuffleModeChanged(@android.support.v4.media.session.PlaybackStateCompat.ShuffleMode int);
+ }
+
+ public static final class MediaControllerCompat.PlaybackInfo {
+ method public androidx.media.AudioAttributesCompat getAudioAttributes();
+ method @Deprecated public int getAudioStream();
+ method public int getCurrentVolume();
+ method public int getMaxVolume();
+ method public int getPlaybackType();
+ method public int getVolumeControl();
+ field public static final int PLAYBACK_TYPE_LOCAL = 1; // 0x1
+ field public static final int PLAYBACK_TYPE_REMOTE = 2; // 0x2
+ }
+
+ public abstract static class MediaControllerCompat.TransportControls {
+ method public abstract void fastForward();
+ method public abstract void pause();
+ method public abstract void play();
+ method public abstract void playFromMediaId(String!, android.os.Bundle!);
+ method public abstract void playFromSearch(String!, android.os.Bundle!);
+ method public abstract void playFromUri(android.net.Uri!, android.os.Bundle!);
+ method public abstract void prepare();
+ method public abstract void prepareFromMediaId(String!, android.os.Bundle!);
+ method public abstract void prepareFromSearch(String!, android.os.Bundle!);
+ method public abstract void prepareFromUri(android.net.Uri!, android.os.Bundle!);
+ method public abstract void rewind();
+ method public abstract void seekTo(long);
+ method public abstract void sendCustomAction(android.support.v4.media.session.PlaybackStateCompat.CustomAction!, android.os.Bundle!);
+ method public abstract void sendCustomAction(String!, android.os.Bundle!);
+ method public abstract void setCaptioningEnabled(boolean);
+ method public void setPlaybackSpeed(float);
+ method public abstract void setRating(android.support.v4.media.RatingCompat!);
+ method public abstract void setRating(android.support.v4.media.RatingCompat!, android.os.Bundle!);
+ method public abstract void setRepeatMode(@android.support.v4.media.session.PlaybackStateCompat.RepeatMode int);
+ method public abstract void setShuffleMode(@android.support.v4.media.session.PlaybackStateCompat.ShuffleMode int);
+ method public abstract void skipToNext();
+ method public abstract void skipToPrevious();
+ method public abstract void skipToQueueItem(long);
+ method public abstract void stop();
+ field public static final String EXTRA_LEGACY_STREAM_TYPE = "android.media.session.extra.LEGACY_STREAM_TYPE";
+ }
+
+ public class MediaSessionCompat {
+ ctor public MediaSessionCompat(android.content.Context, String);
+ ctor public MediaSessionCompat(android.content.Context, String, android.content.ComponentName?, android.app.PendingIntent?);
+ ctor public MediaSessionCompat(android.content.Context, String, android.content.ComponentName?, android.app.PendingIntent?, android.os.Bundle?);
+ ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public MediaSessionCompat(android.content.Context, String, android.content.ComponentName?, android.app.PendingIntent?, android.os.Bundle?, androidx.versionedparcelable.VersionedParcelable?);
+ method public void addOnActiveChangeListener(android.support.v4.media.session.MediaSessionCompat.OnActiveChangeListener!);
+ method public static android.support.v4.media.session.MediaSessionCompat! fromMediaSession(android.content.Context!, Object!);
+ method public android.support.v4.media.session.MediaControllerCompat! getController();
+ method public final androidx.media.MediaSessionManager.RemoteUserInfo getCurrentControllerInfo();
+ method public Object! getMediaSession();
+ method public Object! getRemoteControlClient();
+ method public android.support.v4.media.session.MediaSessionCompat.Token! getSessionToken();
+ method public boolean isActive();
+ method public void release();
+ method public void removeOnActiveChangeListener(android.support.v4.media.session.MediaSessionCompat.OnActiveChangeListener!);
+ method public void sendSessionEvent(String!, android.os.Bundle!);
+ method public void setActive(boolean);
+ method public void setCallback(android.support.v4.media.session.MediaSessionCompat.Callback!);
+ method public void setCallback(android.support.v4.media.session.MediaSessionCompat.Callback!, android.os.Handler!);
+ method public void setCaptioningEnabled(boolean);
+ method public void setExtras(android.os.Bundle!);
+ method public void setFlags(int);
+ method public void setMediaButtonReceiver(android.app.PendingIntent!);
+ method public void setMetadata(android.support.v4.media.MediaMetadataCompat!);
+ method public void setPlaybackState(android.support.v4.media.session.PlaybackStateCompat!);
+ method public void setPlaybackToLocal(int);
+ method public void setPlaybackToRemote(androidx.media.VolumeProviderCompat!);
+ method public void setQueue(java.util.List<android.support.v4.media.session.MediaSessionCompat.QueueItem!>!);
+ method public void setQueueTitle(CharSequence!);
+ method public void setRatingType(@android.support.v4.media.RatingCompat.Style int);
+ method public void setRepeatMode(@android.support.v4.media.session.PlaybackStateCompat.RepeatMode int);
+ method public void setSessionActivity(android.app.PendingIntent!);
+ method public void setShuffleMode(@android.support.v4.media.session.PlaybackStateCompat.ShuffleMode int);
+ field public static final String ACTION_FLAG_AS_INAPPROPRIATE = "android.support.v4.media.session.action.FLAG_AS_INAPPROPRIATE";
+ field public static final String ACTION_FOLLOW = "android.support.v4.media.session.action.FOLLOW";
+ field public static final String ACTION_SKIP_AD = "android.support.v4.media.session.action.SKIP_AD";
+ field public static final String ACTION_UNFOLLOW = "android.support.v4.media.session.action.UNFOLLOW";
+ field public static final String ARGUMENT_MEDIA_ATTRIBUTE = "android.support.v4.media.session.ARGUMENT_MEDIA_ATTRIBUTE";
+ field public static final String ARGUMENT_MEDIA_ATTRIBUTE_VALUE = "android.support.v4.media.session.ARGUMENT_MEDIA_ATTRIBUTE_VALUE";
+ field @Deprecated public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1; // 0x1
+ field public static final int FLAG_HANDLES_QUEUE_COMMANDS = 4; // 0x4
+ field @Deprecated public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 2; // 0x2
+ field public static final int MEDIA_ATTRIBUTE_ALBUM = 1; // 0x1
+ field public static final int MEDIA_ATTRIBUTE_ARTIST = 0; // 0x0
+ field public static final int MEDIA_ATTRIBUTE_PLAYLIST = 2; // 0x2
+ }
+
+ public abstract static class MediaSessionCompat.Callback {
+ ctor public MediaSessionCompat.Callback();
+ method public void onAddQueueItem(android.support.v4.media.MediaDescriptionCompat!);
+ method public void onAddQueueItem(android.support.v4.media.MediaDescriptionCompat!, int);
+ method public void onCommand(String!, android.os.Bundle!, android.os.ResultReceiver!);
+ method public void onCustomAction(String!, android.os.Bundle!);
+ method public void onFastForward();
+ method public boolean onMediaButtonEvent(android.content.Intent!);
+ method public void onPause();
+ method public void onPlay();
+ method public void onPlayFromMediaId(String!, android.os.Bundle!);
+ method public void onPlayFromSearch(String!, android.os.Bundle!);
+ method public void onPlayFromUri(android.net.Uri!, android.os.Bundle!);
+ method public void onPrepare();
+ method public void onPrepareFromMediaId(String!, android.os.Bundle!);
+ method public void onPrepareFromSearch(String!, android.os.Bundle!);
+ method public void onPrepareFromUri(android.net.Uri!, android.os.Bundle!);
+ method public void onRemoveQueueItem(android.support.v4.media.MediaDescriptionCompat!);
+ method @Deprecated public void onRemoveQueueItemAt(int);
+ method public void onRewind();
+ method public void onSeekTo(long);
+ method public void onSetCaptioningEnabled(boolean);
+ method public void onSetPlaybackSpeed(float);
+ method public void onSetRating(android.support.v4.media.RatingCompat!);
+ method public void onSetRating(android.support.v4.media.RatingCompat!, android.os.Bundle!);
+ method public void onSetRepeatMode(@android.support.v4.media.session.PlaybackStateCompat.RepeatMode int);
+ method public void onSetShuffleMode(@android.support.v4.media.session.PlaybackStateCompat.ShuffleMode int);
+ method public void onSkipToNext();
+ method public void onSkipToPrevious();
+ method public void onSkipToQueueItem(long);
+ method public void onStop();
+ }
+
+ public static interface MediaSessionCompat.OnActiveChangeListener {
+ method public void onActiveChanged();
+ }
+
+ public static final class MediaSessionCompat.QueueItem implements android.os.Parcelable {
+ ctor public MediaSessionCompat.QueueItem(android.support.v4.media.MediaDescriptionCompat!, long);
+ method public int describeContents();
+ method public static android.support.v4.media.session.MediaSessionCompat.QueueItem! fromQueueItem(Object!);
+ method public static java.util.List<android.support.v4.media.session.MediaSessionCompat.QueueItem!>! fromQueueItemList(java.util.List<?>!);
+ method public android.support.v4.media.MediaDescriptionCompat! getDescription();
+ method public long getQueueId();
+ method public Object! getQueueItem();
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.session.MediaSessionCompat.QueueItem!>! CREATOR;
+ field public static final int UNKNOWN_ID = -1; // 0xffffffff
+ }
+
+ public static final class MediaSessionCompat.Token implements android.os.Parcelable {
+ method public int describeContents();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static android.support.v4.media.session.MediaSessionCompat.Token! fromBundle(android.os.Bundle!);
+ method public static android.support.v4.media.session.MediaSessionCompat.Token! fromToken(Object!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.versionedparcelable.VersionedParcelable! getSession2Token();
+ method public Object! getToken();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setSession2Token(androidx.versionedparcelable.VersionedParcelable!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.os.Bundle! toBundle();
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.session.MediaSessionCompat.Token!>! CREATOR;
+ }
+
+ public class ParcelableVolumeInfo implements android.os.Parcelable {
+ ctor public ParcelableVolumeInfo(int, int, int, int, int);
+ ctor public ParcelableVolumeInfo(android.os.Parcel!);
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.session.ParcelableVolumeInfo!>! CREATOR;
+ field public int audioStream;
+ field public int controlType;
+ field public int currentVolume;
+ field public int maxVolume;
+ field public int volumeType;
+ }
+
+ public final class PlaybackStateCompat implements android.os.Parcelable {
+ method public int describeContents();
+ method public static android.support.v4.media.session.PlaybackStateCompat! fromPlaybackState(Object!);
+ method @android.support.v4.media.session.PlaybackStateCompat.Actions public long getActions();
+ method public long getActiveQueueItemId();
+ method public long getBufferedPosition();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public long getCurrentPosition(Long!);
+ method public java.util.List<android.support.v4.media.session.PlaybackStateCompat.CustomAction!>! getCustomActions();
+ method public int getErrorCode();
+ method public CharSequence! getErrorMessage();
+ method public android.os.Bundle? getExtras();
+ method public long getLastPositionUpdateTime();
+ method public float getPlaybackSpeed();
+ method public Object! getPlaybackState();
+ method public long getPosition();
+ method @android.support.v4.media.session.PlaybackStateCompat.State public int getState();
+ method public static int toKeyCode(@android.support.v4.media.session.PlaybackStateCompat.MediaKeyAction long);
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final long ACTION_FAST_FORWARD = 64L; // 0x40L
+ field public static final long ACTION_PAUSE = 2L; // 0x2L
+ field public static final long ACTION_PLAY = 4L; // 0x4L
+ field public static final long ACTION_PLAY_FROM_MEDIA_ID = 1024L; // 0x400L
+ field public static final long ACTION_PLAY_FROM_SEARCH = 2048L; // 0x800L
+ field public static final long ACTION_PLAY_FROM_URI = 8192L; // 0x2000L
+ field public static final long ACTION_PLAY_PAUSE = 512L; // 0x200L
+ field public static final long ACTION_PREPARE = 16384L; // 0x4000L
+ field public static final long ACTION_PREPARE_FROM_MEDIA_ID = 32768L; // 0x8000L
+ field public static final long ACTION_PREPARE_FROM_SEARCH = 65536L; // 0x10000L
+ field public static final long ACTION_PREPARE_FROM_URI = 131072L; // 0x20000L
+ field public static final long ACTION_REWIND = 8L; // 0x8L
+ field public static final long ACTION_SEEK_TO = 256L; // 0x100L
+ field public static final long ACTION_SET_CAPTIONING_ENABLED = 1048576L; // 0x100000L
+ field public static final long ACTION_SET_RATING = 128L; // 0x80L
+ field public static final long ACTION_SET_REPEAT_MODE = 262144L; // 0x40000L
+ field public static final long ACTION_SET_SHUFFLE_MODE = 2097152L; // 0x200000L
+ field @Deprecated public static final long ACTION_SET_SHUFFLE_MODE_ENABLED = 524288L; // 0x80000L
+ field public static final long ACTION_SKIP_TO_NEXT = 32L; // 0x20L
+ field public static final long ACTION_SKIP_TO_PREVIOUS = 16L; // 0x10L
+ field public static final long ACTION_SKIP_TO_QUEUE_ITEM = 4096L; // 0x1000L
+ field public static final long ACTION_STOP = 1L; // 0x1L
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.session.PlaybackStateCompat!>! CREATOR;
+ field public static final int ERROR_CODE_ACTION_ABORTED = 10; // 0xa
+ field public static final int ERROR_CODE_APP_ERROR = 1; // 0x1
+ field public static final int ERROR_CODE_AUTHENTICATION_EXPIRED = 3; // 0x3
+ field public static final int ERROR_CODE_CONCURRENT_STREAM_LIMIT = 5; // 0x5
+ field public static final int ERROR_CODE_CONTENT_ALREADY_PLAYING = 8; // 0x8
+ field public static final int ERROR_CODE_END_OF_QUEUE = 11; // 0xb
+ field public static final int ERROR_CODE_NOT_AVAILABLE_IN_REGION = 7; // 0x7
+ field public static final int ERROR_CODE_NOT_SUPPORTED = 2; // 0x2
+ field public static final int ERROR_CODE_PARENTAL_CONTROL_RESTRICTED = 6; // 0x6
+ field public static final int ERROR_CODE_PREMIUM_ACCOUNT_REQUIRED = 4; // 0x4
+ field public static final int ERROR_CODE_SKIP_LIMIT_REACHED = 9; // 0x9
+ field public static final int ERROR_CODE_UNKNOWN_ERROR = 0; // 0x0
+ field public static final long PLAYBACK_POSITION_UNKNOWN = -1L; // 0xffffffffffffffffL
+ field public static final int REPEAT_MODE_ALL = 2; // 0x2
+ field public static final int REPEAT_MODE_GROUP = 3; // 0x3
+ field public static final int REPEAT_MODE_INVALID = -1; // 0xffffffff
+ field public static final int REPEAT_MODE_NONE = 0; // 0x0
+ field public static final int REPEAT_MODE_ONE = 1; // 0x1
+ field public static final int SHUFFLE_MODE_ALL = 1; // 0x1
+ field public static final int SHUFFLE_MODE_GROUP = 2; // 0x2
+ field public static final int SHUFFLE_MODE_INVALID = -1; // 0xffffffff
+ field public static final int SHUFFLE_MODE_NONE = 0; // 0x0
+ field public static final int STATE_BUFFERING = 6; // 0x6
+ field public static final int STATE_CONNECTING = 8; // 0x8
+ field public static final int STATE_ERROR = 7; // 0x7
+ field public static final int STATE_FAST_FORWARDING = 4; // 0x4
+ field public static final int STATE_NONE = 0; // 0x0
+ field public static final int STATE_PAUSED = 2; // 0x2
+ field public static final int STATE_PLAYING = 3; // 0x3
+ field public static final int STATE_REWINDING = 5; // 0x5
+ field public static final int STATE_SKIPPING_TO_NEXT = 10; // 0xa
+ field public static final int STATE_SKIPPING_TO_PREVIOUS = 9; // 0x9
+ field public static final int STATE_SKIPPING_TO_QUEUE_ITEM = 11; // 0xb
+ field public static final int STATE_STOPPED = 1; // 0x1
+ }
+
+ @LongDef(flag=true, value={android.support.v4.media.session.PlaybackStateCompat.ACTION_STOP, android.support.v4.media.session.PlaybackStateCompat.ACTION_PAUSE, android.support.v4.media.session.PlaybackStateCompat.ACTION_PLAY, android.support.v4.media.session.PlaybackStateCompat.ACTION_REWIND, android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS, android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_NEXT, android.support.v4.media.session.PlaybackStateCompat.ACTION_FAST_FORWARD, android.support.v4.media.session.PlaybackStateCompat.ACTION_SET_RATING, android.support.v4.media.session.PlaybackStateCompat.ACTION_SEEK_TO, android.support.v4.media.session.PlaybackStateCompat.ACTION_PLAY_PAUSE, android.support.v4.media.session.PlaybackStateCompat.ACTION_PLAY_FROM_MEDIA_ID, android.support.v4.media.session.PlaybackStateCompat.ACTION_PLAY_FROM_SEARCH, android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM, android.support.v4.media.session.PlaybackStateCompat.ACTION_PLAY_FROM_URI, android.support.v4.media.session.PlaybackStateCompat.ACTION_PREPARE, android.support.v4.media.session.PlaybackStateCompat.ACTION_PREPARE_FROM_MEDIA_ID, android.support.v4.media.session.PlaybackStateCompat.ACTION_PREPARE_FROM_SEARCH, android.support.v4.media.session.PlaybackStateCompat.ACTION_PREPARE_FROM_URI, android.support.v4.media.session.PlaybackStateCompat.ACTION_SET_REPEAT_MODE, android.support.v4.media.session.PlaybackStateCompat.ACTION_SET_SHUFFLE_MODE, android.support.v4.media.session.PlaybackStateCompat.ACTION_SET_CAPTIONING_ENABLED}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PlaybackStateCompat.Actions {
+ }
+
+ public static final class PlaybackStateCompat.Builder {
+ ctor public PlaybackStateCompat.Builder();
+ ctor public PlaybackStateCompat.Builder(android.support.v4.media.session.PlaybackStateCompat!);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! addCustomAction(String!, String!, int);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! addCustomAction(android.support.v4.media.session.PlaybackStateCompat.CustomAction!);
+ method public android.support.v4.media.session.PlaybackStateCompat! build();
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! setActions(@android.support.v4.media.session.PlaybackStateCompat.Actions long);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! setActiveQueueItemId(long);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! setBufferedPosition(long);
+ method @Deprecated public android.support.v4.media.session.PlaybackStateCompat.Builder! setErrorMessage(CharSequence!);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! setErrorMessage(int, CharSequence!);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! setExtras(android.os.Bundle!);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! setState(@android.support.v4.media.session.PlaybackStateCompat.State int, long, float);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! setState(@android.support.v4.media.session.PlaybackStateCompat.State int, long, float, long);
+ }
+
+ public static final class PlaybackStateCompat.CustomAction implements android.os.Parcelable {
+ method public int describeContents();
+ method public static android.support.v4.media.session.PlaybackStateCompat.CustomAction! fromCustomAction(Object!);
+ method public String! getAction();
+ method public Object! getCustomAction();
+ method public android.os.Bundle! getExtras();
+ method public int getIcon();
+ method public CharSequence! getName();
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.session.PlaybackStateCompat.CustomAction!>! CREATOR;
+ }
+
+ public static final class PlaybackStateCompat.CustomAction.Builder {
+ ctor public PlaybackStateCompat.CustomAction.Builder(String!, CharSequence!, int);
+ method public android.support.v4.media.session.PlaybackStateCompat.CustomAction! build();
+ method public android.support.v4.media.session.PlaybackStateCompat.CustomAction.Builder! setExtras(android.os.Bundle!);
+ }
+
+
+ @IntDef({android.support.v4.media.session.PlaybackStateCompat.REPEAT_MODE_INVALID, android.support.v4.media.session.PlaybackStateCompat.REPEAT_MODE_NONE, android.support.v4.media.session.PlaybackStateCompat.REPEAT_MODE_ONE, android.support.v4.media.session.PlaybackStateCompat.REPEAT_MODE_ALL, android.support.v4.media.session.PlaybackStateCompat.REPEAT_MODE_GROUP}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PlaybackStateCompat.RepeatMode {
+ }
+
+ @IntDef({android.support.v4.media.session.PlaybackStateCompat.SHUFFLE_MODE_INVALID, android.support.v4.media.session.PlaybackStateCompat.SHUFFLE_MODE_NONE, android.support.v4.media.session.PlaybackStateCompat.SHUFFLE_MODE_ALL, android.support.v4.media.session.PlaybackStateCompat.SHUFFLE_MODE_GROUP}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PlaybackStateCompat.ShuffleMode {
+ }
+
+ @IntDef({android.support.v4.media.session.PlaybackStateCompat.STATE_NONE, android.support.v4.media.session.PlaybackStateCompat.STATE_STOPPED, android.support.v4.media.session.PlaybackStateCompat.STATE_PAUSED, android.support.v4.media.session.PlaybackStateCompat.STATE_PLAYING, android.support.v4.media.session.PlaybackStateCompat.STATE_FAST_FORWARDING, android.support.v4.media.session.PlaybackStateCompat.STATE_REWINDING, android.support.v4.media.session.PlaybackStateCompat.STATE_BUFFERING, android.support.v4.media.session.PlaybackStateCompat.STATE_ERROR, android.support.v4.media.session.PlaybackStateCompat.STATE_CONNECTING, android.support.v4.media.session.PlaybackStateCompat.STATE_SKIPPING_TO_PREVIOUS, android.support.v4.media.session.PlaybackStateCompat.STATE_SKIPPING_TO_NEXT, android.support.v4.media.session.PlaybackStateCompat.STATE_SKIPPING_TO_QUEUE_ITEM}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PlaybackStateCompat.State {
+ }
+
+}
+
+package androidx.media {
+
+ public class AudioAttributesCompat implements androidx.versionedparcelable.VersionedParcelable {
+ method public int getContentType();
+ method public int getFlags();
+ method public int getLegacyStreamType();
+ method @androidx.media.AudioAttributesCompat.AttributeUsage public int getUsage();
+ method public int getVolumeControlStream();
+ method public Object? unwrap();
+ method public static androidx.media.AudioAttributesCompat? wrap(Object);
+ field public static final int CONTENT_TYPE_MOVIE = 3; // 0x3
+ field public static final int CONTENT_TYPE_MUSIC = 2; // 0x2
+ field public static final int CONTENT_TYPE_SONIFICATION = 4; // 0x4
+ field public static final int CONTENT_TYPE_SPEECH = 1; // 0x1
+ field public static final int CONTENT_TYPE_UNKNOWN = 0; // 0x0
+ field public static final int FLAG_AUDIBILITY_ENFORCED = 1; // 0x1
+ field public static final int FLAG_HW_AV_SYNC = 16; // 0x10
+ field public static final int USAGE_ALARM = 4; // 0x4
+ field public static final int USAGE_ASSISTANCE_ACCESSIBILITY = 11; // 0xb
+ field public static final int USAGE_ASSISTANCE_NAVIGATION_GUIDANCE = 12; // 0xc
+ field public static final int USAGE_ASSISTANCE_SONIFICATION = 13; // 0xd
+ field public static final int USAGE_ASSISTANT = 16; // 0x10
+ field public static final int USAGE_GAME = 14; // 0xe
+ field public static final int USAGE_MEDIA = 1; // 0x1
+ field public static final int USAGE_NOTIFICATION = 5; // 0x5
+ field public static final int USAGE_NOTIFICATION_COMMUNICATION_DELAYED = 9; // 0x9
+ field public static final int USAGE_NOTIFICATION_COMMUNICATION_INSTANT = 8; // 0x8
+ field public static final int USAGE_NOTIFICATION_COMMUNICATION_REQUEST = 7; // 0x7
+ field public static final int USAGE_NOTIFICATION_EVENT = 10; // 0xa
+ field public static final int USAGE_NOTIFICATION_RINGTONE = 6; // 0x6
+ field public static final int USAGE_UNKNOWN = 0; // 0x0
+ field public static final int USAGE_VOICE_COMMUNICATION = 2; // 0x2
+ field public static final int USAGE_VOICE_COMMUNICATION_SIGNALLING = 3; // 0x3
+ }
+
+
+
+ public static class AudioAttributesCompat.Builder {
+ ctor public AudioAttributesCompat.Builder();
+ ctor public AudioAttributesCompat.Builder(androidx.media.AudioAttributesCompat!);
+ method public androidx.media.AudioAttributesCompat! build();
+ method public androidx.media.AudioAttributesCompat.Builder! setContentType(@androidx.media.AudioAttributesCompat.AttributeContentType int);
+ method public androidx.media.AudioAttributesCompat.Builder! setFlags(int);
+ method public androidx.media.AudioAttributesCompat.Builder! setLegacyStreamType(int);
+ method public androidx.media.AudioAttributesCompat.Builder! setUsage(@androidx.media.AudioAttributesCompat.AttributeUsage int);
+ }
+
+
+
+
+ public class AudioFocusRequestCompat {
+ method public androidx.media.AudioAttributesCompat getAudioAttributesCompat();
+ method public android.os.Handler getFocusChangeHandler();
+ method public int getFocusGain();
+ method public android.media.AudioManager.OnAudioFocusChangeListener getOnAudioFocusChangeListener();
+ method public boolean willPauseWhenDucked();
+ }
+
+ public static final class AudioFocusRequestCompat.Builder {
+ ctor public AudioFocusRequestCompat.Builder(int);
+ ctor public AudioFocusRequestCompat.Builder(androidx.media.AudioFocusRequestCompat);
+ method public androidx.media.AudioFocusRequestCompat! build();
+ method public androidx.media.AudioFocusRequestCompat.Builder setAudioAttributes(androidx.media.AudioAttributesCompat);
+ method public androidx.media.AudioFocusRequestCompat.Builder setFocusGain(int);
+ method public androidx.media.AudioFocusRequestCompat.Builder setOnAudioFocusChangeListener(android.media.AudioManager.OnAudioFocusChangeListener);
+ method public androidx.media.AudioFocusRequestCompat.Builder setOnAudioFocusChangeListener(android.media.AudioManager.OnAudioFocusChangeListener, android.os.Handler);
+ method public androidx.media.AudioFocusRequestCompat.Builder setWillPauseWhenDucked(boolean);
+ }
+
+ public final class AudioManagerCompat {
+ method public static int abandonAudioFocusRequest(android.media.AudioManager, androidx.media.AudioFocusRequestCompat);
+ method public static int requestAudioFocus(android.media.AudioManager, androidx.media.AudioFocusRequestCompat);
+ field public static final int AUDIOFOCUS_GAIN = 1; // 0x1
+ field public static final int AUDIOFOCUS_GAIN_TRANSIENT = 2; // 0x2
+ field public static final int AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE = 4; // 0x4
+ field public static final int AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK = 3; // 0x3
+ }
+
+
+
+ public abstract class MediaBrowserServiceCompat extends android.app.Service {
+ ctor public MediaBrowserServiceCompat();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void attachToBaseContext(android.content.Context!);
+ method public void dump(java.io.FileDescriptor!, java.io.PrintWriter!, String![]!);
+ method public final android.os.Bundle! getBrowserRootHints();
+ method public final androidx.media.MediaSessionManager.RemoteUserInfo getCurrentBrowserInfo();
+ method public android.support.v4.media.session.MediaSessionCompat.Token? getSessionToken();
+ method public void notifyChildrenChanged(String);
+ method public void notifyChildrenChanged(String, android.os.Bundle);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void notifyChildrenChanged(androidx.media.MediaSessionManager.RemoteUserInfo, String, android.os.Bundle);
+ method public android.os.IBinder! onBind(android.content.Intent!);
+ method public void onCustomAction(String, android.os.Bundle!, androidx.media.MediaBrowserServiceCompat.Result<android.os.Bundle!>);
+ method public abstract androidx.media.MediaBrowserServiceCompat.BrowserRoot? onGetRoot(String, int, android.os.Bundle?);
+ method public abstract void onLoadChildren(String, androidx.media.MediaBrowserServiceCompat.Result<java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem!>!>);
+ method public void onLoadChildren(String, androidx.media.MediaBrowserServiceCompat.Result<java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem!>!>, android.os.Bundle);
+ method public void onLoadItem(String!, androidx.media.MediaBrowserServiceCompat.Result<android.support.v4.media.MediaBrowserCompat.MediaItem!>);
+ method public void onSearch(String, android.os.Bundle!, androidx.media.MediaBrowserServiceCompat.Result<java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem!>!>);
+ method public void setSessionToken(android.support.v4.media.session.MediaSessionCompat.Token!);
+ field public static final String SERVICE_INTERFACE = "android.media.browse.MediaBrowserService";
+ }
+
+ public static final class MediaBrowserServiceCompat.BrowserRoot {
+ ctor public MediaBrowserServiceCompat.BrowserRoot(String, android.os.Bundle?);
+ method public android.os.Bundle! getExtras();
+ method public String! getRootId();
+ field public static final String EXTRA_OFFLINE = "android.service.media.extra.OFFLINE";
+ field public static final String EXTRA_RECENT = "android.service.media.extra.RECENT";
+ field public static final String EXTRA_SUGGESTED = "android.service.media.extra.SUGGESTED";
+ field @Deprecated public static final String EXTRA_SUGGESTION_KEYWORDS = "android.service.media.extra.SUGGESTION_KEYWORDS";
+ }
+
+ public static class MediaBrowserServiceCompat.Result<T> {
+ method public void detach();
+ method public void sendError(android.os.Bundle!);
+ method public void sendProgressUpdate(android.os.Bundle!);
+ method public void sendResult(T!);
+ }
+
+ public final class MediaSessionManager {
+ method public static androidx.media.MediaSessionManager getSessionManager(android.content.Context);
+ method public boolean isTrustedForMediaControl(androidx.media.MediaSessionManager.RemoteUserInfo);
+ }
+
+ public static final class MediaSessionManager.RemoteUserInfo {
+ ctor public MediaSessionManager.RemoteUserInfo(String, int, int);
+ method public String getPackageName();
+ method public int getPid();
+ method public int getUid();
+ field public static final String LEGACY_CONTROLLER = "android.media.session.MediaController";
+ }
+
+ public abstract class VolumeProviderCompat {
+ ctor public VolumeProviderCompat(@androidx.media.VolumeProviderCompat.ControlType int, int, int);
+ method public final int getCurrentVolume();
+ method public final int getMaxVolume();
+ method @androidx.media.VolumeProviderCompat.ControlType public final int getVolumeControl();
+ method public Object! getVolumeProvider();
+ method public void onAdjustVolume(int);
+ method public void onSetVolumeTo(int);
+ method public void setCallback(androidx.media.VolumeProviderCompat.Callback!);
+ method public final void setCurrentVolume(int);
+ field public static final int VOLUME_CONTROL_ABSOLUTE = 2; // 0x2
+ field public static final int VOLUME_CONTROL_FIXED = 0; // 0x0
+ field public static final int VOLUME_CONTROL_RELATIVE = 1; // 0x1
+ }
+
+ public abstract static class VolumeProviderCompat.Callback {
+ ctor public VolumeProviderCompat.Callback();
+ method public abstract void onVolumeChanged(androidx.media.VolumeProviderCompat!);
+ }
+
+ @IntDef({androidx.media.VolumeProviderCompat.VOLUME_CONTROL_FIXED, androidx.media.VolumeProviderCompat.VOLUME_CONTROL_RELATIVE, androidx.media.VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface VolumeProviderCompat.ControlType {
+ }
+
+}
+
+package androidx.media.app {
+
+ public class NotificationCompat {
+ }
+
+ public static class NotificationCompat.DecoratedMediaCustomViewStyle extends androidx.media.app.NotificationCompat.MediaStyle {
+ ctor public NotificationCompat.DecoratedMediaCustomViewStyle();
+ }
+
+ public static class NotificationCompat.MediaStyle extends androidx.core.app.NotificationCompat.Style {
+ ctor public NotificationCompat.MediaStyle();
+ ctor public NotificationCompat.MediaStyle(androidx.core.app.NotificationCompat.Builder!);
+ method public static android.support.v4.media.session.MediaSessionCompat.Token! getMediaSession(android.app.Notification!);
+ method public androidx.media.app.NotificationCompat.MediaStyle! setCancelButtonIntent(android.app.PendingIntent!);
+ method public androidx.media.app.NotificationCompat.MediaStyle! setMediaSession(android.support.v4.media.session.MediaSessionCompat.Token!);
+ method public androidx.media.app.NotificationCompat.MediaStyle! setShowActionsInCompactView(int...);
+ method public androidx.media.app.NotificationCompat.MediaStyle! setShowCancelButton(boolean);
+ }
+
+}
+
+package androidx.media.session {
+
+ public class MediaButtonReceiver extends android.content.BroadcastReceiver {
+ ctor public MediaButtonReceiver();
+ method public static android.app.PendingIntent! buildMediaButtonPendingIntent(android.content.Context!, @android.support.v4.media.session.PlaybackStateCompat.MediaKeyAction long);
+ method public static android.app.PendingIntent! buildMediaButtonPendingIntent(android.content.Context!, android.content.ComponentName!, @android.support.v4.media.session.PlaybackStateCompat.MediaKeyAction long);
+ method public static android.view.KeyEvent! handleIntent(android.support.v4.media.session.MediaSessionCompat!, android.content.Intent!);
+ method public void onReceive(android.content.Context!, android.content.Intent!);
+ }
+
+}
+
diff --git a/media/build.gradle b/media/build.gradle
index f0a49ef..913a501 100644
--- a/media/build.gradle
+++ b/media/build.gradle
@@ -40,5 +40,4 @@
inceptionYear = "2011"
description = "The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren't a part of the framework APIs. Compatible on devices running API 14 or later."
failOnDeprecationWarnings = false
- trackRestrictedAPIs = false // TODO: Remove it (b/131031933)
}
diff --git a/media/src/main/java/android/support/v4/media/MediaBrowserCompat.java b/media/src/main/java/android/support/v4/media/MediaBrowserCompat.java
index eddc7a8..4ec5200 100644
--- a/media/src/main/java/android/support/v4/media/MediaBrowserCompat.java
+++ b/media/src/main/java/android/support/v4/media/MediaBrowserCompat.java
@@ -446,7 +446,7 @@
* String, Bundle)}
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY_GROUP_PREFIX) // accessed by media2-session
@Nullable
public Bundle getNotifyChildrenChangedOptions() {
return mImpl.getNotifyChildrenChangedOptions();
@@ -462,11 +462,9 @@
private final int mFlags;
private final MediaDescriptionCompat mDescription;
- /** @hide */
- @RestrictTo(LIBRARY_GROUP_PREFIX)
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag=true, value = { FLAG_BROWSABLE, FLAG_PLAYABLE })
- public @interface Flags { }
+ private @interface Flags { }
/**
* Flag: Indicates that the item has children of its own.
diff --git a/media/src/main/java/android/support/v4/media/MediaDescriptionCompat.java b/media/src/main/java/android/support/v4/media/MediaDescriptionCompat.java
index e34708f..a4734db 100644
--- a/media/src/main/java/android/support/v4/media/MediaDescriptionCompat.java
+++ b/media/src/main/java/android/support/v4/media/MediaDescriptionCompat.java
@@ -15,7 +15,7 @@
*/
package android.support.v4.media;
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
import android.annotation.SuppressLint;
import android.graphics.Bitmap;
@@ -140,7 +140,7 @@
*
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
public static final String DESCRIPTION_KEY_MEDIA_URI =
"android.support.v4.media.description.MEDIA_URI";
/**
@@ -148,7 +148,7 @@
*
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
public static final String DESCRIPTION_KEY_NULL_BUNDLE_FLAG =
"android.support.v4.media.description.NULL_BUNDLE_FLAG";
/**
diff --git a/media/src/main/java/android/support/v4/media/MediaMetadataCompat.java b/media/src/main/java/android/support/v4/media/MediaMetadataCompat.java
index bf8b780..96c192c 100644
--- a/media/src/main/java/android/support/v4/media/MediaMetadataCompat.java
+++ b/media/src/main/java/android/support/v4/media/MediaMetadataCompat.java
@@ -15,7 +15,7 @@
*/
package android.support.v4.media;
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
import android.annotation.SuppressLint;
import android.graphics.Bitmap;
@@ -262,10 +262,6 @@
public static final String METADATA_KEY_DOWNLOAD_STATUS =
"android.media.metadata.DOWNLOAD_STATUS";
- /**
- * @hide
- */
- @RestrictTo(LIBRARY_GROUP_PREFIX)
@StringDef(value =
{METADATA_KEY_TITLE, METADATA_KEY_ARTIST, METADATA_KEY_ALBUM, METADATA_KEY_AUTHOR,
METADATA_KEY_WRITER, METADATA_KEY_COMPOSER, METADATA_KEY_COMPILATION,
@@ -275,35 +271,23 @@
METADATA_KEY_MEDIA_ID, METADATA_KEY_MEDIA_URI},
open = true)
@Retention(RetentionPolicy.SOURCE)
- public @interface TextKey {}
+ private @interface TextKey {}
- /**
- * @hide
- */
- @RestrictTo(LIBRARY_GROUP_PREFIX)
@StringDef(value =
{METADATA_KEY_DURATION, METADATA_KEY_YEAR, METADATA_KEY_TRACK_NUMBER,
METADATA_KEY_NUM_TRACKS, METADATA_KEY_DISC_NUMBER, METADATA_KEY_BT_FOLDER_TYPE,
METADATA_KEY_ADVERTISEMENT, METADATA_KEY_DOWNLOAD_STATUS},
open = true)
@Retention(RetentionPolicy.SOURCE)
- public @interface LongKey {}
+ private @interface LongKey {}
- /**
- * @hide
- */
- @RestrictTo(LIBRARY_GROUP_PREFIX)
@StringDef({METADATA_KEY_ART, METADATA_KEY_ALBUM_ART, METADATA_KEY_DISPLAY_ICON})
@Retention(RetentionPolicy.SOURCE)
- public @interface BitmapKey {}
+ private @interface BitmapKey {}
- /**
- * @hide
- */
- @RestrictTo(LIBRARY_GROUP_PREFIX)
@StringDef({METADATA_KEY_USER_RATING, METADATA_KEY_RATING})
@Retention(RetentionPolicy.SOURCE)
- public @interface RatingKey {}
+ private @interface RatingKey {}
static final int METADATA_TYPE_LONG = 0;
static final int METADATA_TYPE_TEXT = 1;
@@ -695,7 +679,7 @@
* in the metadata.
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
public Builder(MediaMetadataCompat source, int maxBitmapSize) {
this(source);
for (String key : mBundle.keySet()) {
diff --git a/media/src/main/java/android/support/v4/media/RatingCompat.java b/media/src/main/java/android/support/v4/media/RatingCompat.java
index d109647..42b6da0 100644
--- a/media/src/main/java/android/support/v4/media/RatingCompat.java
+++ b/media/src/main/java/android/support/v4/media/RatingCompat.java
@@ -16,6 +16,7 @@
package android.support.v4.media;
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
import android.annotation.SuppressLint;
@@ -46,7 +47,7 @@
/**
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY_GROUP_PREFIX) // used by media2-session
@IntDef({RATING_NONE, RATING_HEART, RATING_THUMB_UP_DOWN, RATING_3_STARS, RATING_4_STARS,
RATING_5_STARS, RATING_PERCENTAGE})
@Retention(RetentionPolicy.SOURCE)
@@ -55,7 +56,7 @@
/**
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
@IntDef({RATING_3_STARS, RATING_4_STARS, RATING_5_STARS})
@Retention(RetentionPolicy.SOURCE)
public @interface StarStyle {}
diff --git a/media/src/main/java/android/support/v4/media/session/MediaControllerCompat.java b/media/src/main/java/android/support/v4/media/session/MediaControllerCompat.java
index 8f43365..7ed8a4a 100644
--- a/media/src/main/java/android/support/v4/media/session/MediaControllerCompat.java
+++ b/media/src/main/java/android/support/v4/media/session/MediaControllerCompat.java
@@ -53,8 +53,8 @@
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.core.app.BundleCompat;
-import androidx.core.app.ComponentActivity;
import androidx.media.AudioAttributesCompat;
+import androidx.media.R;
import androidx.media.VolumeProviderCompat;
import androidx.versionedparcelable.ParcelUtils;
import androidx.versionedparcelable.VersionedParcelable;
@@ -144,26 +144,11 @@
public static final String COMMAND_ARGUMENT_INDEX =
"android.support.v4.media.session.command.ARGUMENT_INDEX";
- private static class MediaControllerExtraData extends ComponentActivity.ExtraData {
- private final MediaControllerCompat mMediaController;
-
- MediaControllerExtraData(MediaControllerCompat mediaController) {
- mMediaController = mediaController;
- }
-
- MediaControllerCompat getMediaController() {
- return mMediaController;
- }
- }
-
/**
* Sets a {@link MediaControllerCompat} in the {@code activity} for later retrieval via
* {@link #getMediaController(Activity)}.
*
- * <p>This is compatible with {@link Activity#setMediaController(MediaController)}.
- * If {@code activity} inherits {@link androidx.fragment.app.FragmentActivity}, the
- * {@code mediaController} will be saved in the {@code activity}. In addition to that,
- * on API 21 and later, {@link Activity#setMediaController(MediaController)} will be
+ * <p>On API 21 and later, {@link Activity#setMediaController(MediaController)} will also be
* called.</p>
*
* @param activity The activity to set the {@code mediaController} in, must not be null.
@@ -174,10 +159,8 @@
*/
public static void setMediaController(@NonNull Activity activity,
MediaControllerCompat mediaController) {
- if (activity instanceof ComponentActivity) {
- ((ComponentActivity) activity).putExtraData(
- new MediaControllerExtraData(mediaController));
- }
+ activity.getWindow().getDecorView().setTag(
+ R.id.media_controller_compat_view_tag, mediaController);
if (android.os.Build.VERSION.SDK_INT >= 21) {
MediaController controllerFwk = null;
if (mediaController != null) {
@@ -200,10 +183,10 @@
* @see #setMediaController(Activity, MediaControllerCompat)
*/
public static MediaControllerCompat getMediaController(@NonNull Activity activity) {
- if (activity instanceof ComponentActivity) {
- MediaControllerExtraData extraData =
- ((ComponentActivity) activity).getExtraData(MediaControllerExtraData.class);
- return extraData != null ? extraData.getMediaController() : null;
+ Object tag = activity.getWindow().getDecorView()
+ .getTag(R.id.media_controller_compat_view_tag);
+ if (tag instanceof MediaControllerCompat) {
+ return (MediaControllerCompat) tag;
} else if (android.os.Build.VERSION.SDK_INT >= 21) {
MediaController controllerFwk = activity.getMediaController();
if (controllerFwk == null) {
@@ -531,7 +514,7 @@
* @return The session's token as VersionedParcelable.
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
@Nullable
public VersionedParcelable getSession2Token() {
return mToken.getSession2Token();
@@ -832,7 +815,7 @@
/**
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY_GROUP_PREFIX) // accessed by media2-session
public IMediaControllerCallback getIControllerCallback() {
return mIControllerCallback;
}
diff --git a/media/src/main/java/android/support/v4/media/session/MediaSessionCompat.java b/media/src/main/java/android/support/v4/media/session/MediaSessionCompat.java
index e54467b9..bbf6980 100644
--- a/media/src/main/java/android/support/v4/media/session/MediaSessionCompat.java
+++ b/media/src/main/java/android/support/v4/media/session/MediaSessionCompat.java
@@ -125,16 +125,12 @@
private final MediaControllerCompat mController;
private final ArrayList<OnActiveChangeListener> mActiveListeners = new ArrayList<>();
- /**
- * @hide
- */
- @RestrictTo(LIBRARY_GROUP_PREFIX)
@IntDef(flag=true, value={
FLAG_HANDLES_MEDIA_BUTTONS,
FLAG_HANDLES_TRANSPORT_CONTROLS,
FLAG_HANDLES_QUEUE_COMMANDS })
@Retention(RetentionPolicy.SOURCE)
- public @interface SessionFlags {}
+ private @interface SessionFlags {}
/**
* Sets this flag on the session to indicate that it can handle media button
@@ -422,7 +418,7 @@
/**
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
public static final String KEY_TOKEN = "android.support.v4.media.session.TOKEN";
/**
@@ -435,7 +431,7 @@
/**
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
public static final String KEY_SESSION2_TOKEN =
"android.support.v4.media.session.SESSION_TOKEN2";
@@ -533,7 +529,7 @@
/**
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY_GROUP_PREFIX) // accessed by media2-session
public MediaSessionCompat(@NonNull Context context, @NonNull String tag,
@Nullable ComponentName mbrComponent, @Nullable PendingIntent mbrIntent,
@Nullable Bundle sessionInfo, @Nullable VersionedParcelable session2Token) {
@@ -964,7 +960,7 @@
*
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
public String getCallingPackage() {
return mImpl.getCallingPackage();
}
@@ -1031,11 +1027,8 @@
/**
* Returns whether the given bundle includes non-framework Parcelables.
- *
- * @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
- public static boolean doesBundleHaveCustomParcelable(@Nullable Bundle bundle) {
+ static boolean doesBundleHaveCustomParcelable(@Nullable Bundle bundle) {
if (bundle == null) {
return false;
}
@@ -1066,7 +1059,7 @@
*
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
public static void ensureClassLoader(@Nullable Bundle bundle) {
if (bundle != null) {
bundle.setClassLoader(MediaSessionCompat.class.getClassLoader());
@@ -1832,7 +1825,7 @@
* @return A compat Token for use with {@link MediaControllerCompat}.
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
public static Token fromToken(Object token, IMediaSession extraBinder) {
if (token != null && android.os.Build.VERSION.SDK_INT >= 21) {
if (!(token instanceof MediaSession.Token)) {
@@ -1901,7 +1894,7 @@
/**
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
public IMediaSession getExtraBinder() {
return mExtraBinder;
}
@@ -1909,7 +1902,7 @@
/**
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
public void setExtraBinder(IMediaSession extraBinder) {
mExtraBinder = extraBinder;
}
@@ -1917,7 +1910,7 @@
/**
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY_GROUP_PREFIX) // accessed by media2-session
public VersionedParcelable getSession2Token() {
return mSession2Token;
}
@@ -1925,7 +1918,7 @@
/**
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY_GROUP_PREFIX) // accessed by media2-session
public void setSession2Token(VersionedParcelable session2Token) {
mSession2Token = session2Token;
}
@@ -1933,7 +1926,7 @@
/**
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY_GROUP_PREFIX) // accessed by media2-session
public Bundle toBundle() {
Bundle bundle = new Bundle();
bundle.putParcelable(KEY_TOKEN, this);
@@ -1953,7 +1946,7 @@
* @return A compat Token for use with {@link MediaControllerCompat}.
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY_GROUP_PREFIX) // accessed by media2-session
public static Token fromBundle(Bundle tokenBundle) {
if (tokenBundle == null) {
return null;
@@ -2147,12 +2140,9 @@
* This is a wrapper for {@link ResultReceiver} for sending over aidl
* interfaces. The framework version was not exposed to aidls until
* {@link android.os.Build.VERSION_CODES#LOLLIPOP}.
- *
- * @hide
*/
- @RestrictTo(LIBRARY)
@SuppressLint("BanParcelableUsage")
- public static final class ResultReceiverWrapper implements Parcelable {
+ /* package */ static final class ResultReceiverWrapper implements Parcelable {
ResultReceiver mResultReceiver;
public ResultReceiverWrapper(@NonNull ResultReceiver resultReceiver) {
diff --git a/media/src/main/java/android/support/v4/media/session/PlaybackStateCompat.java b/media/src/main/java/android/support/v4/media/session/PlaybackStateCompat.java
index d1399d6..164a459 100644
--- a/media/src/main/java/android/support/v4/media/session/PlaybackStateCompat.java
+++ b/media/src/main/java/android/support/v4/media/session/PlaybackStateCompat.java
@@ -15,6 +15,7 @@
*/
package android.support.v4.media.session;
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
import android.annotation.SuppressLint;
@@ -48,7 +49,7 @@
/**
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY_GROUP_PREFIX) // used by media2-session
@LongDef(flag=true, value={ACTION_STOP, ACTION_PAUSE, ACTION_PLAY, ACTION_REWIND,
ACTION_SKIP_TO_PREVIOUS, ACTION_SKIP_TO_NEXT, ACTION_FAST_FORWARD, ACTION_SET_RATING,
ACTION_SEEK_TO, ACTION_PLAY_PAUSE, ACTION_PLAY_FROM_MEDIA_ID, ACTION_PLAY_FROM_SEARCH,
@@ -61,7 +62,7 @@
/**
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
@LongDef({ACTION_STOP, ACTION_PAUSE, ACTION_PLAY, ACTION_REWIND, ACTION_SKIP_TO_PREVIOUS,
ACTION_SKIP_TO_NEXT, ACTION_FAST_FORWARD, ACTION_PLAY_PAUSE})
@Retention(RetentionPolicy.SOURCE)
@@ -226,7 +227,7 @@
/**
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY_GROUP_PREFIX) // used by media2-session
@IntDef({STATE_NONE, STATE_STOPPED, STATE_PAUSED, STATE_PLAYING, STATE_FAST_FORWARDING,
STATE_REWINDING, STATE_BUFFERING, STATE_ERROR, STATE_CONNECTING,
STATE_SKIPPING_TO_PREVIOUS, STATE_SKIPPING_TO_NEXT, STATE_SKIPPING_TO_QUEUE_ITEM})
@@ -339,7 +340,7 @@
/**
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY_GROUP_PREFIX) // used by media2-session
@IntDef({REPEAT_MODE_INVALID, REPEAT_MODE_NONE, REPEAT_MODE_ONE, REPEAT_MODE_ALL,
REPEAT_MODE_GROUP})
@Retention(RetentionPolicy.SOURCE)
@@ -380,7 +381,7 @@
/**
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY_GROUP_PREFIX) // used by media2-session
@IntDef({SHUFFLE_MODE_INVALID, SHUFFLE_MODE_NONE, SHUFFLE_MODE_ALL, SHUFFLE_MODE_GROUP})
@Retention(RetentionPolicy.SOURCE)
public @interface ShuffleMode {}
@@ -411,17 +412,13 @@
*/
public static final int SHUFFLE_MODE_GROUP = 2;
- /**
- * @hide
- */
- @RestrictTo(LIBRARY_GROUP_PREFIX)
@IntDef({ERROR_CODE_UNKNOWN_ERROR, ERROR_CODE_APP_ERROR, ERROR_CODE_NOT_SUPPORTED,
ERROR_CODE_AUTHENTICATION_EXPIRED, ERROR_CODE_PREMIUM_ACCOUNT_REQUIRED,
ERROR_CODE_CONCURRENT_STREAM_LIMIT, ERROR_CODE_PARENTAL_CONTROL_RESTRICTED,
ERROR_CODE_NOT_AVAILABLE_IN_REGION, ERROR_CODE_CONTENT_ALREADY_PLAYING,
ERROR_CODE_SKIP_LIMIT_REACHED, ERROR_CODE_ACTION_ABORTED, ERROR_CODE_END_OF_QUEUE})
@Retention(RetentionPolicy.SOURCE)
- public @interface ErrorCode {}
+ private @interface ErrorCode {}
/**
* This is the default error code and indicates that none of the other error codes applies.
@@ -668,7 +665,7 @@
* @return The current playback position in ms
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY_GROUP_PREFIX) // accessed by media2-session
public long getCurrentPosition(Long timeDiff) {
long expectedPosition = mPosition + (long) (mSpeed * (
(timeDiff != null) ? timeDiff : SystemClock.elapsedRealtime() - mUpdateTime));
diff --git a/media/src/main/java/androidx/media/AudioAttributesCompat.java b/media/src/main/java/androidx/media/AudioAttributesCompat.java
index 730daff..7337f93 100644
--- a/media/src/main/java/androidx/media/AudioAttributesCompat.java
+++ b/media/src/main/java/androidx/media/AudioAttributesCompat.java
@@ -16,7 +16,7 @@
package androidx.media;
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
import android.media.AudioAttributes;
import android.media.AudioManager;
@@ -234,12 +234,8 @@
static final int INVALID_STREAM_TYPE = -1; // AudioSystem.STREAM_DEFAULT
- /**
- * @hide
- */
- @RestrictTo(LIBRARY_GROUP_PREFIX)
@ParcelField(1)
- public AudioAttributesImpl mImpl;
+ AudioAttributesImpl mImpl;
AudioAttributesCompat() {
}
@@ -538,7 +534,7 @@
*
* @hide For testing only.
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
public static void setForceLegacyBehavior(boolean force) {
sForceLegacyBehavior = force;
}
@@ -631,7 +627,7 @@
USAGE_GAME,
USAGE_ASSISTANT,
})
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
@Retention(RetentionPolicy.SOURCE)
public @interface AttributeUsage {
}
@@ -645,7 +641,7 @@
CONTENT_TYPE_SONIFICATION
})
@Retention(RetentionPolicy.SOURCE)
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
public @interface AttributeContentType {
}
}
diff --git a/media/src/main/java/androidx/media/AudioAttributesImplApi21.java b/media/src/main/java/androidx/media/AudioAttributesImplApi21.java
index 81b9c2c..bddc8a6 100644
--- a/media/src/main/java/androidx/media/AudioAttributesImplApi21.java
+++ b/media/src/main/java/androidx/media/AudioAttributesImplApi21.java
@@ -16,7 +16,7 @@
package androidx.media;
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
import static androidx.media.AudioAttributesCompat.INVALID_STREAM_TYPE;
import android.annotation.SuppressLint;
@@ -29,20 +29,16 @@
/** @hide */
@VersionedParcelize(jetifyAs = "android.support.v4.media.AudioAttributesImplApi21")
-@RestrictTo(LIBRARY_GROUP_PREFIX)
+@RestrictTo(LIBRARY)
@RequiresApi(21)
public class AudioAttributesImplApi21 implements AudioAttributesImpl {
private static final String TAG = "AudioAttributesCompat21";
- /** @hide */
- @RestrictTo(LIBRARY_GROUP_PREFIX)
@ParcelField(1)
- public AudioAttributes mAudioAttributes;
+ AudioAttributes mAudioAttributes;
- /** @hide */
- @RestrictTo(LIBRARY_GROUP_PREFIX)
@ParcelField(2)
- public int mLegacyStreamType = INVALID_STREAM_TYPE;
+ int mLegacyStreamType = INVALID_STREAM_TYPE;
/**
* Used for VersionedParcelable
diff --git a/media/src/main/java/androidx/media/AudioAttributesImplApi26.java b/media/src/main/java/androidx/media/AudioAttributesImplApi26.java
index 12e14cb..21411ff 100644
--- a/media/src/main/java/androidx/media/AudioAttributesImplApi26.java
+++ b/media/src/main/java/androidx/media/AudioAttributesImplApi26.java
@@ -16,7 +16,7 @@
package androidx.media;
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
import static androidx.media.AudioAttributesCompat.INVALID_STREAM_TYPE;
import android.media.AudioAttributes;
@@ -27,7 +27,7 @@
/** @hide */
@VersionedParcelize(jetifyAs = "android.support.v4.media.AudioAttributesImplApi26")
-@RestrictTo(LIBRARY_GROUP_PREFIX)
+@RestrictTo(LIBRARY)
@RequiresApi(26)
public class AudioAttributesImplApi26 extends AudioAttributesImplApi21 {
private static final String TAG = "AudioAttributesCompat26";
diff --git a/media/src/main/java/androidx/media/AudioAttributesImplBase.java b/media/src/main/java/androidx/media/AudioAttributesImplBase.java
index 1750a12..6525ab6 100644
--- a/media/src/main/java/androidx/media/AudioAttributesImplBase.java
+++ b/media/src/main/java/androidx/media/AudioAttributesImplBase.java
@@ -16,7 +16,7 @@
package androidx.media;
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
import static androidx.media.AudioAttributesCompat.CONTENT_TYPE_MOVIE;
import static androidx.media.AudioAttributesCompat.CONTENT_TYPE_MUSIC;
import static androidx.media.AudioAttributesCompat.CONTENT_TYPE_SONIFICATION;
@@ -50,24 +50,16 @@
/** @hide */
@VersionedParcelize(jetifyAs = "android.support.v4.media.AudioAttributesImplBase")
-@RestrictTo(LIBRARY_GROUP_PREFIX)
+@RestrictTo(LIBRARY)
public class AudioAttributesImplBase implements AudioAttributesImpl {
- /** @hide */
- @RestrictTo(LIBRARY_GROUP_PREFIX)
@ParcelField(1)
- public int mUsage = USAGE_UNKNOWN;
- /** @hide */
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ int mUsage = USAGE_UNKNOWN;
@ParcelField(2)
- public int mContentType = CONTENT_TYPE_UNKNOWN;
- /** @hide */
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ int mContentType = CONTENT_TYPE_UNKNOWN;
@ParcelField(3)
- public int mFlags = 0x0;
- /** @hide */
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ int mFlags = 0x0;
@ParcelField(4)
- public int mLegacyStream = INVALID_STREAM_TYPE;
+ int mLegacyStream = INVALID_STREAM_TYPE;
/**
* Used for VersionedParcelable
diff --git a/media/src/main/java/androidx/media/AudioFocusRequestCompat.java b/media/src/main/java/androidx/media/AudioFocusRequestCompat.java
index e932d63..8935faa 100644
--- a/media/src/main/java/androidx/media/AudioFocusRequestCompat.java
+++ b/media/src/main/java/androidx/media/AudioFocusRequestCompat.java
@@ -16,8 +16,6 @@
package androidx.media;
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
-
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.media.AudioAttributes;
@@ -33,7 +31,6 @@
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
-import androidx.annotation.RestrictTo;
import androidx.core.util.ObjectsCompat;
import java.lang.annotation.Retention;
@@ -45,8 +42,6 @@
/* package */ static final AudioAttributesCompat FOCUS_DEFAULT_ATTR =
new AudioAttributesCompat.Builder().setUsage(AudioAttributesCompat.USAGE_MEDIA).build();
- /** @hide */
- @RestrictTo(LIBRARY_GROUP_PREFIX)
@Retention(SOURCE)
@IntDef({
AudioManagerCompat.AUDIOFOCUS_GAIN,
@@ -54,7 +49,7 @@
AudioManagerCompat.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK,
AudioManagerCompat.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE
})
- public @interface FocusGainType {}
+ private @interface FocusGainType {}
private final int mFocusGain;
private final OnAudioFocusChangeListener mOnAudioFocusChangeListener;
@@ -392,10 +387,8 @@
*
* @param focusGain value to check
* @return true if focusGain is a valid value for an audio focus request.
- * @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
- static boolean isValidFocusGain(@FocusGainType int focusGain) {
+ private static boolean isValidFocusGain(@FocusGainType int focusGain) {
switch (focusGain) {
case AudioManagerCompat.AUDIOFOCUS_GAIN:
case AudioManagerCompat.AUDIOFOCUS_GAIN_TRANSIENT:
diff --git a/media/src/main/java/androidx/media/MediaBrowserCompatUtils.java b/media/src/main/java/androidx/media/MediaBrowserCompatUtils.java
index 0ca95e8..2c48e8c 100644
--- a/media/src/main/java/androidx/media/MediaBrowserCompatUtils.java
+++ b/media/src/main/java/androidx/media/MediaBrowserCompatUtils.java
@@ -16,7 +16,7 @@
package androidx.media;
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
import android.os.Bundle;
import android.support.v4.media.MediaBrowserCompat;
@@ -26,7 +26,7 @@
/**
* @hide
*/
-@RestrictTo(LIBRARY_GROUP_PREFIX)
+@RestrictTo(LIBRARY)
public class MediaBrowserCompatUtils {
public static boolean areSameOptions(Bundle options1, Bundle options2) {
if (options1 == options2) {
diff --git a/media/src/main/java/androidx/media/MediaBrowserProtocol.java b/media/src/main/java/androidx/media/MediaBrowserProtocol.java
index ddd3283..5326e1f 100644
--- a/media/src/main/java/androidx/media/MediaBrowserProtocol.java
+++ b/media/src/main/java/androidx/media/MediaBrowserProtocol.java
@@ -15,6 +15,8 @@
*/
package androidx.media;
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
+
import android.os.Bundle;
import android.support.v4.media.MediaBrowserCompat;
@@ -25,7 +27,7 @@
*
* @hide
*/
-@RestrictTo(RestrictTo.Scope.LIBRARY)
+@RestrictTo(LIBRARY)
public class MediaBrowserProtocol {
public static final String DATA_CALLBACK_TOKEN = "data_callback_token";
diff --git a/media/src/main/java/androidx/media/MediaBrowserServiceCompat.java b/media/src/main/java/androidx/media/MediaBrowserServiceCompat.java
index 35d46b2..82ff4b9 100644
--- a/media/src/main/java/androidx/media/MediaBrowserServiceCompat.java
+++ b/media/src/main/java/androidx/media/MediaBrowserServiceCompat.java
@@ -176,8 +176,6 @@
@RestrictTo(LIBRARY)
public static final int RESULT_PROGRESS_UPDATE = 1;
- /** @hide */
- @RestrictTo(LIBRARY)
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {RESULT_FLAG_OPTION_NOT_HANDLED,
RESULT_FLAG_ON_LOAD_ITEM_NOT_IMPLEMENTED, RESULT_FLAG_ON_SEARCH_NOT_IMPLEMENTED})
@@ -1304,7 +1302,7 @@
*
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY_GROUP_PREFIX) // accessed by media2-session
public void attachToBaseContext(Context base) {
attachBaseContext(base);
}
@@ -1422,7 +1420,7 @@
* @param option option
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
public void onSubscribe(String id, Bundle option) {
}
@@ -1432,7 +1430,7 @@
* @param id
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
public void onUnsubscribe(String id) {
}
@@ -1618,7 +1616,7 @@
* contain the information about the change.
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY_GROUP_PREFIX) // accessed by media2-session
public void notifyChildrenChanged(@NonNull RemoteUserInfo remoteUserInfo,
@NonNull String parentId, @NonNull Bundle options) {
if (remoteUserInfo == null) {
diff --git a/media/src/main/java/androidx/media/MediaSessionManager.java b/media/src/main/java/androidx/media/MediaSessionManager.java
index 7183e02..b11f186 100644
--- a/media/src/main/java/androidx/media/MediaSessionManager.java
+++ b/media/src/main/java/androidx/media/MediaSessionManager.java
@@ -16,7 +16,7 @@
package androidx.media;
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
import android.content.Context;
import android.os.Build;
@@ -128,14 +128,14 @@
* Represents an unknown pid of an application.
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
public static final int UNKNOWN_PID = -1;
/**
* Represents an unknown uid of an application.
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
public static final int UNKNOWN_UID = -1;
RemoteUserInfoImpl mImpl;
@@ -169,7 +169,7 @@
* @param remoteUserInfo Framework RemoteUserInfo
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
@RequiresApi(28)
public RemoteUserInfo(
android.media.session.MediaSessionManager.RemoteUserInfo remoteUserInfo) {
diff --git a/media/src/main/java/androidx/media/VolumeProviderCompat.java b/media/src/main/java/androidx/media/VolumeProviderCompat.java
index eea4c7b..6d835d6 100644
--- a/media/src/main/java/androidx/media/VolumeProviderCompat.java
+++ b/media/src/main/java/androidx/media/VolumeProviderCompat.java
@@ -39,7 +39,7 @@
/**
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY_GROUP_PREFIX) // used by mediarouter
@IntDef({VOLUME_CONTROL_FIXED, VOLUME_CONTROL_RELATIVE, VOLUME_CONTROL_ABSOLUTE})
@Retention(RetentionPolicy.SOURCE)
public @interface ControlType {}
diff --git a/media/src/main/java/androidx/media/app/NotificationCompat.java b/media/src/main/java/androidx/media/app/NotificationCompat.java
index 9e6af2e..e1fe44d 100644
--- a/media/src/main/java/androidx/media/app/NotificationCompat.java
+++ b/media/src/main/java/androidx/media/app/NotificationCompat.java
@@ -16,7 +16,7 @@
package androidx.media.app;
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
import static androidx.core.app.NotificationCompat.COLOR_DEFAULT;
import android.app.Notification;
@@ -204,7 +204,7 @@
/**
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
@Override
public void apply(NotificationBuilderWithBuilderAccessor builder) {
if (Build.VERSION.SDK_INT >= 21) {
@@ -229,7 +229,7 @@
/**
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
@Override
public RemoteViews makeContentView(NotificationBuilderWithBuilderAccessor builder) {
if (Build.VERSION.SDK_INT >= 21) {
@@ -297,7 +297,7 @@
/**
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
@Override
public RemoteViews makeBigContentView(NotificationBuilderWithBuilderAccessor builder) {
if (Build.VERSION.SDK_INT >= 21) {
@@ -382,7 +382,7 @@
/**
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
@Override
public void apply(NotificationBuilderWithBuilderAccessor builder) {
if (Build.VERSION.SDK_INT >= 24) {
@@ -396,7 +396,7 @@
/**
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
@Override
public RemoteViews makeContentView(NotificationBuilderWithBuilderAccessor builder) {
if (Build.VERSION.SDK_INT >= 24) {
@@ -438,7 +438,7 @@
/**
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
@Override
public RemoteViews makeBigContentView(NotificationBuilderWithBuilderAccessor builder) {
if (Build.VERSION.SDK_INT >= 24) {
@@ -470,7 +470,7 @@
/**
* @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @RestrictTo(LIBRARY)
@Override
public RemoteViews makeHeadsUpContentView(NotificationBuilderWithBuilderAccessor builder) {
if (Build.VERSION.SDK_INT >= 24) {
diff --git a/benchmark/src/androidTest/AndroidManifest.xml b/media/src/main/res/values/ids.xml
similarity index 65%
copy from benchmark/src/androidTest/AndroidManifest.xml
copy to media/src/main/res/values/ids.xml
index f5ec776..2fa8e5c 100644
--- a/benchmark/src/androidTest/AndroidManifest.xml
+++ b/media/src/main/res/values/ids.xml
@@ -14,13 +14,7 @@
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.benchmark.test">
- <application
- android:name="androidx.benchmark.ArgumentInjectingApplication">
- <activity android:name="android.app.Activity"/>
- </application>
-</manifest>
\ No newline at end of file
+<resources>
+ <item type="id" name="media_controller_compat_view_tag" />
+</resources>
\ No newline at end of file
diff --git a/media2/common/src/main/java/androidx/media2/common/SessionPlayer.java b/media2/common/src/main/java/androidx/media2/common/SessionPlayer.java
index 2d1b17f..c67153a 100644
--- a/media2/common/src/main/java/androidx/media2/common/SessionPlayer.java
+++ b/media2/common/src/main/java/androidx/media2/common/SessionPlayer.java
@@ -452,8 +452,8 @@
* @return the size of the video. The width and height of size could be 0 if there is no video
* or the size has not been determined yet.
* The {@link PlayerCallback} can be registered via {@link #registerPlayerCallback} to
- * receive a notification {@link PlayerCallback#onVideoSizeChangedInternal} when the size
- * is available.
+ * receive a notification {@link PlayerCallback#onVideoSizeChanged(SessionPlayer, VideoSize)}
+ * when the size is available.
*
* @hide
*/
@@ -1338,6 +1338,16 @@
}
/**
+ * @deprecated Use {@link #onVideoSizeChanged(SessionPlayer, VideoSize)} instead.
+ * @hide
+ */
+ @Deprecated
+ @RestrictTo(LIBRARY_GROUP)
+ public void onVideoSizeChangedInternal(
+ @NonNull SessionPlayer player, @NonNull MediaItem item, @NonNull VideoSize size) {
+ }
+
+ /**
* Called to indicate the video size
* <p>
* The video size (width and height) could be 0 if there was no video,
@@ -1348,16 +1358,14 @@
* is called.
*
* @param player the player associated with this callback
- * @param item the MediaItem of this media item
* @param size the size of the video
* @see #getVideoSizeInternal()
*
* @hide
*/
- // TODO: Add onVideoSizeChanged and deprecate this method (b/132928418)
+ // TODO: Unhide this method (b/132928418)
@RestrictTo(LIBRARY_GROUP)
- public void onVideoSizeChangedInternal(
- @NonNull SessionPlayer player, @NonNull MediaItem item, @NonNull VideoSize size) {
+ public void onVideoSizeChanged(@NonNull SessionPlayer player, @NonNull VideoSize size) {
}
/**
diff --git a/media2/media2-exoplayer/src/main/libs/exoplayer-media2.jar b/media2/media2-exoplayer/src/main/libs/exoplayer-media2.jar
index dffa337..121b6d1 100755
--- a/media2/media2-exoplayer/src/main/libs/exoplayer-media2.jar
+++ b/media2/media2-exoplayer/src/main/libs/exoplayer-media2.jar
Binary files differ
diff --git a/media2/player/src/androidTest/java/androidx/media2/player/MediaPlayerTest.java b/media2/player/src/androidTest/java/androidx/media2/player/MediaPlayerTest.java
index 1cf11d4..00e85db 100644
--- a/media2/player/src/androidTest/java/androidx/media2/player/MediaPlayerTest.java
+++ b/media2/player/src/androidTest/java/androidx/media2/player/MediaPlayerTest.java
@@ -199,6 +199,16 @@
MediaPlayer.PlayerCallback callback = new MediaPlayer.PlayerCallback() {
@Override
public void onVideoSizeChanged(MediaPlayer mp, MediaItem dsd, VideoSize size) {
+ assertVideoSizeEquals(size);
+ }
+
+ @Override
+ public void onVideoSizeChanged(@NonNull SessionPlayer player,
+ @NonNull androidx.media2.common.VideoSize size) {
+ assertVideoSizeEquals(new VideoSize(size.getWidth(), size.getHeight()));
+ }
+
+ private void assertVideoSizeEquals(VideoSize size) {
if (size.getWidth() == 0 && size.getHeight() == 0) {
// A size of 0x0 can be sent initially one time when using NuPlayer.
assertFalse(onVideoSizeChangedCalled.isSignalled());
@@ -226,7 +236,7 @@
mPlayer.prepare();
mPlayer.play();
- onVideoSizeChangedCalled.waitForSignal();
+ onVideoSizeChangedCalled.waitForCountedSignals(2);
onVideoRenderingStartCalled.waitForSignal();
mPlayer.setPlayerVolume(volume);
@@ -299,6 +309,16 @@
MediaPlayer.PlayerCallback callback = new MediaPlayer.PlayerCallback() {
@Override
public void onVideoSizeChanged(MediaPlayer mp, MediaItem dsd, VideoSize size) {
+ assertVideoSizeEquals(size);
+ }
+
+ @Override
+ public void onVideoSizeChanged(@NonNull SessionPlayer player,
+ @NonNull androidx.media2.common.VideoSize size) {
+ assertVideoSizeEquals(new VideoSize(size.getWidth(), size.getHeight()));
+ }
+
+ private void assertVideoSizeEquals(VideoSize size) {
if (size.getWidth() == 0 && size.getHeight() == 0) {
// A size of 0x0 can be sent initially one time when using NuPlayer.
assertFalse(onVideoSizeChangedCalled.isSignalled());
@@ -326,7 +346,7 @@
mPlayer.prepare();
mPlayer.play();
- onVideoSizeChangedCalled.waitForSignal();
+ onVideoSizeChangedCalled.waitForCountedSignals(2);
onVideoRenderingStartCalled.waitForSignal();
}
diff --git a/media2/player/src/main/java/androidx/media2/player/AudioFocusHandler.java b/media2/player/src/main/java/androidx/media2/player/AudioFocusHandler.java
index 8738f1d..54797a6 100644
--- a/media2/player/src/main/java/androidx/media2/player/AudioFocusHandler.java
+++ b/media2/player/src/main/java/androidx/media2/player/AudioFocusHandler.java
@@ -280,7 +280,7 @@
// want to have more finer grained control. (e.g. adding audio focus listener)
return AudioManager.AUDIOFOCUS_NONE;
}
- // Javadoc here means 'The different types of focus reuqests' written in the
+ // Javadoc here means 'The different types of focus requests' written in the
// {@link AudioFocusRequest}.
switch (audioAttributesCompat.getUsage()) {
// USAGE_VOICE_COMMUNICATION_SIGNALLING is for DTMF that may happen multiple times
diff --git a/media2/player/src/main/java/androidx/media2/player/MediaPlayer.java b/media2/player/src/main/java/androidx/media2/player/MediaPlayer.java
index 56e8aac..4142751 100644
--- a/media2/player/src/main/java/androidx/media2/player/MediaPlayer.java
+++ b/media2/player/src/main/java/androidx/media2/player/MediaPlayer.java
@@ -428,7 +428,6 @@
.setAudioFallbackMode(PlaybackParams.AUDIO_FALLBACK_MODE_DEFAULT)
.build();
- private static final int CALL_COMPLETE_PLAYLIST_BASE = -1000;
private static final int END_OF_PLAYLIST = -1;
private static final int NO_MEDIA_ITEM = -2;
@@ -504,21 +503,27 @@
@SuppressWarnings("WeakerAccess") /* synthetic access */
@MediaPlayer2.CallCompleted final int mCallType;
@SuppressWarnings("WeakerAccess") /* synthetic access */
- final ResolvableFuture mFuture;
+ final ResolvableFuture<? extends PlayerResult> mFuture;
@SuppressWarnings("WeakerAccess") /* synthetic access */
final SessionPlayer.TrackInfo mTrackInfo;
@SuppressWarnings("WeakerAccess") /* synthetic access */
- PendingCommand(int callType, ResolvableFuture future) {
+ PendingCommand(int callType, ResolvableFuture<? extends PlayerResult> future) {
this(callType, future, null);
}
@SuppressWarnings("WeakerAccess") /* synthetic access */
- PendingCommand(int callType, ResolvableFuture future, SessionPlayer.TrackInfo trackInfo) {
+ PendingCommand(int callType, ResolvableFuture<? extends PlayerResult> future,
+ SessionPlayer.TrackInfo trackInfo) {
mCallType = callType;
mFuture = future;
mTrackInfo = trackInfo;
}
+
+ @SuppressWarnings("unchecked")
+ <V extends PlayerResult> void setResult(V value) {
+ ((ResolvableFuture<V>) mFuture).set(value);
+ }
}
/* A list for tracking the commands submitted to MediaPlayer2.*/
@@ -622,7 +627,7 @@
/* A list of pending operations within this MediaPlayer that will be executed sequentially. */
@SuppressWarnings("WeakerAccess") /* synthetic access */
@GuardedBy("mPendingFutures")
- final ArrayDeque<PendingFuture<? super PlayerResult>> mPendingFutures = new ArrayDeque<>();
+ final ArrayDeque<PendingFuture<? extends PlayerResult>> mPendingFutures = new ArrayDeque<>();
private final Object mStateLock = new Object();
@GuardedBy("mStateLock")
@@ -686,7 +691,8 @@
@GuardedBy("mPendingCommands")
@SuppressWarnings("WeakerAccess") /* synthetic access */
void addPendingCommandLocked(
- int callType, final ResolvableFuture future, final Object token) {
+ int callType, final ResolvableFuture<? extends PlayerResult> future,
+ final Object token) {
final PendingCommand pendingCommand = new PendingCommand(callType, future);
mPendingCommands.add(pendingCommand);
addFutureListener(pendingCommand, future, token);
@@ -695,17 +701,16 @@
@GuardedBy("mPendingCommands")
@SuppressWarnings("WeakerAccess") /* synthetic access */
void addPendingCommandWithTrackInfoLocked(
- int callType, final ResolvableFuture future, final SessionPlayer.TrackInfo trackInfo,
- final Object token) {
+ int callType, final ResolvableFuture<? extends PlayerResult> future,
+ final SessionPlayer.TrackInfo trackInfo, final Object token) {
final PendingCommand pendingCommand = new PendingCommand(callType, future, trackInfo);
mPendingCommands.add(pendingCommand);
addFutureListener(pendingCommand, future, token);
}
- @GuardedBy("mPendingCommands")
@SuppressWarnings("WeakerAccess") /* synthetic access */
- void addFutureListener(final PendingCommand pendingCommand, final ResolvableFuture future,
- final Object token) {
+ void addFutureListener(final PendingCommand pendingCommand,
+ final ResolvableFuture<? extends PlayerResult> future, final Object token) {
future.addListener(new Runnable() {
@Override
public void run() {
@@ -721,8 +726,7 @@
}, mExecutor);
}
- @SuppressWarnings("unchecked")
- private void addPendingFuture(final PendingFuture pendingFuture) {
+ private void addPendingFuture(final PendingFuture<? extends PlayerResult> pendingFuture) {
synchronized (mPendingFutures) {
mPendingFutures.add(pendingFuture);
executePendingFutures();
@@ -1776,7 +1780,7 @@
}
// Cancel the pending futures.
synchronized (mPendingFutures) {
- for (PendingFuture f : mPendingFutures) {
+ for (PendingFuture<? extends PlayerResult> f : mPendingFutures) {
if (f.mExecuteCalled && !f.isDone() && !f.isCancelled()) {
f.cancel(true);
}
@@ -2976,7 +2980,7 @@
return (value > maxValue) ? maxValue : value;
}
- @SuppressWarnings({"WeakerAccess", "unchecked"}) /* synthetic access */
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
void handleCallComplete(MediaPlayer2 mp, final MediaItem item, int what, int status) {
PendingCommand expected;
synchronized (mPendingCommands) {
@@ -3014,13 +3018,14 @@
case MediaPlayer2.CALL_COMPLETED_SET_DATA_SOURCE:
case MediaPlayer2.CALL_COMPLETED_SKIP_TO_NEXT:
final List<SessionPlayer.TrackInfo> tracks = mp.getTrackInfo();
+ final androidx.media2.common.VideoSize videoSize = getVideoSizeInternal();
notifySessionPlayerCallback(new SessionPlayerCallbackNotifier() {
@Override
public void callCallback(
SessionPlayer.PlayerCallback callback) {
callback.onCurrentMediaItemChanged(MediaPlayer.this, item);
- callback.onVideoSizeChangedInternal(MediaPlayer.this,
- getCurrentMediaItem(), getVideoSizeInternal());
+ callback.onVideoSizeChanged(MediaPlayer.this, videoSize);
+ callback.onVideoSizeChangedInternal(MediaPlayer.this, item, videoSize);
callback.onTrackInfoChanged(MediaPlayer.this, tracks);
}
});
@@ -3066,20 +3071,20 @@
if (what != MediaPlayer2.CALL_COMPLETED_PREPARE_DRM) {
Integer resultCode = sResultCodeMap.containsKey(status)
? sResultCodeMap.get(status) : RESULT_ERROR_UNKNOWN;
- expected.mFuture.set(new PlayerResult(resultCode, item));
+ expected.setResult(new PlayerResult(resultCode, item));
} else {
Integer resultCode = sPrepareDrmStatusMap.containsKey(status)
? sPrepareDrmStatusMap.get(status) : DrmResult.RESULT_ERROR_PREPARATION_ERROR;
- expected.mFuture.set(new DrmResult(resultCode, item));
+ expected.setResult(new DrmResult(resultCode, item));
}
executePendingFutures();
}
private void executePendingFutures() {
synchronized (mPendingFutures) {
- Iterator<PendingFuture<? super PlayerResult>> it = mPendingFutures.iterator();
+ Iterator<PendingFuture<? extends PlayerResult>> it = mPendingFutures.iterator();
while (it.hasNext()) {
- PendingFuture f = it.next();
+ PendingFuture<? extends PlayerResult> f = it.next();
if (f.isCancelled() || f.execute()) {
mPendingFutures.removeFirst();
} else {
@@ -3088,7 +3093,7 @@
}
// Execute skip futures earlier for making them be skipped.
while (it.hasNext()) {
- PendingFuture f = it.next();
+ PendingFuture<? extends PlayerResult> f = it.next();
if (!f.mIsSeekTo) {
break;
}
@@ -3129,6 +3134,7 @@
notifySessionPlayerCallback(new SessionPlayerCallbackNotifier() {
@Override
public void callCallback(SessionPlayer.PlayerCallback callback) {
+ callback.onVideoSizeChanged(MediaPlayer.this, commonSize);
callback.onVideoSizeChangedInternal(MediaPlayer.this, item, commonSize);
}
});
@@ -3257,9 +3263,12 @@
@NonNull MediaPlayer mp, @NonNull MediaItem item, @NonNull VideoSize size) { }
/**
+ * @deprecated Use
+ * {@link #onVideoSizeChanged(SessionPlayer, androidx.media2.common.VideoSize)} instead.
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
+ @Deprecated
@Override
public void onVideoSizeChangedInternal(
@NonNull SessionPlayer player, @NonNull MediaItem item,
@@ -3508,7 +3517,7 @@
DrmInfo(MediaPlayer2.DrmInfo info) {
mMp2DrmInfo = info;
}
- };
+ }
/**
* Interface definition of a callback to be invoked when the app
diff --git a/media2/player/src/main/java/androidx/media2/player/MediaPlayer2.java b/media2/player/src/main/java/androidx/media2/player/MediaPlayer2.java
index 3cb98ea..379bfac 100644
--- a/media2/player/src/main/java/androidx/media2/player/MediaPlayer2.java
+++ b/media2/player/src/main/java/androidx/media2/player/MediaPlayer2.java
@@ -690,8 +690,8 @@
* Returns the audio session ID.
*
* @return the audio session ID. {@see #setAudioSessionId(int)}
- * Note that the audio session ID is 0 only if a problem occured when the MediaPlayer2 was
- * contructed.
+ * Note that the audio session ID is 0 only if a problem occurred when the MediaPlayer2 was
+ * constructed.
*/
public abstract int getAudioSessionId();
diff --git a/media2/player/src/main/java/androidx/media2/player/MediaTimestamp.java b/media2/player/src/main/java/androidx/media2/player/MediaTimestamp.java
index 63bb050..8d33205 100644
--- a/media2/player/src/main/java/androidx/media2/player/MediaTimestamp.java
+++ b/media2/player/src/main/java/androidx/media2/player/MediaTimestamp.java
@@ -103,8 +103,12 @@
@Override
public boolean equals(Object obj) {
- if (this == obj) return true;
- if (obj == null || getClass() != obj.getClass()) return false;
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
final MediaTimestamp that = (MediaTimestamp) obj;
return (this.mMediaTimeUs == that.mMediaTimeUs)
diff --git a/media2/player/src/main/java/androidx/media2/player/exoplayer/DurationProvidingMediaSource.java b/media2/player/src/main/java/androidx/media2/player/exoplayer/DurationProvidingMediaSource.java
index 79aeb0a..5c52b8f 100644
--- a/media2/player/src/main/java/androidx/media2/player/exoplayer/DurationProvidingMediaSource.java
+++ b/media2/player/src/main/java/androidx/media2/player/exoplayer/DurationProvidingMediaSource.java
@@ -20,7 +20,6 @@
import android.annotation.SuppressLint;
-import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.media2.exoplayer.external.C;
import androidx.media2.exoplayer.external.Timeline;
@@ -65,6 +64,13 @@
}
@Override
+ protected void onChildSourceInfoRefreshed(Void id,
+ MediaSource mediaSource, Timeline timeline) {
+ mCurrentTimeline = timeline;
+ refreshSourceInfo(timeline);
+ }
+
+ @Override
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) {
return mMediaSource.createPeriod(id, allocator, startPositionUs);
}
@@ -74,11 +80,4 @@
mMediaSource.releasePeriod(mediaPeriod);
}
- @Override
- protected void onChildSourceInfoRefreshed(
- Void id, MediaSource mediaSource, Timeline timeline, @Nullable Object manifest) {
- mCurrentTimeline = timeline;
- refreshSourceInfo(timeline, manifest);
- }
-
}
diff --git a/media2/player/src/main/java/androidx/media2/player/exoplayer/ExoPlayerWrapper.java b/media2/player/src/main/java/androidx/media2/player/exoplayer/ExoPlayerWrapper.java
index f7b801f..d8b2cd8 100644
--- a/media2/player/src/main/java/androidx/media2/player/exoplayer/ExoPlayerWrapper.java
+++ b/media2/player/src/main/java/androidx/media2/player/exoplayer/ExoPlayerWrapper.java
@@ -923,11 +923,12 @@
public void setNextMediaItems(List<MediaItem> mediaItems) {
int size = mConcatenatingMediaSource.getSize();
+ List<MediaItemInfo> oldMediaItemInfos = new ArrayList<>(size > 1 ? size - 1 : 0);
if (size > 1) {
mConcatenatingMediaSource.removeMediaSourceRange(
/* fromIndex= */ 1, /* toIndex= */ size);
while (mMediaItemInfos.size() > 1) {
- releaseMediaItem(mMediaItemInfos.removeLast());
+ oldMediaItemInfos.add(mMediaItemInfos.removeLast());
}
}
@@ -943,6 +944,13 @@
mediaSources);
}
mConcatenatingMediaSource.addMediaSources(mediaSources);
+
+ // Release old media items after appending new ones, so that any items that are present
+ // both before and after this call have their reference counts incremented before they
+ // are decremented.
+ for (MediaItemInfo mediaItemInfo : oldMediaItemInfos) {
+ releaseMediaItem(mediaItemInfo);
+ }
}
public void preparePlayer() {
@@ -1089,8 +1097,7 @@
mFileDescriptorRegistry.unregisterMediaItem(fileDescriptor);
((FileMediaItem) mediaItem).decreaseRefCount();
} else if (mediaItem instanceof CallbackMediaItem) {
- ((CallbackMediaItem) mediaItemInfo.mMediaItem)
- .getDataSourceCallback().close();
+ ((CallbackMediaItem) mediaItem).getDataSourceCallback().close();
}
} catch (IOException e) {
Log.w(TAG, "Error releasing media item " + mediaItem, e);
diff --git a/media2/session/src/androidTest/java/androidx/media2/session/MediaBrowserLegacyTest.java b/media2/session/src/androidTest/java/androidx/media2/session/MediaBrowserLegacyTest.java
index 33d62d8..061098b 100644
--- a/media2/session/src/androidTest/java/androidx/media2/session/MediaBrowserLegacyTest.java
+++ b/media2/session/src/androidTest/java/androidx/media2/session/MediaBrowserLegacyTest.java
@@ -40,6 +40,7 @@
import androidx.media2.session.MediaBrowser.BrowserCallback;
import androidx.media2.session.MediaLibraryService.LibraryParams;
import androidx.media2.session.MockMediaBrowserServiceCompat.Proxy;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import com.google.common.util.concurrent.ListenableFuture;
@@ -47,6 +48,7 @@
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.List;
@@ -57,6 +59,7 @@
/**
* Tests {@link MediaBrowser} with {@link MediaBrowserServiceCompat}.
*/
+@RunWith(AndroidJUnit4.class)
@LargeTest
public class MediaBrowserLegacyTest extends MediaSessionTestBase {
private static final String TAG = "MediaBrowserLegacyTest";
diff --git a/media2/session/src/androidTest/java/androidx/media2/session/MediaBrowserTest.java b/media2/session/src/androidTest/java/androidx/media2/session/MediaBrowserTest.java
index a063c09..68165d2 100644
--- a/media2/session/src/androidTest/java/androidx/media2/session/MediaBrowserTest.java
+++ b/media2/session/src/androidTest/java/androidx/media2/session/MediaBrowserTest.java
@@ -391,8 +391,7 @@
}
@Test
- public void testBrowserCallback_onChildrenChangedIsCalledWhenSubscribed()
- throws InterruptedException {
+ public void testBrowserCallback_onChildrenChangedIsCalledWhenSubscribed() throws Exception {
// This test uses MediaLibrarySession.notifyChildrenChanged().
prepareLooper();
final String expectedParentId = "expectedParentId";
@@ -434,7 +433,10 @@
TestServiceRegistry.getInstance().setSessionCallback(sessionCallback);
MockMediaLibraryService.setAssertLibraryParams(testParams);
- createBrowser(controllerCallbackProxy).subscribe(expectedParentId, testParams);
+ LibraryResult result = createBrowser(controllerCallbackProxy)
+ .subscribe(expectedParentId, testParams)
+ .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertEquals(RESULT_SUCCESS, result.getResultCode());
// onChildrenChanged() should be called.
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
@@ -497,8 +499,7 @@
}
@Test
- public void testBrowserCallback_onChildrenChangedIsCalledWhenSubscribed2()
- throws InterruptedException {
+ public void testBrowserCallback_onChildrenChangedIsCalledWhenSubscribed2() throws Exception {
// This test uses MediaLibrarySession.notifyChildrenChanged(ControllerInfo).
prepareLooper();
final String expectedParentId = "expectedParentId";
@@ -540,7 +541,10 @@
};
TestServiceRegistry.getInstance().setSessionCallback(sessionCallback);
- createBrowser(controllerCallbackProxy).subscribe(expectedParentId, null);
+ LibraryResult result = createBrowser(controllerCallbackProxy)
+ .subscribe(expectedParentId, null)
+ .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertEquals(RESULT_SUCCESS, result.getResultCode());
// onChildrenChanged() should be called.
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
diff --git a/media2/session/src/androidTest/java/androidx/media2/session/MediaControllerTest.java b/media2/session/src/androidTest/java/androidx/media2/session/MediaControllerTest.java
index 36e8ff2..a839548 100644
--- a/media2/session/src/androidTest/java/androidx/media2/session/MediaControllerTest.java
+++ b/media2/session/src/androidTest/java/androidx/media2/session/MediaControllerTest.java
@@ -24,7 +24,6 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
import android.app.PendingIntent;
import android.content.Context;
@@ -143,14 +142,11 @@
}
@Test
- public void testPlay() {
+ public void testPlay() throws Exception {
prepareLooper();
- mController.play();
- try {
- assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
- } catch (InterruptedException e) {
- fail(e.getMessage());
- }
+ SessionResult result = mController.play().get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertEquals(RESULT_SUCCESS, result.getResultCode());
+ assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertTrue(mPlayer.mPlayCalled);
}
@@ -161,46 +157,39 @@
final MockPlayer player = new MockPlayer(2);
player.mLastPlayerState = SessionPlayer.PLAYER_STATE_IDLE;
mSession.updatePlayer(player);
- mController.play();
+ SessionResult result = mController.play().get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertEquals(RESULT_SUCCESS, result.getResultCode());
assertTrue(player.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertTrue(player.mPlayCalled);
assertTrue(player.mPrepareCalled);
}
@Test
- public void testPause() {
+ public void testPause() throws Exception {
prepareLooper();
- mController.pause();
- try {
- assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
- } catch (InterruptedException e) {
- fail(e.getMessage());
- }
+ SessionResult result = mController.pause().get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertEquals(RESULT_SUCCESS, result.getResultCode());
+ assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertTrue(mPlayer.mPauseCalled);
}
@Test
- public void testPrepare() {
+ public void testPrepare() throws Exception {
prepareLooper();
- mController.prepare();
- try {
- assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
- } catch (InterruptedException e) {
- fail(e.getMessage());
- }
+ SessionResult result = mController.prepare().get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertEquals(RESULT_SUCCESS, result.getResultCode());
+ assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertTrue(mPlayer.mPrepareCalled);
}
@Test
- public void testSeekTo() {
+ public void testSeekTo() throws Exception {
prepareLooper();
final long seekPosition = 12125L;
- mController.seekTo(seekPosition);
- try {
- assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
- } catch (InterruptedException e) {
- fail(e.getMessage());
- }
+ SessionResult result = mController.seekTo(seekPosition)
+ .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertEquals(RESULT_SUCCESS, result.getResultCode());
+ assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertTrue(mPlayer.mSeekToCalled);
assertEquals(seekPosition, mPlayer.mSeekPosition);
}
@@ -270,7 +259,7 @@
MockPlayer player = new MockPlayer(0);
player.mLastPlayerState = testState;
- player.setAudioAttributes(testAudioAttributes);
+ player.mAudioAttributes = testAudioAttributes;
player.mPlaylist = testPlaylist;
mSession.updatePlayer(player);
@@ -290,10 +279,12 @@
}
@Test
- public void testSetPlaylist() throws InterruptedException {
+ public void testSetPlaylist() throws Exception {
prepareLooper();
final List<String> list = TestUtils.createMediaIds(2);
- mController.setPlaylist(list, null /* Metadata */);
+ SessionResult result = mController.setPlaylist(list, null /* Metadata */)
+ .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertEquals(RESULT_SUCCESS, result.getResultCode());
assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertTrue(mPlayer.mSetPlaylistCalled);
@@ -311,9 +302,9 @@
public void testSetMediaItem() throws Exception {
prepareLooper();
String mediaId = "testSetMediaItem";
- int resultCode = mController.setMediaItem(mediaId).get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
- .getResultCode();
- assertEquals(RESULT_SUCCESS, resultCode);
+ SessionResult result = mController.setMediaItem(mediaId)
+ .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertEquals(RESULT_SUCCESS, result.getResultCode());
assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertNull(mPlayer.mMetadata);
@@ -386,10 +377,12 @@
}
@Test
- public void testUpdatePlaylistMetadata() throws InterruptedException {
+ public void testUpdatePlaylistMetadata() throws Exception {
prepareLooper();
final MediaMetadata testMetadata = TestUtils.createMetadata();
- mController.updatePlaylistMetadata(testMetadata);
+ SessionResult result = mController.updatePlaylistMetadata(testMetadata)
+ .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertEquals(RESULT_SUCCESS, result.getResultCode());
assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertTrue(mPlayer.mUpdatePlaylistMetadataCalled);
@@ -430,7 +423,9 @@
public void testSetPlaybackSpeed() throws Exception {
prepareLooper();
final float speed = 1.5f;
- mController.setPlaybackSpeed(speed);
+ SessionResult result = mController.setPlaybackSpeed(speed)
+ .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertEquals(RESULT_SUCCESS, result.getResultCode());
assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertEquals(speed, mPlayer.mPlaybackSpeed, 0.0f);
}
@@ -445,7 +440,7 @@
public void testGetPlaybackSpeed() throws InterruptedException {
prepareLooper();
final float speed = 1.5f;
- mPlayer.setPlaybackSpeed(speed);
+ mPlayer.mPlaybackSpeed = speed;
final CountDownLatch latch = new CountDownLatch(1);
final MediaController controller =
@@ -562,7 +557,7 @@
};
final MediaController controller = createController(mSession.getToken(), true, null,
callback);
- mSession.getPlayer().setPlaylist(testPlaylist, null);
+ mPlayer.mPlaylist = testPlaylist;
mPlayer.mBufferedPosition = testBufferingPosition;
mPlayer.notifyBufferingStateChanged(testItem, testBufferingState);
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
@@ -606,7 +601,7 @@
prepareLooper();
final int listSize = 5;
final List<MediaItem> list = TestUtils.createMediaItems(listSize);
- mPlayer.setPlaylist(list, null);
+ mPlayer.mPlaylist = list;
final int index = 3;
final MediaItem currentItem = list.get(index);
@@ -639,22 +634,25 @@
assertFalse(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
// Known DSD should be notified through the onCurrentMediaItemChanged.
- mPlayer.skipToPlaylistItem(index);
+ mPlayer.mIndex = index;
+ mPlayer.mCurrentMediaItem = mPlayer.mItem = mPlayer.mPlaylist.get(index);
mPlayer.notifyCurrentMediaItemChanged(currentItem);
assertFalse(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
// Null DSD becomes null MediaItem.
- mPlayer.setMediaItem(null);
+ mPlayer.mCurrentMediaItem = mPlayer.mItem = null;
mPlayer.notifyCurrentMediaItemChanged(null);
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
@Test
- public void testAddPlaylistItem() throws InterruptedException {
+ public void testAddPlaylistItem() throws Exception {
prepareLooper();
final int testIndex = 12;
final String testId = "testAddPlaylistItem";
- mController.addPlaylistItem(testIndex, testId);
+ SessionResult result = mController.addPlaylistItem(testIndex, testId)
+ .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertEquals(RESULT_SUCCESS, result.getResultCode());
assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertTrue(mPlayer.mAddPlaylistItemCalled);
@@ -663,7 +661,7 @@
}
@Test
- public void testRemovePlaylistItem() throws InterruptedException {
+ public void testRemovePlaylistItem() throws Exception {
prepareLooper();
mPlayer.mPlaylist = TestUtils.createMediaItems(2);
@@ -672,7 +670,9 @@
// player.
MediaController controller = createController(mSession.getToken());
int targetIndex = 0;
- controller.removePlaylistItem(targetIndex);
+ SessionResult result = controller.removePlaylistItem(targetIndex)
+ .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertEquals(RESULT_SUCCESS, result.getResultCode());
assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertTrue(mPlayer.mRemovePlaylistItemCalled);
@@ -680,11 +680,13 @@
}
@Test
- public void testReplacePlaylistItem() throws InterruptedException {
+ public void testReplacePlaylistItem() throws Exception {
prepareLooper();
final int testIndex = 12;
final String testId = "testAddPlaylistItem";
- mController.replacePlaylistItem(testIndex, testId);
+ SessionResult result = mController.replacePlaylistItem(testIndex, testId)
+ .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertEquals(RESULT_SUCCESS, result.getResultCode());
assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertTrue(mPlayer.mReplacePlaylistItemCalled);
@@ -693,29 +695,35 @@
}
@Test
- public void testSkipToPreviousItem() throws InterruptedException {
+ public void testSkipToPreviousItem() throws Exception {
prepareLooper();
- mController.skipToPreviousPlaylistItem();
+ SessionResult result = mController.skipToPreviousPlaylistItem()
+ .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertEquals(RESULT_SUCCESS, result.getResultCode());
assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertTrue(mPlayer.mSkipToPreviousItemCalled);
}
@Test
- public void testSkipToNextItem() throws InterruptedException {
+ public void testSkipToNextItem() throws Exception {
prepareLooper();
- mController.skipToNextPlaylistItem();
+ SessionResult result = mController.skipToNextPlaylistItem()
+ .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertEquals(RESULT_SUCCESS, result.getResultCode());
assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertTrue(mPlayer.mSkipToNextItemCalled);
}
@Test
- public void testSkipToPlaylistItem() throws InterruptedException {
+ public void testSkipToPlaylistItem() throws Exception {
prepareLooper();
List<MediaItem> playlist = TestUtils.createMediaItems(2);
int targetIndex = 1;
mPlayer.mPlaylist = playlist;
MediaController controller = createController(mSession.getToken());
- controller.skipToPlaylistItem(targetIndex);
+ SessionResult result = controller.skipToPlaylistItem(targetIndex)
+ .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertEquals(RESULT_SUCCESS, result.getResultCode());
assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertTrue(mPlayer.mSkipToPlaylistItemCalled);
@@ -745,10 +753,12 @@
}
@Test
- public void testSetShuffleMode() throws InterruptedException {
+ public void testSetShuffleMode() throws Exception {
prepareLooper();
final int testShuffleMode = SessionPlayer.SHUFFLE_MODE_GROUP;
- mController.setShuffleMode(testShuffleMode);
+ SessionResult result = mController.setShuffleMode(testShuffleMode)
+ .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertEquals(RESULT_SUCCESS, result.getResultCode());
assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertTrue(mPlayer.mSetShuffleModeCalled);
@@ -778,10 +788,12 @@
}
@Test
- public void testSetRepeatMode() throws InterruptedException {
+ public void testSetRepeatMode() throws Exception {
prepareLooper();
final int testRepeatMode = SessionPlayer.REPEAT_MODE_GROUP;
- mController.setRepeatMode(testRepeatMode);
+ SessionResult result = mController.setRepeatMode(testRepeatMode)
+ .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertEquals(RESULT_SUCCESS, result.getResultCode());
assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertTrue(mPlayer.mSetRepeatModeCalled);
@@ -875,7 +887,9 @@
final MediaController controller = createController(mSession.getToken(), true, null, null);
final int targetVolume = 50;
- controller.setVolumeTo(targetVolume, 0 /* flags */);
+ SessionResult result = controller.setVolumeTo(targetVolume, 0 /* flags */)
+ .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertEquals(RESULT_SUCCESS, result.getResultCode());
assertTrue(remotePlayer.mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertTrue(remotePlayer.mSetVolumeToCalled);
assertEquals(targetVolume, (int) remotePlayer.mCurrentVolume);
@@ -894,7 +908,9 @@
final MediaController controller = createController(mSession.getToken(), true, null, null);
final int direction = AudioManager.ADJUST_RAISE;
- controller.adjustVolume(direction, 0 /* flags */);
+ SessionResult result = controller.adjustVolume(direction, 0 /* flags */)
+ .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertEquals(RESULT_SUCCESS, result.getResultCode());
assertTrue(remotePlayer.mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertTrue(remotePlayer.mAdjustVolumeCalled);
assertEquals(direction, remotePlayer.mDirection);
@@ -923,7 +939,7 @@
AudioAttributesCompat attrs = new AudioAttributesCompat.Builder()
.setLegacyStreamType(stream)
.build();
- mPlayer.setAudioAttributes(attrs);
+ mPlayer.mAudioAttributes = attrs;
mSession.updatePlayer(mPlayer);
final int originalVolume = mAudioManager.getStreamVolume(stream);
@@ -931,7 +947,9 @@
? originalVolume + 1 : originalVolume - 1;
Log.d(TAG, "originalVolume=" + originalVolume + ", targetVolume=" + targetVolume);
- mController.setVolumeTo(targetVolume, AudioManager.FLAG_SHOW_UI);
+ SessionResult result = mController.setVolumeTo(targetVolume, AudioManager.FLAG_SHOW_UI)
+ .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertEquals(RESULT_SUCCESS, result.getResultCode());
new PollingCheck(VOLUME_CHANGE_TIMEOUT_MS) {
@Override
protected boolean check() {
@@ -966,7 +984,7 @@
AudioAttributesCompat attrs = new AudioAttributesCompat.Builder()
.setLegacyStreamType(stream)
.build();
- mPlayer.setAudioAttributes(attrs);
+ mPlayer.mAudioAttributes = attrs;
mSession.updatePlayer(mPlayer);
final int originalVolume = mAudioManager.getStreamVolume(stream);
@@ -975,7 +993,9 @@
final int targetVolume = originalVolume + direction;
Log.d(TAG, "originalVolume=" + originalVolume + ", targetVolume=" + targetVolume);
- mController.adjustVolume(direction, AudioManager.FLAG_SHOW_UI);
+ SessionResult result = mController.adjustVolume(direction, AudioManager.FLAG_SHOW_UI)
+ .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertEquals(RESULT_SUCCESS, result.getResultCode());
new PollingCheck(VOLUME_CHANGE_TIMEOUT_MS) {
@Override
protected boolean check() {
@@ -995,7 +1015,7 @@
}
@Test
- public void testSendCustomCommand() throws InterruptedException {
+ public void testSendCustomCommand() throws Exception {
prepareLooper();
// TODO(jaewan): Need to revisit with the permission.
final String command = "test_custom_command";
@@ -1031,7 +1051,9 @@
mSession = new MediaSession.Builder(mContext, mPlayer)
.setSessionCallback(sHandlerExecutor, callback).setId(TAG).build();
final MediaController controller = createController(mSession.getToken());
- controller.sendCustomCommand(testCommand, testArgs);
+ SessionResult result = controller.sendCustomCommand(testCommand, testArgs)
+ .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertEquals(RESULT_SUCCESS, result.getResultCode());
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
@@ -1084,7 +1106,7 @@
}
@Test
- public void testFastForward() throws InterruptedException {
+ public void testFastForward() throws Exception {
prepareLooper();
final CountDownLatch latch = new CountDownLatch(1);
final SessionCallback callback = new SessionCallback() {
@@ -1100,13 +1122,14 @@
.setSessionCallback(sHandlerExecutor, callback)
.setId("testFastForward").build()) {
MediaController controller = createController(session.getToken());
- controller.fastForward();
+ SessionResult result = controller.fastForward().get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertEquals(RESULT_SUCCESS, result.getResultCode());
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
}
@Test
- public void testRewind() throws InterruptedException {
+ public void testRewind() throws Exception {
prepareLooper();
final CountDownLatch latch = new CountDownLatch(1);
final SessionCallback callback = new SessionCallback() {
@@ -1121,13 +1144,14 @@
.setSessionCallback(sHandlerExecutor, callback)
.setId("testRewind").build()) {
MediaController controller = createController(session.getToken());
- controller.rewind();
+ SessionResult result = controller.rewind().get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertEquals(RESULT_SUCCESS, result.getResultCode());
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
}
@Test
- public void testPlayFromSearch() throws InterruptedException {
+ public void testPlayFromSearch() throws Exception {
prepareLooper();
final String request = "random query";
final Bundle bundle = new Bundle();
@@ -1149,13 +1173,15 @@
.setSessionCallback(sHandlerExecutor, callback)
.setId("testPlayFromSearch").build()) {
MediaController controller = createController(session.getToken());
- controller.playFromSearch(request, bundle);
+ SessionResult result = controller.playFromSearch(request, bundle)
+ .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertEquals(RESULT_SUCCESS, result.getResultCode());
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
}
@Test
- public void testPlayFromUri() throws InterruptedException {
+ public void testPlayFromUri() throws Exception {
prepareLooper();
final Uri request = Uri.parse("foo://boo");
final Bundle bundle = new Bundle();
@@ -1176,13 +1202,15 @@
.setSessionCallback(sHandlerExecutor, callback)
.setId("testPlayFromUri").build()) {
MediaController controller = createController(session.getToken());
- controller.playFromUri(request, bundle);
+ SessionResult result = controller.playFromUri(request, bundle)
+ .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertEquals(RESULT_SUCCESS, result.getResultCode());
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
}
@Test
- public void testPlayFromMediaId() throws InterruptedException {
+ public void testPlayFromMediaId() throws Exception {
prepareLooper();
final String request = "media_id";
final Bundle bundle = new Bundle();
@@ -1203,13 +1231,15 @@
.setSessionCallback(sHandlerExecutor, callback)
.setId("testPlayFromMediaId").build()) {
MediaController controller = createController(session.getToken());
- controller.playFromMediaId(request, bundle);
+ SessionResult result = controller.playFromMediaId(request, bundle)
+ .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertEquals(RESULT_SUCCESS, result.getResultCode());
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
}
@Test
- public void testPrepareFromSearch() throws InterruptedException {
+ public void testPrepareFromSearch() throws Exception {
prepareLooper();
final String request = "random query";
final Bundle bundle = new Bundle();
@@ -1230,13 +1260,15 @@
.setSessionCallback(sHandlerExecutor, callback)
.setId("testPrepareFromSearch").build()) {
MediaController controller = createController(session.getToken());
- controller.prepareFromSearch(request, bundle);
+ SessionResult result = controller.prepareFromSearch(request, bundle)
+ .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertEquals(RESULT_SUCCESS, result.getResultCode());
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
}
@Test
- public void testPrepareFromUri() throws InterruptedException {
+ public void testPrepareFromUri() throws Exception {
prepareLooper();
final Uri request = Uri.parse("foo://boo");
final Bundle bundle = new Bundle();
@@ -1257,13 +1289,15 @@
.setSessionCallback(sHandlerExecutor, callback)
.setId("testPrepareFromUri").build()) {
MediaController controller = createController(session.getToken());
- controller.prepareFromUri(request, bundle);
+ SessionResult result = controller.prepareFromUri(request, bundle)
+ .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertEquals(RESULT_SUCCESS, result.getResultCode());
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
}
@Test
- public void testPrepareFromMediaId() throws InterruptedException {
+ public void testPrepareFromMediaId() throws Exception {
prepareLooper();
final String request = "media_id";
final Bundle bundle = new Bundle();
@@ -1284,13 +1318,15 @@
.setSessionCallback(sHandlerExecutor, callback)
.setId("testPrepareFromMediaId").build()) {
MediaController controller = createController(session.getToken());
- controller.prepareFromMediaId(request, bundle);
+ SessionResult result = controller.prepareFromMediaId(request, bundle)
+ .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertEquals(RESULT_SUCCESS, result.getResultCode());
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
}
@Test
- public void testSetRating() throws InterruptedException {
+ public void testSetRating() throws Exception {
prepareLooper();
final float ratingValue = 3.5f;
final Rating rating = new StarRating(5, ratingValue);
@@ -1314,7 +1350,9 @@
.setSessionCallback(sHandlerExecutor, callback)
.setId("testSetRating").build()) {
MediaController controller = createController(session.getToken());
- controller.setRating(mediaId, rating);
+ SessionResult result = controller.setRating(mediaId, rating)
+ .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertEquals(RESULT_SUCCESS, result.getResultCode());
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
}
@@ -1367,6 +1405,7 @@
});
final MediaController controller = createController(mSession.getToken());
testHandler.post(new Runnable() {
+ @SuppressWarnings("FutureReturnValueIgnored")
@Override
public void run() {
final int state = SessionPlayer.PLAYER_STATE_ERROR;
@@ -1423,18 +1462,18 @@
}
@Test
- public void testConnectToService_sessionService() throws InterruptedException {
+ public void testConnectToService_sessionService() throws Exception {
prepareLooper();
testConnectToService(MockMediaSessionService.ID);
}
@Test
- public void testConnectToService_libraryService() throws InterruptedException {
+ public void testConnectToService_libraryService() throws Exception {
prepareLooper();
testConnectToService(MockMediaLibraryService.ID);
}
- public void testConnectToService(String id) throws InterruptedException {
+ public void testConnectToService(String id) throws Exception {
prepareLooper();
final CountDownLatch latch = new CountDownLatch(1);
final MediaLibrarySessionCallback sessionCallback = new MediaLibrarySessionCallback() {
@@ -1474,7 +1513,8 @@
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
// Test command from controller to session service.
- mController.play();
+ SessionResult result = mController.play().get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertEquals(RESULT_SUCCESS, result.getResultCode());
assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertTrue(mPlayer.mPlayCalled);
@@ -1492,7 +1532,7 @@
@LargeTest
@Test
- public void testControllerAfterSessionIsClosed_sessionService() throws InterruptedException {
+ public void testControllerAfterSessionIsClosed_sessionService() throws Exception {
prepareLooper();
testConnectToService(MockMediaSessionService.ID);
testControllerAfterSessionIsClosed(MockMediaSessionService.ID);
@@ -1601,7 +1641,7 @@
}
};
MediaController controller = createController(mSession.getToken(), true, null, callback);
- mPlayer.setMediaItem(item);
+ mPlayer.mCurrentMediaItem = mPlayer.mItem = item;
mPlayer.notifyCurrentMediaItemChanged(item);
assertFalse(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
item.setMetadata(TestUtils.createMetadata(item.getMediaId(), duration));
@@ -1638,8 +1678,9 @@
}
};
MediaController controller = createController(mSession.getToken(), true, null, callback);
- mPlayer.setPlaylist(list, null);
- mPlayer.skipToPlaylistItem(currentItemIdx);
+ mPlayer.mPlaylist = list;
+ mPlayer.mIndex = currentItemIdx;
+ mPlayer.mCurrentMediaItem = mPlayer.mItem = mPlayer.mPlaylist.get(currentItemIdx);
mPlayer.notifyPlaylistChanged();
assertFalse(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
list.get(1).setMetadata(newMetadata);
@@ -1649,8 +1690,9 @@
@Test
public void testGetVideoSize() throws InterruptedException {
prepareLooper();
+ final MediaItem item = TestUtils.createMediaItemWithMetadata();
final VideoSize testVideoSize = new VideoSize(100, 42);
- final CountDownLatch latch = new CountDownLatch(1);
+ final CountDownLatch latch = new CountDownLatch(2);
final ControllerCallback callback = new ControllerCallback() {
@Override
public void onVideoSizeChanged(@NonNull MediaController controller,
@@ -1659,8 +1701,16 @@
assertEquals(testVideoSize, videoSize);
latch.countDown();
}
+
+ @Override
+ public void onVideoSizeChanged(@NonNull MediaController controller,
+ @NonNull VideoSize videoSize) {
+ assertEquals(testVideoSize, videoSize);
+ latch.countDown();
+ }
};
MediaController controller = createController(mSession.getToken(), true, null, callback);
+ mPlayer.notifyCurrentMediaItemChanged(item);
mPlayer.notifyVideoSizeChanged(testVideoSize);
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertEquals(testVideoSize, controller.getVideoSize());
diff --git a/media2/session/src/androidTest/java/androidx/media2/session/MediaSessionTest.java b/media2/session/src/androidTest/java/androidx/media2/session/MediaSessionTest.java
index b41b628..c6a1640 100644
--- a/media2/session/src/androidTest/java/androidx/media2/session/MediaSessionTest.java
+++ b/media2/session/src/androidTest/java/androidx/media2/session/MediaSessionTest.java
@@ -159,7 +159,7 @@
final AudioAttributesCompat attrs = new AudioAttributesCompat.Builder()
.setContentType(CONTENT_TYPE_MUSIC)
.build();
- player.setAudioAttributes(attrs);
+ player.mAudioAttributes = attrs;
final int maxVolume = 100;
final int currentVolume = 23;
@@ -217,37 +217,37 @@
}
@Test
- public void testPlay() throws Exception {
+ public void testPlay() {
prepareLooper();
- mSession.getPlayer().play();
+ assertNotNull(mSession.getPlayer().play());
assertTrue(mPlayer.mPlayCalled);
}
@Test
- public void testPause() throws Exception {
+ public void testPause() {
prepareLooper();
- mSession.getPlayer().pause();
+ assertNotNull(mSession.getPlayer().pause());
assertTrue(mPlayer.mPauseCalled);
}
@Test
- public void testPrepare() throws Exception {
+ public void testPrepare() {
prepareLooper();
- mSession.getPlayer().prepare();
+ assertNotNull(mSession.getPlayer().prepare());
assertTrue(mPlayer.mPrepareCalled);
}
@Test
- public void testSeekTo() throws Exception {
+ public void testSeekTo() {
prepareLooper();
final long pos = 1004L;
- mSession.getPlayer().seekTo(pos);
+ assertNotNull(mSession.getPlayer().seekTo(pos));
assertTrue(mPlayer.mSeekToCalled);
assertEquals(pos, mPlayer.mSeekPosition);
}
@Test
- public void testGetDuration() throws Exception {
+ public void testGetDuration() {
prepareLooper();
final long testDuration = 9999;
mPlayer.mDuration = testDuration;
@@ -256,19 +256,19 @@
}
@Test
- public void testSetPlaybackSpeed() throws Exception {
+ public void testSetPlaybackSpeed() {
prepareLooper();
final float speed = 1.5f;
- mSession.getPlayer().setPlaybackSpeed(speed);
+ assertNotNull(mSession.getPlayer().setPlaybackSpeed(speed));
assertTrue(mPlayer.mSetPlaybackSpeedCalled);
assertEquals(speed, mPlayer.mPlaybackSpeed, 0.0f);
}
@Test
- public void testGetPlaybackSpeed() throws Exception {
+ public void testGetPlaybackSpeed() {
prepareLooper();
final float speed = 1.5f;
- mPlayer.setPlaybackSpeed(speed);
+ mPlayer.mPlaybackSpeed = speed;
mPlayer.mLastPlayerState = SessionPlayer.PLAYER_STATE_PLAYING;
assertEquals(speed, mSession.getPlayer().getPlaybackSpeed(), 0.0f);
}
@@ -284,24 +284,24 @@
@Test
public void testSkipToPreviousItem() {
prepareLooper();
- mSession.getPlayer().skipToPreviousPlaylistItem();
+ assertNotNull(mSession.getPlayer().skipToPreviousPlaylistItem());
assertTrue(mPlayer.mSkipToPreviousItemCalled);
}
@Test
- public void testSkipToNextItem() throws Exception {
+ public void testSkipToNextItem() {
prepareLooper();
- mSession.getPlayer().skipToNextPlaylistItem();
+ assertNotNull(mSession.getPlayer().skipToNextPlaylistItem());
assertTrue(mPlayer.mSkipToNextItemCalled);
}
@Test
- public void testSkipToPlaylistItem() throws Exception {
+ public void testSkipToPlaylistItem() {
prepareLooper();
final List<MediaItem> list = TestUtils.createMediaItems(2);
int targetIndex = 0;
- mSession.getPlayer().setPlaylist(list, null);
- mSession.getPlayer().skipToPlaylistItem(targetIndex);
+ assertNotNull(mSession.getPlayer().setPlaylist(list, null));
+ assertNotNull(mSession.getPlayer().skipToPlaylistItem(targetIndex));
assertTrue(mPlayer.mSkipToPlaylistItemCalled);
assertSame(targetIndex, mPlayer.mIndex);
}
@@ -344,7 +344,7 @@
public void testSetPlaylist() {
prepareLooper();
final List<MediaItem> list = TestUtils.createMediaItems(2);
- mSession.getPlayer().setPlaylist(list, null);
+ assertNotNull(mSession.getPlayer().setPlaylist(list, null));
assertTrue(mPlayer.mSetPlaylistCalled);
assertSame(list, mPlayer.mPlaylist);
assertNull(mPlayer.mMetadata);
@@ -362,7 +362,7 @@
public void testUpdatePlaylistMetadata() {
prepareLooper();
final MediaMetadata testMetadata = TestUtils.createMetadata();
- mSession.getPlayer().updatePlaylistMetadata(testMetadata);
+ assertNotNull(mSession.getPlayer().updatePlaylistMetadata(testMetadata));
assertTrue(mPlayer.mUpdatePlaylistMetadataCalled);
assertSame(testMetadata, mPlayer.mMetadata);
}
@@ -380,7 +380,7 @@
prepareLooper();
final int testIndex = 12;
final MediaItem testMediaItem = TestUtils.createMediaItemWithMetadata();
- mSession.getPlayer().addPlaylistItem(testIndex, testMediaItem);
+ assertNotNull(mSession.getPlayer().addPlaylistItem(testIndex, testMediaItem));
assertTrue(mPlayer.mAddPlaylistItemCalled);
assertEquals(testIndex, mPlayer.mIndex);
assertSame(testMediaItem, mPlayer.mItem);
@@ -391,18 +391,18 @@
prepareLooper();
final List<MediaItem> list = TestUtils.createMediaItems(2);
int targetIndex = 0;
- mSession.getPlayer().setPlaylist(list, null);
- mSession.getPlayer().removePlaylistItem(targetIndex);
+ assertNotNull(mSession.getPlayer().setPlaylist(list, null));
+ assertNotNull(mSession.getPlayer().removePlaylistItem(targetIndex));
assertTrue(mPlayer.mRemovePlaylistItemCalled);
assertSame(targetIndex, mPlayer.mIndex);
}
@Test
- public void testReplacePlaylistItem() throws InterruptedException {
+ public void testReplacePlaylistItem() {
prepareLooper();
final int testIndex = 12;
final MediaItem testMediaItem = TestUtils.createMediaItemWithMetadata();
- mSession.getPlayer().replacePlaylistItem(testIndex, testMediaItem);
+ assertNotNull(mSession.getPlayer().replacePlaylistItem(testIndex, testMediaItem));
assertTrue(mPlayer.mReplacePlaylistItemCalled);
assertEquals(testIndex, mPlayer.mIndex);
assertSame(testMediaItem, mPlayer.mItem);
@@ -412,7 +412,7 @@
public void testSetShuffleMode() {
prepareLooper();
final int testShuffleMode = SessionPlayer.SHUFFLE_MODE_GROUP;
- mSession.getPlayer().setShuffleMode(testShuffleMode);
+ assertNotNull(mSession.getPlayer().setShuffleMode(testShuffleMode));
assertTrue(mPlayer.mSetShuffleModeCalled);
assertEquals(testShuffleMode, mPlayer.mShuffleMode);
}
@@ -421,13 +421,13 @@
public void testSetRepeatMode() {
prepareLooper();
final int testRepeatMode = SessionPlayer.REPEAT_MODE_GROUP;
- mSession.getPlayer().setRepeatMode(testRepeatMode);
+ assertNotNull(mSession.getPlayer().setRepeatMode(testRepeatMode));
assertTrue(mPlayer.mSetRepeatModeCalled);
assertEquals(testRepeatMode, mPlayer.mRepeatMode);
}
@Test
- public void testOnCommandCallback() throws InterruptedException {
+ public void testOnCommandCallback() throws Exception {
prepareLooper();
final MockOnCommandCallback callback = new MockOnCommandCallback();
sHandler.postAndSync(new Runnable() {
@@ -440,14 +440,16 @@
}
});
MediaController controller = createController(mSession.getToken());
- controller.pause();
+ SessionResult pauseResult = controller.pause().get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertEquals(RESULT_ERROR_INVALID_STATE, pauseResult.getResultCode());
assertFalse(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertFalse(mPlayer.mPauseCalled);
assertEquals(1, callback.commands.size());
assertEquals(SessionCommand.COMMAND_CODE_PLAYER_PAUSE,
- (long) callback.commands.get(0).getCommandCode());
+ callback.commands.get(0).getCommandCode());
- controller.play();
+ SessionResult playResult = controller.play().get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertEquals(RESULT_SUCCESS, playResult.getResultCode());
assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertTrue(mPlayer.mPlayCalled);
assertFalse(mPlayer.mPauseCalled);
@@ -495,7 +497,7 @@
}
@Test
- public void testSetCustomLayout() throws InterruptedException {
+ public void testSetCustomLayout() throws Exception {
prepareLooper();
final List<CommandButton> customLayout = new ArrayList<>();
customLayout.add(new CommandButton.Builder()
@@ -534,7 +536,9 @@
}
};
MediaController controller = createController(session.getToken(), true, null, callback);
- session.setCustomLayout(mTestControllerInfo, customLayout);
+ SessionResult result = session.setCustomLayout(mTestControllerInfo, customLayout)
+ .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertEquals(RESULT_SUCCESS, result.getResultCode());
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
}
@@ -571,7 +575,7 @@
}
@Test
- public void testSendCustomCommand() throws InterruptedException {
+ public void testSendCustomCommand() throws Exception {
prepareLooper();
final SessionCommand testCommand = new SessionCommand("test_command_code", null);
final Bundle testArgs = new Bundle();
@@ -597,7 +601,9 @@
ControllerInfo controllerInfo = getTestControllerInfo();
assertNotNull(controllerInfo);
// TODO(jaewan): Test receivers as well.
- mSession.sendCustomCommand(controllerInfo, testCommand, testArgs);
+ SessionResult result = mSession.sendCustomCommand(controllerInfo, testCommand, testArgs)
+ .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertEquals(RESULT_SUCCESS, result.getResultCode());
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
@@ -615,7 +621,7 @@
@Nullable
public SessionCommandGroup onConnect(@NonNull MediaSession session,
@NonNull ControllerInfo controller) {
- session.sendCustomCommand(controller, testCommand, null);
+ assertNotNull(session.sendCustomCommand(controller, testCommand, null));
return super.onConnect(session, controller);
}
};
@@ -659,7 +665,7 @@
@Override
public void onPostConnect(@NonNull MediaSession session,
@NonNull ControllerInfo controller) {
- session.sendCustomCommand(controller, testCommand, null);
+ assertNotNull(session.sendCustomCommand(controller, testCommand, null));
}
};
final ControllerCallback testControllerCallback = new ControllerCallback() {
diff --git a/media2/session/src/androidTest/java/androidx/media2/session/MockMediaBrowserServiceCompat.java b/media2/session/src/androidTest/java/androidx/media2/session/MockMediaBrowserServiceCompat.java
index 2f3fd4f..a33ac3a 100644
--- a/media2/session/src/androidTest/java/androidx/media2/session/MockMediaBrowserServiceCompat.java
+++ b/media2/session/src/androidTest/java/androidx/media2/session/MockMediaBrowserServiceCompat.java
@@ -22,13 +22,12 @@
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.MediaSessionCompat.Callback;
+import androidx.annotation.GuardedBy;
import androidx.media.MediaBrowserServiceCompat;
import java.lang.reflect.Method;
import java.util.List;
-import javax.annotation.concurrent.GuardedBy;
-
/**
* Mock implementation of the browser.
*/
diff --git a/media2/session/src/androidTest/java/androidx/media2/session/MockMediaLibraryService.java b/media2/session/src/androidTest/java/androidx/media2/session/MockMediaLibraryService.java
index f6c5185..c52bd8c 100644
--- a/media2/session/src/androidTest/java/androidx/media2/session/MockMediaLibraryService.java
+++ b/media2/session/src/androidTest/java/androidx/media2/session/MockMediaLibraryService.java
@@ -25,6 +25,7 @@
import static androidx.media2.session.LibraryResult.RESULT_SUCCESS;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
import android.content.ComponentName;
import android.content.Context;
@@ -32,6 +33,7 @@
import android.text.TextUtils;
import android.util.Log;
+import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.media2.common.MediaItem;
import androidx.media2.common.MediaMetadata;
@@ -46,8 +48,6 @@
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
-import javax.annotation.concurrent.GuardedBy;
-
/**
* Mock implementation of {@link MediaLibraryService} for testing.
*/
@@ -221,13 +221,13 @@
params);
} else if (SEARCH_QUERY_TAKES_TIME.equals(query)) {
// Searching takes some time. Notify after 5 seconds.
- Executors.newSingleThreadScheduledExecutor().schedule(new Runnable() {
+ assertNotNull(Executors.newSingleThreadScheduledExecutor().schedule(new Runnable() {
@Override
public void run() {
mSession.notifySearchResultChanged(
controllerInfo, query, SEARCH_RESULT_COUNT, params);
}
- }, SEARCH_TIME_IN_MS, TimeUnit.MILLISECONDS);
+ }, SEARCH_TIME_IN_MS, TimeUnit.MILLISECONDS));
} else if (SEARCH_QUERY_EMPTY_RESULT.equals(query)) {
mSession.notifySearchResultChanged(controllerInfo, query, 0, params);
} else {
diff --git a/media2/session/src/androidTest/java/androidx/media2/session/MockPlayer.java b/media2/session/src/androidTest/java/androidx/media2/session/MockPlayer.java
index 4d935f2..12a672c 100644
--- a/media2/session/src/androidTest/java/androidx/media2/session/MockPlayer.java
+++ b/media2/session/src/androidTest/java/androidx/media2/session/MockPlayer.java
@@ -87,7 +87,7 @@
public boolean mSetRepeatModeCalled;
public boolean mSetShuffleModeCalled;
- private AudioAttributesCompat mAudioAttributes;
+ AudioAttributesCompat mAudioAttributes;
public MockPlayer(int count) {
this(count, false);
diff --git a/media2/session/src/androidTest/java/androidx/media2/session/TestBrowserCallback.java b/media2/session/src/androidTest/java/androidx/media2/session/TestBrowserCallback.java
index 0d7bed7..cb17489 100644
--- a/media2/session/src/androidTest/java/androidx/media2/session/TestBrowserCallback.java
+++ b/media2/session/src/androidTest/java/androidx/media2/session/TestBrowserCallback.java
@@ -183,6 +183,12 @@
}
@Override
+ public void onVideoSizeChanged(@NonNull MediaController controller,
+ @NonNull VideoSize videoSize) {
+ mCallbackProxy.onVideoSizeChanged(controller, videoSize);
+ }
+
+ @Override
public void onSubtitleData(@NonNull MediaController controller, @NonNull MediaItem item,
@NonNull SessionPlayer.TrackInfo track, @NonNull SubtitleData data) {
mCallbackProxy.onSubtitleData(controller, item, track, data);
diff --git a/media2/session/src/main/java/androidx/media2/session/MediaController.java b/media2/session/src/main/java/androidx/media2/session/MediaController.java
index 3413d57..bbe069d 100644
--- a/media2/session/src/main/java/androidx/media2/session/MediaController.java
+++ b/media2/session/src/main/java/androidx/media2/session/MediaController.java
@@ -1632,6 +1632,8 @@
*
* @param connectionHints a bundle which contains the connection hints
* @return The Builder to allow chaining
+ * @throws IllegalArgumentException if the bundle contains any non-framework Parcelable
+ * objects.
*/
@NonNull
@SuppressWarnings("unchecked")
@@ -1639,6 +1641,10 @@
if (connectionHints == null) {
throw new NullPointerException("connectionHints shouldn't be null");
}
+ if (MediaUtils.doesBundleHaveCustomParcelable(connectionHints)) {
+ throw new IllegalArgumentException(
+ "connectionHints shouldn't contain any custom parcelables");
+ }
mConnectionHints = new Bundle(connectionHints);
return (U) this;
}
@@ -1877,16 +1883,25 @@
public void onPlaybackCompleted(@NonNull MediaController controller) {}
/**
+ * @deprecated Use {@link #onVideoSizeChanged(MediaController, VideoSize)} instead.
+ * @hide
+ */
+ @RestrictTo(LIBRARY_GROUP)
+ @Deprecated
+ public void onVideoSizeChanged(@NonNull MediaController controller, @NonNull MediaItem item,
+ @NonNull VideoSize videoSize) {}
+
+ /**
* Called when video size is changed.
*
* @param controller the controller for this event
- * @param item the media item for which the video size changed
* @param videoSize the size of video
*
* @hide
*/
+ // TODO: Unhide this (b/134749006)
@RestrictTo(LIBRARY_GROUP)
- public void onVideoSizeChanged(@NonNull MediaController controller, @NonNull MediaItem item,
+ public void onVideoSizeChanged(@NonNull MediaController controller,
@NonNull VideoSize videoSize) {}
/**
diff --git a/media2/session/src/main/java/androidx/media2/session/MediaControllerImplBase.java b/media2/session/src/main/java/androidx/media2/session/MediaControllerImplBase.java
index 755a17e..3ceb640 100644
--- a/media2/session/src/main/java/androidx/media2/session/MediaControllerImplBase.java
+++ b/media2/session/src/main/java/androidx/media2/session/MediaControllerImplBase.java
@@ -1144,9 +1144,11 @@
});
}
- void notifyVideoSizeChanged(final MediaItem item, final VideoSize videoSize) {
+ void notifyVideoSizeChanged(final VideoSize videoSize) {
+ final MediaItem currentItem;
synchronized (mLock) {
mVideoSize = videoSize;
+ currentItem = mCurrentMediaItem;
}
mInstance.notifyControllerCallback(new ControllerCallbackRunnable() {
@Override
@@ -1154,7 +1156,11 @@
if (!mInstance.isConnected()) {
return;
}
- callback.onVideoSizeChanged(mInstance, item, videoSize);
+
+ if (currentItem != null) {
+ callback.onVideoSizeChanged(mInstance, currentItem, videoSize);
+ }
+ callback.onVideoSizeChanged(mInstance, videoSize);
}
});
}
diff --git a/media2/session/src/main/java/androidx/media2/session/MediaControllerStub.java b/media2/session/src/main/java/androidx/media2/session/MediaControllerStub.java
index 142dca2..8e16d21 100644
--- a/media2/session/src/main/java/androidx/media2/session/MediaControllerStub.java
+++ b/media2/session/src/main/java/androidx/media2/session/MediaControllerStub.java
@@ -242,23 +242,18 @@
@Override
public void onVideoSizeChanged(int seq, final ParcelImpl item, final ParcelImpl videoSize) {
- if (item == null || videoSize == null) {
+ if (videoSize == null) {
return;
}
dispatchControllerTask(new ControllerTask() {
@Override
public void run(MediaControllerImplBase controller) {
- MediaItem itemObj = MediaParcelUtils.fromParcelable(item);
- if (itemObj == null) {
- Log.w(TAG, "onVideoSizeChanged(): Ignoring null MediaItem");
- return;
- }
VideoSize size = MediaParcelUtils.fromParcelable(videoSize);
if (size == null) {
Log.w(TAG, "onVideoSizeChanged(): Ignoring null VideoSize");
return;
}
- controller.notifyVideoSizeChanged(itemObj, size);
+ controller.notifyVideoSizeChanged(size);
}
});
}
diff --git a/media2/session/src/main/java/androidx/media2/session/MediaLibraryServiceLegacyStub.java b/media2/session/src/main/java/androidx/media2/session/MediaLibraryServiceLegacyStub.java
index 062046a..7244f81 100644
--- a/media2/session/src/main/java/androidx/media2/session/MediaLibraryServiceLegacyStub.java
+++ b/media2/session/src/main/java/androidx/media2/session/MediaLibraryServiceLegacyStub.java
@@ -492,8 +492,7 @@
}
@Override
- final void onVideoSizeChanged(int seq, @NonNull MediaItem item,
- @NonNull VideoSize videoSize) {
+ void onVideoSizeChanged(int seq, @NonNull VideoSize videoSize) throws RemoteException {
// No-op. BrowserCompat doesn't understand Controller features.
}
diff --git a/media2/session/src/main/java/androidx/media2/session/MediaSession.java b/media2/session/src/main/java/androidx/media2/session/MediaSession.java
index a1c663e..90664f5 100644
--- a/media2/session/src/main/java/androidx/media2/session/MediaSession.java
+++ b/media2/session/src/main/java/androidx/media2/session/MediaSession.java
@@ -915,7 +915,12 @@
mRemoteUserInfo = remoteUserInfo;
mIsTrusted = trusted;
mControllerCb = cb;
- mConnectionHints = connectionHints;
+ if (connectionHints == null
+ || MediaUtils.doesBundleHaveCustomParcelable(connectionHints)) {
+ mConnectionHints = null;
+ } else {
+ mConnectionHints = connectionHints;
+ }
}
/**
@@ -1201,8 +1206,8 @@
int currentIdx, int previousIdx, int nextIdx) throws RemoteException;
abstract void onPlaybackCompleted(int seq) throws RemoteException;
abstract void onDisconnected(int seq) throws RemoteException;
- abstract void onVideoSizeChanged(int seq, @NonNull MediaItem item,
- @NonNull VideoSize videoSize) throws RemoteException;
+ abstract void onVideoSizeChanged(int seq, @NonNull VideoSize videoSize)
+ throws RemoteException;
abstract void onTrackInfoChanged(int seq, List<TrackInfo> trackInfos,
TrackInfo selectedVideoTrack, TrackInfo selectedAudioTrack,
TrackInfo selectedSubtitleTrack, TrackInfo selectedMetadataTrack)
@@ -1358,9 +1363,11 @@
/**
* Set extras for the session token. If not set, {@link SessionToken#getExtras()}
- * will return {@link Bundle#EMPTY}.
+ * will return an empty {@link Bundle}.
*
* @return The Builder to allow chaining
+ * @throws IllegalArgumentException if the bundle contains any non-framework Parcelable
+ * objects.
* @see SessionToken#getExtras()
*/
@NonNull
@@ -1369,7 +1376,11 @@
if (extras == null) {
throw new NullPointerException("extras shouldn't be null");
}
- mExtras = extras;
+ if (MediaUtils.doesBundleHaveCustomParcelable(extras)) {
+ throw new IllegalArgumentException(
+ "extras shouldn't contain any custom parcelables");
+ }
+ mExtras = new Bundle(extras);
return (U) this;
}
diff --git a/media2/session/src/main/java/androidx/media2/session/MediaSessionImplBase.java b/media2/session/src/main/java/androidx/media2/session/MediaSessionImplBase.java
index 7f42fc3..eee1bea 100644
--- a/media2/session/src/main/java/androidx/media2/session/MediaSessionImplBase.java
+++ b/media2/session/src/main/java/androidx/media2/session/MediaSessionImplBase.java
@@ -1536,17 +1536,22 @@
}
@Override
- public void onVideoSizeChangedInternal(@NonNull final SessionPlayer player,
- @NonNull final MediaItem item, @NonNull final VideoSize videoSize) {
+ public void onVideoSizeChanged(@NonNull SessionPlayer player, @NonNull VideoSize size) {
dispatchRemoteControllerTask(player, new RemoteControllerTask() {
@Override
public void run(ControllerCb callback, int seq) throws RemoteException {
- callback.onVideoSizeChanged(seq, item, videoSize);
+ callback.onVideoSizeChanged(seq, size);
}
});
}
@Override
+ public void onVideoSizeChangedInternal(@NonNull final SessionPlayer player,
+ @NonNull final MediaItem item, @NonNull final VideoSize videoSize) {
+ onVideoSizeChanged(player, videoSize);
+ }
+
+ @Override
public void onTrackInfoChanged(@NonNull SessionPlayer player,
@NonNull final List<TrackInfo> trackInfos) {
final MediaSessionImplBase session = getSession();
diff --git a/media2/session/src/main/java/androidx/media2/session/MediaSessionLegacyStub.java b/media2/session/src/main/java/androidx/media2/session/MediaSessionLegacyStub.java
index 367034f..4776445 100644
--- a/media2/session/src/main/java/androidx/media2/session/MediaSessionLegacyStub.java
+++ b/media2/session/src/main/java/androidx/media2/session/MediaSessionLegacyStub.java
@@ -732,7 +732,7 @@
}
@Override
- void onVideoSizeChanged(int seq, @NonNull MediaItem item, @NonNull VideoSize videoSize) {
+ void onVideoSizeChanged(int seq, @NonNull VideoSize videoSize) throws RemoteException {
// no-op
}
@@ -947,7 +947,7 @@
}
@Override
- void onVideoSizeChanged(int seq, @NonNull MediaItem item, @NonNull VideoSize videoSize) {
+ void onVideoSizeChanged(int seq, @NonNull VideoSize videoSize) throws RemoteException {
// no-op
}
diff --git a/media2/session/src/main/java/androidx/media2/session/MediaSessionStub.java b/media2/session/src/main/java/androidx/media2/session/MediaSessionStub.java
index 0507f33..6833d59 100644
--- a/media2/session/src/main/java/androidx/media2/session/MediaSessionStub.java
+++ b/media2/session/src/main/java/androidx/media2/session/MediaSessionStub.java
@@ -1432,11 +1432,9 @@
}
@Override
- void onVideoSizeChanged(int seq, @NonNull MediaItem item, @NonNull VideoSize videoSize)
- throws RemoteException {
- ParcelImpl itemParcel = MediaParcelUtils.toParcelable(item);
+ void onVideoSizeChanged(int seq, @NonNull VideoSize videoSize) throws RemoteException {
ParcelImpl videoSizeParcel = MediaParcelUtils.toParcelable(videoSize);
- mIControllerCallback.onVideoSizeChanged(seq, itemParcel, videoSizeParcel);
+ mIControllerCallback.onVideoSizeChanged(seq, null, videoSizeParcel);
}
@Override
diff --git a/media2/session/src/main/java/androidx/media2/session/MediaUtils.java b/media2/session/src/main/java/androidx/media2/session/MediaUtils.java
index 51949fe..1b2b775 100644
--- a/media2/session/src/main/java/androidx/media2/session/MediaUtils.java
+++ b/media2/session/src/main/java/androidx/media2/session/MediaUtils.java
@@ -42,6 +42,7 @@
import android.content.Context;
import android.graphics.Bitmap;
import android.net.Uri;
+import android.os.BadParcelableException;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
@@ -55,6 +56,7 @@
import android.support.v4.media.session.PlaybackStateCompat;
import android.support.v4.media.session.PlaybackStateCompat.CustomAction;
import android.text.TextUtils;
+import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -849,4 +851,26 @@
}
return layout;
}
+
+ @SuppressWarnings("ParcelClassLoader")
+ static boolean doesBundleHaveCustomParcelable(@NonNull Bundle bundle) {
+ // Try writing the bundle to parcel, and read it with framework classloader.
+ Parcel parcel = Parcel.obtain();
+ try {
+ parcel.writeBundle(bundle);
+ parcel.setDataPosition(0);
+ Bundle out = parcel.readBundle(null);
+
+ if (out != null) {
+ // Calling Bundle#isEmpty() will trigger Bundle#unparcel().
+ out.isEmpty();
+ }
+ return false;
+ } catch (BadParcelableException e) {
+ Log.d(TAG, "Custom parcelables are not allowed", e);
+ return true;
+ } finally {
+ parcel.recycle();
+ }
+ }
}
diff --git a/media2/session/src/main/java/androidx/media2/session/SessionToken.java b/media2/session/src/main/java/androidx/media2/session/SessionToken.java
index f5dcebb..4779326 100644
--- a/media2/session/src/main/java/androidx/media2/session/SessionToken.java
+++ b/media2/session/src/main/java/androidx/media2/session/SessionToken.java
@@ -235,7 +235,11 @@
*/
@NonNull
public Bundle getExtras() {
- return mImpl.getExtras();
+ Bundle extras = mImpl.getExtras();
+ if (extras == null || MediaUtils.doesBundleHaveCustomParcelable(extras)) {
+ return Bundle.EMPTY;
+ }
+ return new Bundle(extras);
}
/**
@@ -436,7 +440,7 @@
@Nullable String getServiceName();
@Nullable ComponentName getComponentName();
@TokenType int getType();
- @NonNull Bundle getExtras();
+ @Nullable Bundle getExtras();
Object getBinder();
}
}
diff --git a/media2/session/src/main/java/androidx/media2/session/SessionTokenImplBase.java b/media2/session/src/main/java/androidx/media2/session/SessionTokenImplBase.java
index 279c944..1db4c73 100644
--- a/media2/session/src/main/java/androidx/media2/session/SessionTokenImplBase.java
+++ b/media2/session/src/main/java/androidx/media2/session/SessionTokenImplBase.java
@@ -156,9 +156,9 @@
}
@Override
- @NonNull
+ @Nullable
public Bundle getExtras() {
- return mExtras == null ? Bundle.EMPTY : new Bundle(mExtras);
+ return mExtras;
}
@Override
diff --git a/media2/session/src/main/java/androidx/media2/session/SessionTokenImplLegacy.java b/media2/session/src/main/java/androidx/media2/session/SessionTokenImplLegacy.java
index 0ba7b22..aecfb68 100644
--- a/media2/session/src/main/java/androidx/media2/session/SessionTokenImplLegacy.java
+++ b/media2/session/src/main/java/androidx/media2/session/SessionTokenImplLegacy.java
@@ -167,9 +167,9 @@
}
@Override
- @NonNull
+ @Nullable
public Bundle getExtras() {
- return mExtras == null ? Bundle.EMPTY : new Bundle(mExtras);
+ return mExtras;
}
@Override
diff --git a/media2/session/version-compat-tests/common/src/main/java/androidx/media2/test/common/CustomParcelable.java b/media2/session/version-compat-tests/common/src/main/java/androidx/media2/test/common/CustomParcelable.java
new file mode 100644
index 0000000..0090976
--- /dev/null
+++ b/media2/session/version-compat-tests/common/src/main/java/androidx/media2/test/common/CustomParcelable.java
@@ -0,0 +1,59 @@
+/*
+ * 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.media2.test.common;
+
+import android.annotation.SuppressLint;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Custom Parcelable class to test sending/receiving user parcelables between processes.
+ */
+@SuppressLint("BanParcelableUsage")
+public class CustomParcelable implements Parcelable {
+
+ private int mValue;
+
+ public CustomParcelable(int value) {
+ mValue = value;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @SuppressLint("UnknownNullness") // Parcel dest
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mValue);
+ }
+
+ public static final Parcelable.Creator<CustomParcelable> CREATOR =
+ new Parcelable.Creator<CustomParcelable>() {
+ @Override
+ public CustomParcelable createFromParcel(Parcel in) {
+ int value = in.readInt();
+ return new CustomParcelable(value);
+ }
+
+ @Override
+ public CustomParcelable[] newArray(int size) {
+ return new CustomParcelable[size];
+ }
+ };
+}
diff --git a/media2/session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerCallbackTest.java b/media2/session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerCallbackTest.java
index 8674218..9230345 100644
--- a/media2/session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerCallbackTest.java
+++ b/media2/session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerCallbackTest.java
@@ -865,7 +865,7 @@
prepareLooper();
final VideoSize testSize = new VideoSize(100, 42);
- final CountDownLatch latch = new CountDownLatch(1);
+ final CountDownLatch latch = new CountDownLatch(2);
final MediaController.ControllerCallback callback =
new MediaController.ControllerCallback() {
@Override
@@ -875,10 +875,18 @@
assertEquals(testSize, videoSize);
latch.countDown();
}
+
+ @Override
+ public void onVideoSizeChanged(@NonNull MediaController controller,
+ @NonNull VideoSize videoSize) {
+ assertEquals(testSize, videoSize);
+ latch.countDown();
+ }
};
MediaController controller = createController(mRemoteSession2.getToken(), true, null,
callback);
+ mRemoteSession2.getMockPlayer().notifyCurrentMediaItemChanged(INDEX_FOR_UNKONWN_ITEM);
mRemoteSession2.getMockPlayer().notifyVideoSizeChanged(testSize);
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
diff --git a/media2/session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerTest.java b/media2/session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerTest.java
index 127b8fe..4eb7be6 100644
--- a/media2/session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerTest.java
+++ b/media2/session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerTest.java
@@ -52,6 +52,7 @@
import androidx.media2.session.SessionToken;
import androidx.media2.test.client.MediaTestUtils;
import androidx.media2.test.client.RemoteMediaSession;
+import androidx.media2.test.common.CustomParcelable;
import androidx.media2.test.common.PollingCheck;
import androidx.media2.test.common.TestUtils;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -141,6 +142,16 @@
// expected. pass-through
}
+ try {
+ Bundle connectionHints = new Bundle();
+ connectionHints.putParcelable("key", new CustomParcelable(1));
+ builder = new MediaController.Builder(mContext);
+ builder.setConnectionHints(connectionHints);
+ fail("custom parcelables shouldn't be allowed for connectionHints");
+ } catch (IllegalArgumentException e) {
+ // expected. pass-through
+ }
+
MediaController controller = new MediaController.Builder(mContext)
.setSessionToken(mRemoteSession.getToken())
.setControllerCallback(sHandlerExecutor, new ControllerCallback() {})
diff --git a/media2/session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/TestBrowserCallback.java b/media2/session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/TestBrowserCallback.java
index b7d38fb..70073cd 100644
--- a/media2/session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/TestBrowserCallback.java
+++ b/media2/session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/TestBrowserCallback.java
@@ -189,6 +189,12 @@
}
@Override
+ public void onVideoSizeChanged(@NonNull MediaController controller,
+ @NonNull VideoSize videoSize) {
+ mCallbackProxy.onVideoSizeChanged(controller, videoSize);
+ }
+
+ @Override
public void onTrackInfoChanged(@NonNull MediaController controller,
@NonNull List<SessionPlayer.TrackInfo> trackInfos) {
mCallbackProxy.onTrackInfoChanged(controller, trackInfos);
diff --git a/media2/session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/MockPlayer.java b/media2/session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/MockPlayer.java
index eeee0e8..cbf4673 100644
--- a/media2/session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/MockPlayer.java
+++ b/media2/session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/MockPlayer.java
@@ -622,6 +622,7 @@
@Override
public void run() {
callback.onVideoSizeChangedInternal(MockPlayer.this, dummyItem, videoSize);
+ callback.onVideoSizeChanged(MockPlayer.this, videoSize);
}
});
}
diff --git a/media2/session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionTest.java b/media2/session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionTest.java
index 6811fa8..c083f05 100644
--- a/media2/session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionTest.java
+++ b/media2/session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionTest.java
@@ -21,6 +21,7 @@
import static org.junit.Assert.fail;
import android.os.Build;
+import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Process;
@@ -34,6 +35,7 @@
import androidx.media2.session.MediaSession;
import androidx.media2.session.MediaSession.SessionCallback;
import androidx.media2.session.SessionCommandGroup;
+import androidx.media2.test.common.CustomParcelable;
import androidx.media2.test.common.TestUtils.SyncHandler;
import androidx.media2.test.service.MediaTestUtils;
import androidx.media2.test.service.MockPlayer;
@@ -120,6 +122,15 @@
} catch (NullPointerException e) {
// expected. pass-through
}
+ try {
+ Bundle extras = new Bundle();
+ extras.putParcelable("key", new CustomParcelable(1));
+ builder = new MediaSession.Builder(mContext, mPlayer);
+ builder.setExtras(extras);
+ fail("custom parcelables shouldn't be allowed for extras");
+ } catch (IllegalArgumentException e) {
+ // expected. pass-through
+ }
}
@Test
diff --git a/media2/widget/OWNERS b/media2/widget/OWNERS
deleted file mode 100644
index b2c39ea..0000000
--- a/media2/widget/OWNERS
+++ /dev/null
@@ -1,8 +0,0 @@
-akersten@google.com
-dwkang@google.com
-gyumin@google.com
-hdmoon@google.com
-insun@google.com
-jaewan@google.com
-jinpark@google.com
-sungsoo@google.com
diff --git a/media2/widget/build.gradle b/media2/widget/build.gradle
index 2eb0e7f4..19cc8f8 100644
--- a/media2/widget/build.gradle
+++ b/media2/widget/build.gradle
@@ -46,6 +46,9 @@
defaultConfig {
minSdkVersion 19
}
+ sourceSets {
+ main.res.srcDirs += 'src/main/res-public'
+ }
lintOptions {
// Lint cannot determine the groupId of androidx.media2:media2widget,
// so it fails on calls to other media2 libraries.
diff --git a/media2/widget/src/main/java/androidx/media2/widget/PlayerWrapper.java b/media2/widget/src/main/java/androidx/media2/widget/PlayerWrapper.java
index 7a81bde..de3cc59 100644
--- a/media2/widget/src/main/java/androidx/media2/widget/PlayerWrapper.java
+++ b/media2/widget/src/main/java/androidx/media2/widget/PlayerWrapper.java
@@ -195,6 +195,8 @@
&& mAllowedCommands.hasCommand(SessionCommand.COMMAND_CODE_PLAYER_DESELECT_TRACK);
}
+ // TODO(b/138091975) Do not ignore the returned Future.
+ @SuppressWarnings("FutureReturnValueIgnored")
void pause() {
if (mController != null) {
mController.pause();
@@ -203,6 +205,8 @@
}
}
+ // TODO(b/138091975) Do not ignore the returned Future.
+ @SuppressWarnings("FutureReturnValueIgnored")
void play() {
if (mController != null) {
mController.play();
@@ -211,6 +215,8 @@
}
}
+ // TODO(b/138091975) Do not ignore the returned Future.
+ @SuppressWarnings("FutureReturnValueIgnored")
void seekTo(long posMs) {
if (mController != null) {
mController.seekTo(posMs);
@@ -219,6 +225,8 @@
}
}
+ // TODO(b/138091975) Do not ignore the returned Future.
+ @SuppressWarnings("FutureReturnValueIgnored")
void skipToNextItem() {
if (mController != null) {
mController.skipToNextPlaylistItem();
@@ -227,6 +235,8 @@
}
}
+ // TODO(b/138091975) Do not ignore the returned Future.
+ @SuppressWarnings("FutureReturnValueIgnored")
void skipToPreviousItem() {
if (mController != null) {
mController.skipToPreviousPlaylistItem();
@@ -244,6 +254,8 @@
return 1f;
}
+ // TODO(b/138091975) Do not ignore the returned Future.
+ @SuppressWarnings("FutureReturnValueIgnored")
void setPlaybackSpeed(float speed) {
if (mController != null) {
mController.setPlaybackSpeed(speed);
@@ -252,6 +264,8 @@
}
}
+ // TODO(b/138091975) Do not ignore the returned Future.
+ @SuppressWarnings("FutureReturnValueIgnored")
void selectTrack(TrackInfo trackInfo) {
if (mController != null) {
mController.selectTrack(trackInfo);
@@ -260,6 +274,8 @@
}
}
+ // TODO(b/138091975) Do not ignore the returned Future.
+ @SuppressWarnings("FutureReturnValueIgnored")
void deselectTrack(TrackInfo trackInfo) {
if (mController != null) {
mController.deselectTrack(trackInfo);
@@ -346,23 +362,11 @@
mWrapperCallback.onAllowedCommandsChanged(this, allowedCommands);
}
mWrapperCallback.onCurrentMediaItemChanged(this, item);
-
- // notify other non-cached states
- mWrapperCallback.onPlaybackSpeedChanged(this, getPlaybackSpeed());
- List<TrackInfo> trackInfos = getTrackInfo();
- if (trackInfos != null) {
- mWrapperCallback.onTrackInfoChanged(this, trackInfos);
- }
- if (item != null) {
- mWrapperCallback.onVideoSizeChanged(this, item, getVideoSize());
- }
+ notifyNonCachedStates();
}
@NonNull
VideoSize getVideoSize() {
- if (mSavedPlayerState == SessionPlayer.PLAYER_STATE_IDLE) {
- return new VideoSize(0, 0);
- }
if (mController != null) {
return mController.getVideoSize();
} else if (mPlayer != null) {
@@ -373,9 +377,6 @@
@NonNull
List<TrackInfo> getTrackInfo() {
- if (mSavedPlayerState == SessionPlayer.PLAYER_STATE_IDLE) {
- return null;
- }
if (mController != null) {
return mController.getTrackInfo();
} else if (mPlayer != null) {
@@ -386,9 +387,6 @@
@Nullable
TrackInfo getSelectedTrack(int trackType) {
- if (mSavedPlayerState == SessionPlayer.PLAYER_STATE_IDLE) {
- return null;
- }
if (mController != null) {
return mController.getSelectedTrack(trackType);
} else if (mPlayer != null) {
@@ -518,6 +516,19 @@
}
}
+ private void notifyNonCachedStates() {
+ mWrapperCallback.onPlaybackSpeedChanged(this, getPlaybackSpeed());
+
+ List<TrackInfo> trackInfos = getTrackInfo();
+ if (trackInfos != null) {
+ mWrapperCallback.onTrackInfoChanged(PlayerWrapper.this, trackInfos);
+ }
+ MediaItem item = getCurrentMediaItem();
+ if (item != null) {
+ mWrapperCallback.onVideoSizeChanged(PlayerWrapper.this, item, getVideoSize());
+ }
+ }
+
private class SessionPlayerCallback extends SessionPlayer.PlayerCallback {
SessionPlayerCallback() {
}
diff --git a/media2/widget/src/main/res/values/public.xml b/media2/widget/src/main/res-public/values/public_attrs.xml
similarity index 93%
rename from media2/widget/src/main/res/values/public.xml
rename to media2/widget/src/main/res-public/values/public_attrs.xml
index 6f7f00f..c0bcefd 100644
--- a/media2/widget/src/main/res/values/public.xml
+++ b/media2/widget/src/main/res-public/values/public_attrs.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright 2018 The Android Open Source Project
+ ~ 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.
diff --git a/navigation/benchmark/build.gradle b/navigation/benchmark/build.gradle
index 66e8c38..ad5d976 100644
--- a/navigation/benchmark/build.gradle
+++ b/navigation/benchmark/build.gradle
@@ -22,10 +22,11 @@
id("AndroidXPlugin")
id("com.android.library")
id("kotlin-android")
+ id("androidx.benchmark")
}
dependencies {
- androidTestImplementation(project(":benchmark"))
+ androidTestImplementation(project(":benchmark:benchmark-junit4"))
androidTestImplementation(project(":navigation:navigation-runtime"))
androidTestImplementation(project(":navigation:navigation-testing"))
androidTestImplementation(JUNIT)
diff --git a/navigation/benchmark/src/androidTest/java/androidx/navigation/NavInflaterBenchmark.kt b/navigation/benchmark/src/androidTest/java/androidx/navigation/NavInflaterBenchmark.kt
index 09b55d4..9ff4684 100644
--- a/navigation/benchmark/src/androidTest/java/androidx/navigation/NavInflaterBenchmark.kt
+++ b/navigation/benchmark/src/androidTest/java/androidx/navigation/NavInflaterBenchmark.kt
@@ -16,8 +16,8 @@
package androidx.navigation
-import androidx.benchmark.BenchmarkRule
-import androidx.benchmark.measureRepeated
+import androidx.benchmark.junit4.BenchmarkRule
+import androidx.benchmark.junit4.measureRepeated
import androidx.navigation.testing.TestNavigatorProvider
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
diff --git a/navigation/navigation-common/api/api_lint.ignore b/navigation/navigation-common/api/api_lint.ignore
index 59abda6..7fcf560 100644
--- a/navigation/navigation-common/api/api_lint.ignore
+++ b/navigation/navigation-common/api/api_lint.ignore
@@ -1,11 +1,3 @@
// Baseline format: 1.0
KotlinOperator: androidx.navigation.NavType#get(android.os.Bundle, String):
Method can be invoked with an indexing operator from Kotlin: `get` (this is usually desirable; just make sure it makes sense for this type of object)
-KotlinOperator: androidx.navigation.NavType.ParcelableArrayType#get(android.os.Bundle, String):
- Method can be invoked with an indexing operator from Kotlin: `get` (this is usually desirable; just make sure it makes sense for this type of object)
-KotlinOperator: androidx.navigation.NavType.ParcelableType#get(android.os.Bundle, String):
- Method can be invoked with an indexing operator from Kotlin: `get` (this is usually desirable; just make sure it makes sense for this type of object)
-KotlinOperator: androidx.navigation.NavType.SerializableArrayType#get(android.os.Bundle, String):
- Method can be invoked with an indexing operator from Kotlin: `get` (this is usually desirable; just make sure it makes sense for this type of object)
-KotlinOperator: androidx.navigation.NavType.SerializableType#get(android.os.Bundle, String):
- Method can be invoked with an indexing operator from Kotlin: `get` (this is usually desirable; just make sure it makes sense for this type of object)
diff --git a/navigation/navigation-fragment-ktx/build.gradle b/navigation/navigation-fragment-ktx/build.gradle
index b588ff0..dd016f5 100644
--- a/navigation/navigation-fragment-ktx/build.gradle
+++ b/navigation/navigation-fragment-ktx/build.gradle
@@ -51,6 +51,7 @@
androidTestImplementation(TRUTH)
androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy)
androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy)
+ androidTestImplementation project(':internal-testutils')
}
androidx {
diff --git a/navigation/navigation-fragment-ktx/src/androidTest/AndroidManifest.xml b/navigation/navigation-fragment-ktx/src/androidTest/AndroidManifest.xml
index 70a80b1..151e7a8 100644
--- a/navigation/navigation-fragment-ktx/src/androidTest/AndroidManifest.xml
+++ b/navigation/navigation-fragment-ktx/src/androidTest/AndroidManifest.xml
@@ -19,5 +19,6 @@
package="androidx.navigation.fragment.ktx">
<application>
<activity android:name="androidx.navigation.fragment.TestActivity" />
+ <activity android:name="androidx.navigation.fragment.NavGraphActivity" />
</application>
</manifest>
diff --git a/navigation/navigation-fragment-ktx/src/androidTest/java/androidx/navigation/fragment/NavGraphViewModelLazyTest.kt b/navigation/navigation-fragment-ktx/src/androidTest/java/androidx/navigation/fragment/NavGraphViewModelLazyTest.kt
index b124750..5a7eff9 100644
--- a/navigation/navigation-fragment-ktx/src/androidTest/java/androidx/navigation/fragment/NavGraphViewModelLazyTest.kt
+++ b/navigation/navigation-fragment-ktx/src/androidTest/java/androidx/navigation/fragment/NavGraphViewModelLazyTest.kt
@@ -22,19 +22,25 @@
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.testing.launchFragmentInContainer
+import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelStore
import androidx.navigation.NavHostController
import androidx.navigation.Navigation
+import androidx.navigation.findNavController
+import androidx.navigation.fragment.ktx.test.R
import androidx.navigation.navGraphViewModels
import androidx.navigation.navigation
import androidx.navigation.plusAssign
import androidx.navigation.testing.TestNavigator
import androidx.navigation.testing.test
+import androidx.test.core.app.ActivityScenario
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
+import androidx.testutils.withActivity
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@@ -57,21 +63,82 @@
Navigation.setViewNavController(fragment.requireView(), navController)
}
val navGraph = navController.navigatorProvider.navigation(
- id = GRAPH_ID,
- startDestination = DESTINATION_ID
+ id = R.id.vm_graph,
+ startDestination = R.id.start_destination
) {
- test(DESTINATION_ID)
+ test(R.id.start_destination)
}
navController.setGraph(navGraph, null)
scenario.onFragment { fragment ->
assertThat(fragment.viewModel).isNotNull()
+ assertThat(fragment.savedStateViewModel).isNotNull()
+ }
+ }
+
+ @Test
+ fun sameViewModelAcrossFragments() {
+ with(ActivityScenario.launch(NavGraphActivity::class.java)) {
+ val navController = withActivity { findNavController(R.id.nav_host_fragment) }
+ val firstFragment: TestVMFragment = withActivity {
+ val navHostFragment = supportFragmentManager
+ .findFragmentById(R.id.nav_host_fragment)!!
+ navHostFragment.childFragmentManager.primaryNavigationFragment as TestVMFragment
+ }
+ val viewModel = firstFragment.viewModel
+ val savedStateViewModel = firstFragment.savedStateViewModel
+ assertThat(viewModel).isNotNull()
+ assertThat(savedStateViewModel).isNotNull()
+
+ // First assert that we don't have any default value since the
+ // navigation graph wasn't sent any arguments
+ val initialState: String? = savedStateViewModel.savedStateHandle["test"]
+ assertThat(initialState).isNull()
+
+ // Now set arguments
+ savedStateViewModel.savedStateHandle.set("test", "test")
+
+ // Navigate to the second destination and ensure it
+ // gets the same ViewModels and data
+ navController.navigate(R.id.second_destination)
+ val secondFragment: TestVMFragment = withActivity {
+ val navHostFragment = supportFragmentManager
+ .findFragmentById(R.id.nav_host_fragment)!!
+ navHostFragment.childFragmentManager.primaryNavigationFragment as TestVMFragment
+ }
+ assertThat(secondFragment.viewModel)
+ .isSameInstanceAs(viewModel)
+ assertThat(secondFragment.savedStateViewModel)
+ .isSameInstanceAs(savedStateViewModel)
+ val savedValue: String? = secondFragment.savedStateViewModel
+ .savedStateHandle["test"]
+ assertThat(savedValue).isEqualTo("test")
+
+ // Now recreate the Activity and ensure that when we
+ // first request the nav graph ViewModel on the second destination
+ // that we get the same ViewModel and data back
+ recreate()
+ val recreatedFragment: TestVMFragment = withActivity {
+ val navHostFragment = supportFragmentManager
+ .findFragmentById(R.id.nav_host_fragment)!!
+ navHostFragment.childFragmentManager.primaryNavigationFragment as TestVMFragment
+ }
+ assertThat(recreatedFragment.viewModel)
+ .isSameInstanceAs(viewModel)
+ assertThat(recreatedFragment.savedStateViewModel)
+ .isSameInstanceAs(savedStateViewModel)
+ val recreatedValue: String? = recreatedFragment.savedStateViewModel
+ .savedStateHandle["test"]
+ assertThat(recreatedValue).isEqualTo("test")
}
}
}
+class NavGraphActivity : FragmentActivity(R.layout.activity_nav_graph)
+
class TestVMFragment : Fragment() {
- val viewModel: TestViewModel by navGraphViewModels(GRAPH_ID)
+ val viewModel: TestViewModel by navGraphViewModels(R.id.vm_graph)
+ val savedStateViewModel: TestSavedStateViewModel by navGraphViewModels(R.id.vm_graph)
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@@ -82,6 +149,4 @@
}
class TestViewModel : ViewModel()
-
-private const val GRAPH_ID = 1
-private const val DESTINATION_ID = 2
\ No newline at end of file
+class TestSavedStateViewModel(val savedStateHandle: SavedStateHandle) : ViewModel()
diff --git a/benchmark/src/androidTest/AndroidManifest.xml b/navigation/navigation-fragment-ktx/src/androidTest/res/layout/activity_nav_graph.xml
similarity index 67%
copy from benchmark/src/androidTest/AndroidManifest.xml
copy to navigation/navigation-fragment-ktx/src/androidTest/res/layout/activity_nav_graph.xml
index f5ec776..b8cdfcfb 100644
--- a/benchmark/src/androidTest/AndroidManifest.xml
+++ b/navigation/navigation-fragment-ktx/src/androidTest/res/layout/activity_nav_graph.xml
@@ -14,13 +14,13 @@
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.benchmark.test">
- <application
- android:name="androidx.benchmark.ArgumentInjectingApplication">
- <activity android:name="android.app.Activity"/>
- </application>
-</manifest>
\ No newline at end of file
+<fragment
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/nav_host_fragment"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:name="androidx.navigation.fragment.NavHostFragment"
+ app:defaultNavHost="true"
+ app:navGraph="@navigation/vm_graph" />
diff --git a/navigation/navigation-fragment-ktx/src/androidTest/res/navigation/vm_graph.xml b/navigation/navigation-fragment-ktx/src/androidTest/res/navigation/vm_graph.xml
new file mode 100644
index 0000000..b8daf2b
--- /dev/null
+++ b/navigation/navigation-fragment-ktx/src/androidTest/res/navigation/vm_graph.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+
+<navigation
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/vm_graph"
+ app:startDestination="@+id/start_destination">
+ <fragment
+ android:id="@+id/start_destination"
+ android:name="androidx.navigation.fragment.TestVMFragment">
+ <argument
+ android:name="test"
+ android:defaultValue="first"/>
+ </fragment>
+ <fragment
+ android:id="@+id/second_destination"
+ android:name="androidx.navigation.fragment.TestVMFragment">
+ <argument
+ android:name="test"
+ android:defaultValue="second"/>
+ </fragment>
+</navigation>
diff --git a/navigation/navigation-fragment-ktx/src/main/java/androidx/navigation/NavGraphViewModelLazy.kt b/navigation/navigation-fragment-ktx/src/main/java/androidx/navigation/NavGraphViewModelLazy.kt
index b4c0f16..75e7122 100644
--- a/navigation/navigation-fragment-ktx/src/main/java/androidx/navigation/NavGraphViewModelLazy.kt
+++ b/navigation/navigation-fragment-ktx/src/main/java/androidx/navigation/NavGraphViewModelLazy.kt
@@ -20,6 +20,7 @@
import androidx.annotation.MainThread
import androidx.fragment.app.Fragment
import androidx.fragment.app.createViewModelLazy
+import androidx.lifecycle.HasDefaultViewModelProviderFactory
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelStore
@@ -52,8 +53,14 @@
@IdRes navGraphId: Int,
noinline factoryProducer: (() -> ViewModelProvider.Factory)? = null
): Lazy<VM> {
- val storeProducer: () -> ViewModelStore = {
- findNavController().getViewModelStoreOwner(navGraphId).viewModelStore
+ val viewModelStoreOwner by lazy {
+ findNavController().getViewModelStoreOwner(navGraphId)
}
- return createViewModelLazy(VM::class, storeProducer, factoryProducer)
+ val storeProducer: () -> ViewModelStore = {
+ viewModelStoreOwner.viewModelStore
+ }
+ return createViewModelLazy(VM::class, storeProducer, {
+ factoryProducer?.invoke() ?: (viewModelStoreOwner as HasDefaultViewModelProviderFactory)
+ .defaultViewModelProviderFactory
+ })
}
\ No newline at end of file
diff --git a/navigation/navigation-runtime/api/2.1.0-rc01.ignore b/navigation/navigation-runtime/api/2.1.0-rc01.ignore
new file mode 100644
index 0000000..1ca5fd9
--- /dev/null
+++ b/navigation/navigation-runtime/api/2.1.0-rc01.ignore
@@ -0,0 +1,3 @@
+// Baseline format: 1.0
+RemovedDeprecatedMethod: androidx.navigation.NavController#getViewModelStore(int):
+ Removed deprecated method androidx.navigation.NavController.getViewModelStore(int)
diff --git a/navigation/navigation-runtime/api/2.2.0-alpha01.txt b/navigation/navigation-runtime/api/2.2.0-alpha01.txt
index f361023..5db4937 100644
--- a/navigation/navigation-runtime/api/2.2.0-alpha01.txt
+++ b/navigation/navigation-runtime/api/2.2.0-alpha01.txt
@@ -46,7 +46,6 @@
method public androidx.navigation.NavGraph getGraph();
method public androidx.navigation.NavInflater getNavInflater();
method public androidx.navigation.NavigatorProvider getNavigatorProvider();
- method @Deprecated public androidx.lifecycle.ViewModelStore getViewModelStore(@IdRes int);
method public androidx.lifecycle.ViewModelStoreOwner getViewModelStoreOwner(@IdRes int);
method public boolean handleDeepLink(android.content.Intent?);
method public void navigate(@IdRes int);
diff --git a/navigation/navigation-runtime/api/current.txt b/navigation/navigation-runtime/api/current.txt
index f361023..5db4937 100644
--- a/navigation/navigation-runtime/api/current.txt
+++ b/navigation/navigation-runtime/api/current.txt
@@ -46,7 +46,6 @@
method public androidx.navigation.NavGraph getGraph();
method public androidx.navigation.NavInflater getNavInflater();
method public androidx.navigation.NavigatorProvider getNavigatorProvider();
- method @Deprecated public androidx.lifecycle.ViewModelStore getViewModelStore(@IdRes int);
method public androidx.lifecycle.ViewModelStoreOwner getViewModelStoreOwner(@IdRes int);
method public boolean handleDeepLink(android.content.Intent?);
method public void navigate(@IdRes int);
diff --git a/navigation/navigation-runtime/api/restricted_2.1.0-rc01.ignore b/navigation/navigation-runtime/api/restricted_2.1.0-rc01.ignore
new file mode 100644
index 0000000..1ca5fd9
--- /dev/null
+++ b/navigation/navigation-runtime/api/restricted_2.1.0-rc01.ignore
@@ -0,0 +1,3 @@
+// Baseline format: 1.0
+RemovedDeprecatedMethod: androidx.navigation.NavController#getViewModelStore(int):
+ Removed deprecated method androidx.navigation.NavController.getViewModelStore(int)
diff --git a/navigation/navigation-runtime/api/restricted_2.1.0-rc01.txt b/navigation/navigation-runtime/api/restricted_2.1.0-rc01.txt
new file mode 100644
index 0000000..5db4937
--- /dev/null
+++ b/navigation/navigation-runtime/api/restricted_2.1.0-rc01.txt
@@ -0,0 +1,117 @@
+// Signature format: 3.0
+package androidx.navigation {
+
+ @androidx.navigation.Navigator.Name("activity") public class ActivityNavigator extends androidx.navigation.Navigator<androidx.navigation.ActivityNavigator.Destination> {
+ ctor public ActivityNavigator(android.content.Context);
+ method public static void applyPopAnimationsToPendingTransition(android.app.Activity);
+ method public androidx.navigation.ActivityNavigator.Destination createDestination();
+ method public androidx.navigation.NavDestination? navigate(androidx.navigation.ActivityNavigator.Destination, android.os.Bundle?, androidx.navigation.NavOptions?, androidx.navigation.Navigator.Extras?);
+ method public boolean popBackStack();
+ }
+
+ @androidx.navigation.NavDestination.ClassType(Activity.class) public static class ActivityNavigator.Destination extends androidx.navigation.NavDestination {
+ ctor public ActivityNavigator.Destination(androidx.navigation.NavigatorProvider);
+ ctor public ActivityNavigator.Destination(androidx.navigation.Navigator<? extends androidx.navigation.ActivityNavigator.Destination>);
+ method public final String? getAction();
+ method public final android.content.ComponentName? getComponent();
+ method public final android.net.Uri? getData();
+ method public final String? getDataPattern();
+ method public final android.content.Intent? getIntent();
+ method public final String? getTargetPackage();
+ method public final androidx.navigation.ActivityNavigator.Destination setAction(String?);
+ method public final androidx.navigation.ActivityNavigator.Destination setComponentName(android.content.ComponentName?);
+ method public final androidx.navigation.ActivityNavigator.Destination setData(android.net.Uri?);
+ method public final androidx.navigation.ActivityNavigator.Destination setDataPattern(String?);
+ method public final androidx.navigation.ActivityNavigator.Destination setIntent(android.content.Intent?);
+ method public final androidx.navigation.ActivityNavigator.Destination setTargetPackage(String?);
+ }
+
+ public static final class ActivityNavigator.Extras implements androidx.navigation.Navigator.Extras {
+ method public androidx.core.app.ActivityOptionsCompat? getActivityOptions();
+ method public int getFlags();
+ }
+
+ public static final class ActivityNavigator.Extras.Builder {
+ ctor public ActivityNavigator.Extras.Builder();
+ method public androidx.navigation.ActivityNavigator.Extras.Builder addFlags(int);
+ method public androidx.navigation.ActivityNavigator.Extras build();
+ method public androidx.navigation.ActivityNavigator.Extras.Builder setActivityOptions(androidx.core.app.ActivityOptionsCompat);
+ }
+
+ public class NavController {
+ ctor public NavController(android.content.Context);
+ method public void addOnDestinationChangedListener(androidx.navigation.NavController.OnDestinationChangedListener);
+ method public androidx.navigation.NavDeepLinkBuilder createDeepLink();
+ method public androidx.navigation.NavDestination? getCurrentDestination();
+ method public androidx.navigation.NavGraph getGraph();
+ method public androidx.navigation.NavInflater getNavInflater();
+ method public androidx.navigation.NavigatorProvider getNavigatorProvider();
+ method public androidx.lifecycle.ViewModelStoreOwner getViewModelStoreOwner(@IdRes int);
+ method public boolean handleDeepLink(android.content.Intent?);
+ method public void navigate(@IdRes int);
+ method public void navigate(@IdRes int, android.os.Bundle?);
+ method public void navigate(@IdRes int, android.os.Bundle?, androidx.navigation.NavOptions?);
+ method public void navigate(@IdRes int, android.os.Bundle?, androidx.navigation.NavOptions?, androidx.navigation.Navigator.Extras?);
+ method public void navigate(android.net.Uri);
+ method public void navigate(android.net.Uri, androidx.navigation.NavOptions?);
+ method public void navigate(android.net.Uri, androidx.navigation.NavOptions?, androidx.navigation.Navigator.Extras?);
+ method public void navigate(androidx.navigation.NavDirections);
+ method public void navigate(androidx.navigation.NavDirections, androidx.navigation.NavOptions?);
+ method public void navigate(androidx.navigation.NavDirections, androidx.navigation.Navigator.Extras);
+ method public boolean navigateUp();
+ method public boolean popBackStack();
+ method public boolean popBackStack(@IdRes int, boolean);
+ method public void removeOnDestinationChangedListener(androidx.navigation.NavController.OnDestinationChangedListener);
+ method @CallSuper public void restoreState(android.os.Bundle?);
+ method @CallSuper public android.os.Bundle? saveState();
+ method @CallSuper public void setGraph(@NavigationRes int);
+ method @CallSuper public void setGraph(@NavigationRes int, android.os.Bundle?);
+ method @CallSuper public void setGraph(androidx.navigation.NavGraph);
+ method @CallSuper public void setGraph(androidx.navigation.NavGraph, android.os.Bundle?);
+ field public static final String KEY_DEEP_LINK_INTENT = "android-support-nav:controller:deepLinkIntent";
+ }
+
+ public static interface NavController.OnDestinationChangedListener {
+ method public void onDestinationChanged(androidx.navigation.NavController, androidx.navigation.NavDestination, android.os.Bundle?);
+ }
+
+ public final class NavDeepLinkBuilder {
+ ctor public NavDeepLinkBuilder(android.content.Context);
+ method public android.app.PendingIntent createPendingIntent();
+ method public androidx.core.app.TaskStackBuilder createTaskStackBuilder();
+ method public androidx.navigation.NavDeepLinkBuilder setArguments(android.os.Bundle?);
+ method public androidx.navigation.NavDeepLinkBuilder setComponentName(Class<? extends android.app.Activity>);
+ method public androidx.navigation.NavDeepLinkBuilder setComponentName(android.content.ComponentName);
+ method public androidx.navigation.NavDeepLinkBuilder setDestination(@IdRes int);
+ method public androidx.navigation.NavDeepLinkBuilder setGraph(@NavigationRes int);
+ method public androidx.navigation.NavDeepLinkBuilder setGraph(androidx.navigation.NavGraph);
+ }
+
+ public interface NavHost {
+ method public androidx.navigation.NavController getNavController();
+ }
+
+ public final class NavHostController extends androidx.navigation.NavController {
+ ctor public NavHostController(android.content.Context);
+ method public void enableOnBackPressed(boolean);
+ method public void setLifecycleOwner(androidx.lifecycle.LifecycleOwner);
+ method public void setOnBackPressedDispatcher(androidx.activity.OnBackPressedDispatcher);
+ method public void setViewModelStore(androidx.lifecycle.ViewModelStore);
+ }
+
+ public final class NavInflater {
+ ctor public NavInflater(android.content.Context, androidx.navigation.NavigatorProvider);
+ method public androidx.navigation.NavGraph inflate(@NavigationRes int);
+ }
+
+ public final class Navigation {
+ method public static android.view.View.OnClickListener createNavigateOnClickListener(@IdRes int);
+ method public static android.view.View.OnClickListener createNavigateOnClickListener(@IdRes int, android.os.Bundle?);
+ method public static android.view.View.OnClickListener createNavigateOnClickListener(androidx.navigation.NavDirections);
+ method public static androidx.navigation.NavController findNavController(android.app.Activity, @IdRes int);
+ method public static androidx.navigation.NavController findNavController(android.view.View);
+ method public static void setViewNavController(android.view.View, androidx.navigation.NavController?);
+ }
+
+}
+
diff --git a/navigation/navigation-runtime/api/restricted_2.2.0-alpha01.txt b/navigation/navigation-runtime/api/restricted_2.2.0-alpha01.txt
index f361023..5db4937 100644
--- a/navigation/navigation-runtime/api/restricted_2.2.0-alpha01.txt
+++ b/navigation/navigation-runtime/api/restricted_2.2.0-alpha01.txt
@@ -46,7 +46,6 @@
method public androidx.navigation.NavGraph getGraph();
method public androidx.navigation.NavInflater getNavInflater();
method public androidx.navigation.NavigatorProvider getNavigatorProvider();
- method @Deprecated public androidx.lifecycle.ViewModelStore getViewModelStore(@IdRes int);
method public androidx.lifecycle.ViewModelStoreOwner getViewModelStoreOwner(@IdRes int);
method public boolean handleDeepLink(android.content.Intent?);
method public void navigate(@IdRes int);
diff --git a/navigation/navigation-runtime/api/restricted_current.txt b/navigation/navigation-runtime/api/restricted_current.txt
index f361023..5db4937 100644
--- a/navigation/navigation-runtime/api/restricted_current.txt
+++ b/navigation/navigation-runtime/api/restricted_current.txt
@@ -46,7 +46,6 @@
method public androidx.navigation.NavGraph getGraph();
method public androidx.navigation.NavInflater getNavInflater();
method public androidx.navigation.NavigatorProvider getNavigatorProvider();
- method @Deprecated public androidx.lifecycle.ViewModelStore getViewModelStore(@IdRes int);
method public androidx.lifecycle.ViewModelStoreOwner getViewModelStoreOwner(@IdRes int);
method public boolean handleDeepLink(android.content.Intent?);
method public void navigate(@IdRes int);
diff --git a/navigation/navigation-runtime/build.gradle b/navigation/navigation-runtime/build.gradle
index 9afb788..2624b7c 100644
--- a/navigation/navigation-runtime/build.gradle
+++ b/navigation/navigation-runtime/build.gradle
@@ -30,6 +30,8 @@
api(project(":navigation:navigation-common"))
api(project(":activity:activity"))
api(project(":lifecycle:lifecycle-viewmodel"))
+ api("androidx.savedstate:savedstate:1.0.0-rc01")
+ api(project(":lifecycle:lifecycle-viewmodel-savedstate"))
androidTestImplementation(project(":navigation:navigation-testing"))
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
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 e20b2c8..297e3d8 100644
--- a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt
+++ b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt
@@ -16,11 +16,16 @@
package androidx.navigation
+import android.app.Application
import android.content.Context
import android.net.Uri
import android.os.Bundle
import android.os.Parcel
import android.os.Parcelable
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.SavedStateHandle
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelStore
import androidx.navigation.test.R
import androidx.navigation.testing.TestNavigator
@@ -890,6 +895,58 @@
}
@Test
+ fun testGetViewModelStoreOwnerAndroidViewModel() {
+ val navController = createNavController()
+ navController.setViewModelStore(ViewModelStore())
+ val navGraph = navController.navigatorProvider.navigation(
+ id = 1,
+ startDestination = R.id.start_test
+ ) {
+ test(R.id.start_test)
+ }
+ navController.setGraph(navGraph, null)
+
+ val owner = navController.getViewModelStoreOwner(navGraph.id)
+ assertThat(owner).isNotNull()
+ val viewModelProvider = ViewModelProvider(owner)
+ val viewModel = viewModelProvider[TestAndroidViewModel::class.java]
+ assertThat(viewModel).isNotNull()
+ }
+
+ @Test
+ fun testGetViewModelStoreOwnerSavedStateViewModel() {
+ val hostStore = ViewModelStore()
+ val navController = createNavController()
+ navController.setViewModelStore(hostStore)
+ val navGraph = navController.navigatorProvider.navigation(
+ id = 1,
+ startDestination = R.id.start_test
+ ) {
+ test(R.id.start_test)
+ }
+ navController.setGraph(navGraph, null)
+
+ val owner = navController.getViewModelStoreOwner(navGraph.id)
+ assertThat(owner).isNotNull()
+ val viewModelProvider = ViewModelProvider(owner)
+ val viewModel = viewModelProvider[TestSavedStateViewModel::class.java]
+ assertThat(viewModel).isNotNull()
+ viewModel.savedStateHandle.set("test", "test")
+
+ val savedState = navController.saveState()
+ val restoredNavController = createNavController()
+ restoredNavController.setViewModelStore(hostStore)
+ restoredNavController.restoreState(savedState)
+ restoredNavController.graph = navGraph
+
+ val restoredOwner = navController.getViewModelStoreOwner(navGraph.id)
+ val restoredViewModel = ViewModelProvider(
+ restoredOwner)[TestSavedStateViewModel::class.java]
+ val restoredState: String? = restoredViewModel.savedStateHandle.get("test")
+ assertThat(restoredState).isEqualTo("test")
+ }
+
+ @Test
fun testSaveRestoreGetViewModelStoreOwner() {
val hostStore = ViewModelStore()
val navController = createNavController()
@@ -966,6 +1023,10 @@
}
}
+class TestAndroidViewModel(application: Application) : AndroidViewModel(application)
+
+class TestSavedStateViewModel(val savedStateHandle: SavedStateHandle) : ViewModel()
+
/**
* [TestNavigator] that helps with testing saving and restoring state.
*/
diff --git a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavBackStackEntry.java b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavBackStackEntry.java
index 8aaa08c..f226265 100644
--- a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavBackStackEntry.java
+++ b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavBackStackEntry.java
@@ -16,38 +16,72 @@
package androidx.navigation;
+import android.app.Application;
+import android.content.Context;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.lifecycle.HasDefaultViewModelProviderFactory;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+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;
import java.util.UUID;
/**
* Representation of an entry in the back stack of a {@link NavController}.
*/
-final class NavBackStackEntry implements ViewModelStoreOwner {
+final class NavBackStackEntry implements
+ LifecycleOwner,
+ ViewModelStoreOwner, HasDefaultViewModelProviderFactory,
+ SavedStateRegistryOwner {
+ private final Context mContext;
private final NavDestination mDestination;
private final Bundle mArgs;
+ private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this);
+ private final SavedStateRegistryController mSavedStateRegistryController =
+ SavedStateRegistryController.create(this);
// Internal unique name for this navBackStackEntry;
@NonNull
final UUID mId;
private NavControllerViewModel mNavControllerViewModel;
+ private ViewModelProvider.Factory mDefaultFactory;
- NavBackStackEntry(@NonNull NavDestination destination, @Nullable Bundle args,
+ NavBackStackEntry(@NonNull Context context,
+ @NonNull NavDestination destination, @Nullable Bundle args,
+ @Nullable LifecycleOwner navControllerLifecyleOwner,
@Nullable NavControllerViewModel navControllerViewModel) {
- this(UUID.randomUUID(), destination, args, navControllerViewModel);
+ this(context, destination, args,
+ navControllerLifecyleOwner, navControllerViewModel,
+ UUID.randomUUID(), null);
}
- NavBackStackEntry(@NonNull UUID uuid, @NonNull NavDestination destination,
- @Nullable Bundle args, @Nullable NavControllerViewModel navControllerViewModel) {
+ NavBackStackEntry(@NonNull Context context,
+ @NonNull NavDestination destination, @Nullable Bundle args,
+ @Nullable LifecycleOwner navControllerLifecyleOwner,
+ @Nullable NavControllerViewModel navControllerViewModel,
+ @NonNull UUID uuid, @Nullable Bundle savedState) {
+ mContext = context;
mId = uuid;
mDestination = destination;
mArgs = args;
mNavControllerViewModel = navControllerViewModel;
+ mSavedStateRegistryController.performRestore(savedState);
+ if (navControllerLifecyleOwner != null) {
+ mLifecycle.setCurrentState(navControllerLifecyleOwner.getLifecycle()
+ .getCurrentState());
+ } else {
+ mLifecycle.setCurrentState(Lifecycle.State.CREATED);
+ }
}
/**
@@ -74,7 +108,39 @@
@NonNull
@Override
+ public Lifecycle getLifecycle() {
+ return mLifecycle;
+ }
+
+ void handleLifecycleEvent(Lifecycle.Event event) {
+ mLifecycle.handleLifecycleEvent(event);
+ }
+
+ @NonNull
+ @Override
public ViewModelStore getViewModelStore() {
return mNavControllerViewModel.getViewModelStore(mId);
}
+
+ @NonNull
+ @Override
+ public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
+ if (mDefaultFactory == null) {
+ mDefaultFactory = new SavedStateViewModelFactory(
+ (Application) mContext.getApplicationContext(),
+ this,
+ mArgs);
+ }
+ return mDefaultFactory;
+ }
+
+ @NonNull
+ @Override
+ public SavedStateRegistry getSavedStateRegistry() {
+ return mSavedStateRegistryController.getSavedStateRegistry();
+ }
+
+ void saveState(@NonNull Bundle outBundle) {
+ mSavedStateRegistryController.performSave(outBundle);
+ }
}
diff --git a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavBackStackEntryState.java b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavBackStackEntryState.java
new file mode 100644
index 0000000..5dd3b7e
--- /dev/null
+++ b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavBackStackEntryState.java
@@ -0,0 +1,97 @@
+/*
+ * 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.navigation;
+
+import android.annotation.SuppressLint;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.UUID;
+
+@SuppressLint("BanParcelableUsage")
+final class NavBackStackEntryState implements Parcelable {
+
+ private final UUID mUUID;
+ private final int mDestinationId;
+ private final Bundle mArgs;
+ private final Bundle mSavedState;
+
+ NavBackStackEntryState(NavBackStackEntry entry) {
+ mUUID = entry.mId;
+ mDestinationId = entry.getDestination().getId();
+ mArgs = entry.getArguments();
+ mSavedState = new Bundle();
+ entry.saveState(mSavedState);
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ NavBackStackEntryState(Parcel in) {
+ mUUID = UUID.fromString(in.readString());
+ mDestinationId = in.readInt();
+ mArgs = in.readBundle(getClass().getClassLoader());
+ mSavedState = in.readBundle(getClass().getClassLoader());
+ }
+
+ @NonNull
+ UUID getUUID() {
+ return mUUID;
+ }
+
+ int getDestinationId() {
+ return mDestinationId;
+ }
+
+ @Nullable
+ Bundle getArgs() {
+ return mArgs;
+ }
+
+ @NonNull
+ Bundle getSavedState() {
+ return mSavedState;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel parcel, int i) {
+ parcel.writeString(mUUID.toString());
+ parcel.writeInt(mDestinationId);
+ parcel.writeBundle(mArgs);
+ parcel.writeBundle(mSavedState);
+ }
+
+ public static final Parcelable.Creator<NavBackStackEntryState> CREATOR =
+ new Parcelable.Creator<NavBackStackEntryState>() {
+ @Override
+ public NavBackStackEntryState createFromParcel(Parcel in) {
+ return new NavBackStackEntryState(in);
+ }
+
+ @Override
+ public NavBackStackEntryState[] newArray(int size) {
+ return new NavBackStackEntryState[size];
+ }
+ };
+}
diff --git a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.java b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.java
index f06382e..9686153 100644
--- a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.java
+++ b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.java
@@ -33,6 +33,9 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.TaskStackBuilder;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleEventObserver;
+import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ViewModelStore;
import androidx.lifecycle.ViewModelStoreOwner;
@@ -42,7 +45,6 @@
import java.util.Deque;
import java.util.Iterator;
import java.util.Map;
-import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
/**
@@ -64,11 +66,8 @@
"android-support-nav:controller:navigatorState";
private static final String KEY_NAVIGATOR_STATE_NAMES =
"android-support-nav:controller:navigatorState:names";
- private static final String KEY_BACK_STACK_UUIDS =
- "android-support-nav:controller:backStackUUIDs";
- private static final String KEY_BACK_STACK_IDS = "android-support-nav:controller:backStackIds";
- private static final String KEY_BACK_STACK_ARGS =
- "android-support-nav:controller:backStackArgs";
+ private static final String KEY_BACK_STACK =
+ "android-support-nav:controller:backStack";
static final String KEY_DEEP_LINK_IDS = "android-support-nav:controller:deepLinkIds";
static final String KEY_DEEP_LINK_EXTRAS =
"android-support-nav:controller:deepLinkExtras";
@@ -83,14 +82,14 @@
private final Context mContext;
private Activity mActivity;
private NavInflater mInflater;
- private NavGraph mGraph;
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ NavGraph mGraph;
private Bundle mNavigatorStateToRestore;
- private String[] mBackStackUUIDsToRestore;
- private int[] mBackStackIdsToRestore;
- private Parcelable[] mBackStackArgsToRestore;
+ private Parcelable[] mBackStackToRestore;
private boolean mDeepLinkHandled;
- private final Deque<NavBackStackEntry> mBackStack = new ArrayDeque<>();
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ final Deque<NavBackStackEntry> mBackStack = new ArrayDeque<>();
private LifecycleOwner mLifecycleOwner;
private NavControllerViewModel mViewModel;
@@ -100,6 +99,17 @@
private final CopyOnWriteArrayList<OnDestinationChangedListener>
mOnDestinationChangedListeners = new CopyOnWriteArrayList<>();
+ private final LifecycleObserver mLifecycleObserver = new LifecycleEventObserver() {
+ @Override
+ public void onStateChanged(@NonNull LifecycleOwner source,
+ @NonNull Lifecycle.Event event) {
+ if (mGraph != null) {
+ for (NavBackStackEntry entry : mBackStack) {
+ entry.handleLifecycleEvent(event);
+ }
+ }
+ }
+ };
private final OnBackPressedCallback mOnBackPressedCallback =
new OnBackPressedCallback(false) {
@Override
@@ -282,6 +292,7 @@
for (Navigator<?> navigator : popOperations) {
if (navigator.popBackStack()) {
NavBackStackEntry entry = mBackStack.removeLast();
+ entry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);
if (mViewModel != null) {
mViewModel.clear(entry.mId);
}
@@ -474,25 +485,25 @@
}
}
}
- if (mBackStackUUIDsToRestore != null) {
- for (int index = 0; index < mBackStackUUIDsToRestore.length; index++) {
- UUID uuid = UUID.fromString(mBackStackUUIDsToRestore[index]);
- int destinationId = mBackStackIdsToRestore[index];
- Bundle args = (Bundle) mBackStackArgsToRestore[index];
- NavDestination node = findDestination(destinationId);
+ if (mBackStackToRestore != null) {
+ for (Parcelable parcelable : mBackStackToRestore) {
+ NavBackStackEntryState state = (NavBackStackEntryState) parcelable;
+ NavDestination node = findDestination(state.getDestinationId());
if (node == null) {
throw new IllegalStateException("unknown destination during restore: "
- + mContext.getResources().getResourceName(destinationId));
+ + mContext.getResources().getResourceName(state.getDestinationId()));
}
+ Bundle args = state.getArgs();
if (args != null) {
args.setClassLoader(mContext.getClassLoader());
}
- mBackStack.add(new NavBackStackEntry(uuid, node, args, mViewModel));
+ NavBackStackEntry entry = new NavBackStackEntry(mContext, node, args,
+ mLifecycleOwner, mViewModel,
+ state.getUUID(), state.getSavedState());
+ mBackStack.add(entry);
}
updateOnBackPressedCallbackEnabled();
- mBackStackUUIDsToRestore = null;
- mBackStackIdsToRestore = null;
- mBackStackArgsToRestore = null;
+ mBackStackToRestore = null;
}
if (mGraph != null && mBackStack.isEmpty()) {
boolean deepLinked = !mDeepLinkHandled && mActivity != null
@@ -873,7 +884,9 @@
}
// The mGraph should always be on the back stack after you navigate()
if (mBackStack.isEmpty()) {
- mBackStack.add(new NavBackStackEntry(mGraph, finalArgs, mViewModel));
+ NavBackStackEntry entry = new NavBackStackEntry(mContext, mGraph, finalArgs,
+ mLifecycleOwner, mViewModel);
+ mBackStack.add(entry);
}
// Now ensure all intermediate NavGraphs are put on the back stack
// to ensure that global actions work.
@@ -882,14 +895,16 @@
while (destination != null && findDestination(destination.getId()) == null) {
NavGraph parent = destination.getParent();
if (parent != null) {
- hierarchy.addFirst(new NavBackStackEntry(parent, finalArgs, mViewModel));
+ NavBackStackEntry entry = new NavBackStackEntry(mContext, parent, finalArgs,
+ mLifecycleOwner, mViewModel);
+ hierarchy.addFirst(entry);
}
destination = parent;
}
mBackStack.addAll(hierarchy);
// And finally, add the new destination with its default args
- NavBackStackEntry newBackStackEntry = new NavBackStackEntry(newDest,
- newDest.addInDefaultArgs(finalArgs), mViewModel);
+ NavBackStackEntry newBackStackEntry = new NavBackStackEntry(mContext, newDest,
+ newDest.addInDefaultArgs(finalArgs), mLifecycleOwner, mViewModel);
mBackStack.add(newBackStackEntry);
}
updateOnBackPressedCallbackEnabled();
@@ -971,18 +986,12 @@
if (b == null) {
b = new Bundle();
}
- String[] backStackUUIDs = new String[mBackStack.size()];
- int[] backStackIds = new int[mBackStack.size()];
- Parcelable[] backStackArgs = new Parcelable[mBackStack.size()];
+ Parcelable[] backStack = new Parcelable[mBackStack.size()];
int index = 0;
for (NavBackStackEntry backStackEntry : mBackStack) {
- backStackUUIDs[index] = backStackEntry.mId.toString();
- backStackIds[index] = backStackEntry.getDestination().getId();
- backStackArgs[index++] = backStackEntry.getArguments();
+ backStack[index++] = new NavBackStackEntryState(backStackEntry);
}
- b.putStringArray(KEY_BACK_STACK_UUIDS, backStackUUIDs);
- b.putIntArray(KEY_BACK_STACK_IDS, backStackIds);
- b.putParcelableArray(KEY_BACK_STACK_ARGS, backStackArgs);
+ b.putParcelableArray(KEY_BACK_STACK, backStack);
}
if (mDeepLinkHandled) {
if (b == null) {
@@ -1011,14 +1020,13 @@
navState.setClassLoader(mContext.getClassLoader());
mNavigatorStateToRestore = navState.getBundle(KEY_NAVIGATOR_STATE);
- mBackStackUUIDsToRestore = navState.getStringArray(KEY_BACK_STACK_UUIDS);
- mBackStackIdsToRestore = navState.getIntArray(KEY_BACK_STACK_IDS);
- mBackStackArgsToRestore = navState.getParcelableArray(KEY_BACK_STACK_ARGS);
+ mBackStackToRestore = navState.getParcelableArray(KEY_BACK_STACK);
mDeepLinkHandled = navState.getBoolean(KEY_DEEP_LINK_HANDLED);
}
void setLifecycleOwner(@NonNull LifecycleOwner owner) {
mLifecycleOwner = owner;
+ mLifecycleOwner.getLifecycle().addObserver(mLifecycleObserver);
}
void setOnBackPressedDispatcher(@NonNull OnBackPressedDispatcher dispatcher) {
@@ -1052,26 +1060,6 @@
}
/**
- * Gets the {@link ViewModelStore} for a NavGraph.This can be passed to
- * {@link androidx.lifecycle.ViewModelProvider} to retrieve a ViewModel that is scoped
- * to the navigation graph - it will be cleared when the navigation graph is popped off
- * the back stack.
- *
- * @param navGraphId ID of a NavGraph that exists on the back stack
- * @throws IllegalStateException if called before the {@link NavHost} has called
- * {@link NavHostController#setViewModelStore}.
- * @throws IllegalArgumentException if the NavGraph is not on the back stack
- * @deprecated Use {@link #getViewModelStoreOwner(int)}, calling
- * {@link ViewModelStoreOwner#getViewModelStore()} on the returned ViewModelStoreOwner
- * if you need specifically a ViewModelStore.
- */
- @Deprecated
- @NonNull
- public ViewModelStore getViewModelStore(@IdRes int navGraphId) {
- return getViewModelStoreOwner(navGraphId).getViewModelStore();
- }
-
- /**
* Gets the {@link ViewModelStoreOwner} for a NavGraph.This can be passed to
* {@link androidx.lifecycle.ViewModelProvider} to retrieve a ViewModel that is scoped
* to the navigation graph - it will be cleared when the navigation graph is popped off
diff --git a/navigation/navigation-safe-args-gradle-plugin/build.gradle b/navigation/navigation-safe-args-gradle-plugin/build.gradle
index f492bdf..d308d30 100644
--- a/navigation/navigation-safe-args-gradle-plugin/build.gradle
+++ b/navigation/navigation-safe-args-gradle-plugin/build.gradle
@@ -38,8 +38,8 @@
}
dependencies {
- compile(SAFE_ARGS_ANDROID_GRADLE_PLUGIN)
- compile(SAFE_ARGS_KOTLIN_GRADLE_PLUGIN)
+ compile(ANDROID_GRADLE_PLUGIN)
+ compile(KOTLIN_GRADLE_PLUGIN)
compile project(":navigation:navigation-safe-args-generator")
compile gradleApi()
compile(GSON)
@@ -53,7 +53,7 @@
inputs.property("buildToolsVersion", SupportConfig.BUILD_TOOLS_VERSION)
inputs.property("minSdkVersion", SupportConfig.DEFAULT_MIN_SDK_VERSION)
inputs.property("debugKeystore", debugKeystore)
- inputs.property("navigationCommon", SAFE_ARGS_NAVIGATION_COMMON)
+ inputs.property("navigationCommon", NAVIGATION_COMMON)
inputs.property("kotlinStdlib", KOTLIN_STDLIB)
outputs.dir(generatedResources)
doLast {
@@ -65,7 +65,7 @@
writer.write("buildToolsVersion=$SupportConfig.BUILD_TOOLS_VERSION\n")
writer.write("minSdkVersion=$SupportConfig.DEFAULT_MIN_SDK_VERSION\n")
writer.write("debugKeystore=$debugKeystore\n")
- writer.write("navigationCommon=$SAFE_ARGS_NAVIGATION_COMMON\n")
+ writer.write("navigationCommon=$NAVIGATION_COMMON\n")
writer.write("kotlinStdlib=$KOTLIN_STDLIB\n")
}
}
diff --git a/paging/common/api/3.0.0-alpha01.txt b/paging/common/api/3.0.0-alpha01.txt
index 50d6f73..0d44da6 100644
--- a/paging/common/api/3.0.0-alpha01.txt
+++ b/paging/common/api/3.0.0-alpha01.txt
@@ -71,7 +71,6 @@
public abstract static class ItemKeyedDataSource.LoadCallback<Value> {
ctor public ItemKeyedDataSource.LoadCallback();
- method public void onError(Throwable error);
method public abstract void onResult(java.util.List<? extends Value> data);
}
@@ -111,13 +110,11 @@
public abstract static class PageKeyedDataSource.LoadCallback<Key, Value> {
ctor public PageKeyedDataSource.LoadCallback();
- method public void onError(Throwable error);
method public abstract void onResult(java.util.List<? extends Value> data, Key? adjacentPageKey);
}
public abstract static class PageKeyedDataSource.LoadInitialCallback<Key, Value> {
ctor public PageKeyedDataSource.LoadInitialCallback();
- method public void onError(Throwable error);
method public abstract void onResult(java.util.List<? extends Value> data, int position, int totalCount, Key? previousPageKey, Key? nextPageKey);
method public abstract void onResult(java.util.List<? extends Value> data, Key? previousPageKey, Key? nextPageKey);
}
@@ -150,7 +147,6 @@
method public final androidx.paging.PagedSource<?,T> getPagedSource();
method public int getPositionOffset();
method public int getSize();
- method public abstract boolean isContiguous();
method public abstract boolean isDetached();
method public boolean isImmutable();
method public void loadAround(int index);
@@ -160,7 +156,6 @@
method public java.util.List<T> snapshot();
property public androidx.paging.PagedList.Config config;
property @Deprecated public final androidx.paging.DataSource<?,T> dataSource;
- property public abstract boolean isContiguous;
property public abstract boolean isDetached;
property public boolean isImmutable;
property public abstract Object? lastKey;
@@ -248,13 +243,10 @@
method public void invalidate();
method public abstract boolean isRetryableError(Throwable error);
method public abstract suspend Object load(androidx.paging.PagedSource.LoadParams<Key> params, kotlin.coroutines.Continuation<? super androidx.paging.PagedSource.LoadResult<Key,Value>> p);
+ method public final void registerInvalidatedCallback(kotlin.jvm.functions.Function0<kotlin.Unit> onInvalidatedCallback);
+ method public final void unregisterInvalidatedCallback(kotlin.jvm.functions.Function0<kotlin.Unit> onInvalidatedCallback);
property public final boolean invalid;
property public abstract androidx.paging.PagedSource.KeyProvider<Key,Value> keyProvider;
- field public static final int COUNT_UNDEFINED = -1; // 0xffffffff
- field public static final androidx.paging.PagedSource.Companion! Companion;
- }
-
- public static final class PagedSource.Companion {
}
public abstract static sealed class PagedSource.KeyProvider<Key, Value> {
@@ -289,22 +281,25 @@
}
public static final class PagedSource.LoadResult<Key, Value> {
- ctor public PagedSource.LoadResult(int itemsBefore, int itemsAfter, Key? nextKey, Key? prevKey, java.util.List<? extends Value> data, int offset, boolean counted);
+ ctor public PagedSource.LoadResult(@IntRange(from=null) int itemsBefore, @IntRange(from=null) int itemsAfter, Key? nextKey, Key? prevKey, java.util.List<? extends Value> data, int offset);
method public int component1();
method public int component2();
method public Key? component3();
method public Key? component4();
method public java.util.List<Value> component5();
method public int component6();
- method public boolean component7();
- method public androidx.paging.PagedSource.LoadResult<Key,Value> copy(int itemsBefore, int itemsAfter, Key? nextKey, Key? prevKey, java.util.List<? extends Value> data, int offset, boolean counted);
- method public boolean getCounted();
+ method public androidx.paging.PagedSource.LoadResult<Key,Value> copy(int itemsBefore, int itemsAfter, Key? nextKey, Key? prevKey, java.util.List<? extends Value> data, int offset);
method public java.util.List<Value> getData();
method public int getItemsAfter();
method public int getItemsBefore();
method public Key? getNextKey();
method public int getOffset();
method public Key? getPrevKey();
+ field public static final int COUNT_UNDEFINED = -1; // 0xffffffff
+ field public static final androidx.paging.PagedSource.LoadResult.Companion! Companion;
+ }
+
+ public static final class PagedSource.LoadResult.Companion {
}
public enum PagedSource.LoadType {
@@ -313,6 +308,10 @@
enum_constant public static final androidx.paging.PagedSource.LoadType START;
}
+ public final class PagedSourceKt {
+ ctor public PagedSourceKt();
+ }
+
public abstract class PositionalDataSource<T> extends androidx.paging.DataSource<java.lang.Integer,T> {
ctor public PositionalDataSource();
method public static final int computeInitialLoadPosition(androidx.paging.PositionalDataSource.LoadInitialParams params, int totalCount);
@@ -329,7 +328,6 @@
public abstract static class PositionalDataSource.LoadInitialCallback<T> {
ctor public PositionalDataSource.LoadInitialCallback();
- method public void onError(Throwable error);
method public abstract void onResult(java.util.List<? extends T> data, int position, int totalCount);
method public abstract void onResult(java.util.List<? extends T> data, int position);
}
@@ -344,7 +342,6 @@
public abstract static class PositionalDataSource.LoadRangeCallback<T> {
ctor public PositionalDataSource.LoadRangeCallback();
- method public void onError(Throwable error);
method public abstract void onResult(java.util.List<? extends T> data);
}
diff --git a/paging/common/api/api_lint.ignore b/paging/common/api/api_lint.ignore
index a443331..678d58d 100644
--- a/paging/common/api/api_lint.ignore
+++ b/paging/common/api/api_lint.ignore
@@ -1,22 +1,12 @@
// Baseline format: 1.0
DocumentExceptions: androidx.paging.DataSource#getExecutor():
Method DataSource.getExecutor appears to be throwing java.lang.IllegalStateException; this should be listed in the documentation; see https://android.github.io/kotlin-guides/interop.html#document-exceptions
-DocumentExceptions: androidx.paging.ItemKeyedDataSource.LoadCallback#onError(Throwable):
- Method LoadCallback.onError appears to be throwing java.lang.IllegalStateException; this should be listed in the documentation; see https://android.github.io/kotlin-guides/interop.html#document-exceptions
-DocumentExceptions: androidx.paging.PageKeyedDataSource.LoadCallback#onError(Throwable):
- Method LoadCallback.onError appears to be throwing java.lang.IllegalStateException; this should be listed in the documentation; see https://android.github.io/kotlin-guides/interop.html#document-exceptions
-DocumentExceptions: androidx.paging.PageKeyedDataSource.LoadInitialCallback#onError(Throwable):
- Method LoadInitialCallback.onError appears to be throwing java.lang.IllegalStateException; this should be listed in the documentation; see https://android.github.io/kotlin-guides/interop.html#document-exceptions
DocumentExceptions: androidx.paging.PagedList#loadAround(int):
Method PagedList.loadAround appears to be throwing java.lang.IndexOutOfBoundsException; this should be listed in the documentation; see https://android.github.io/kotlin-guides/interop.html#document-exceptions
DocumentExceptions: androidx.paging.PagedList.Config.Builder#build():
Method Builder.build appears to be throwing java.lang.IllegalArgumentException; this should be listed in the documentation; see https://android.github.io/kotlin-guides/interop.html#document-exceptions
DocumentExceptions: androidx.paging.PagedList.Config.Builder#setPageSize(int):
Method Builder.setPageSize appears to be throwing java.lang.IllegalArgumentException; this should be listed in the documentation; see https://android.github.io/kotlin-guides/interop.html#document-exceptions
-DocumentExceptions: androidx.paging.PositionalDataSource.LoadInitialCallback#onError(Throwable):
- Method LoadInitialCallback.onError appears to be throwing java.lang.IllegalStateException; this should be listed in the documentation; see https://android.github.io/kotlin-guides/interop.html#document-exceptions
-DocumentExceptions: androidx.paging.PositionalDataSource.LoadRangeCallback#onError(Throwable):
- Method LoadRangeCallback.onError appears to be throwing java.lang.IllegalStateException; this should be listed in the documentation; see https://android.github.io/kotlin-guides/interop.html#document-exceptions
EqualsAndHashCode: androidx.paging.DataSource.BaseResult#equals(Object):
@@ -33,10 +23,6 @@
Parameter p references hidden type class androidx.paging.PositionalDataSource.InitialResult.
-KotlinOperator: androidx.paging.PagedList#get(int):
- Method can be invoked with an indexing operator from Kotlin: `get` (this is usually desirable; just make sure it makes sense for this type of object)
-
-
RegistrationName: androidx.paging.DataSource#addInvalidatedCallback(androidx.paging.DataSource.InvalidatedCallback):
Callback methods should be named register/unregister; was addInvalidatedCallback
RegistrationName: androidx.paging.DataSource#addInvalidatedCallback(kotlin.jvm.functions.Function0<kotlin.Unit>):
diff --git a/paging/common/api/current.txt b/paging/common/api/current.txt
index 50d6f73..0d44da6 100644
--- a/paging/common/api/current.txt
+++ b/paging/common/api/current.txt
@@ -71,7 +71,6 @@
public abstract static class ItemKeyedDataSource.LoadCallback<Value> {
ctor public ItemKeyedDataSource.LoadCallback();
- method public void onError(Throwable error);
method public abstract void onResult(java.util.List<? extends Value> data);
}
@@ -111,13 +110,11 @@
public abstract static class PageKeyedDataSource.LoadCallback<Key, Value> {
ctor public PageKeyedDataSource.LoadCallback();
- method public void onError(Throwable error);
method public abstract void onResult(java.util.List<? extends Value> data, Key? adjacentPageKey);
}
public abstract static class PageKeyedDataSource.LoadInitialCallback<Key, Value> {
ctor public PageKeyedDataSource.LoadInitialCallback();
- method public void onError(Throwable error);
method public abstract void onResult(java.util.List<? extends Value> data, int position, int totalCount, Key? previousPageKey, Key? nextPageKey);
method public abstract void onResult(java.util.List<? extends Value> data, Key? previousPageKey, Key? nextPageKey);
}
@@ -150,7 +147,6 @@
method public final androidx.paging.PagedSource<?,T> getPagedSource();
method public int getPositionOffset();
method public int getSize();
- method public abstract boolean isContiguous();
method public abstract boolean isDetached();
method public boolean isImmutable();
method public void loadAround(int index);
@@ -160,7 +156,6 @@
method public java.util.List<T> snapshot();
property public androidx.paging.PagedList.Config config;
property @Deprecated public final androidx.paging.DataSource<?,T> dataSource;
- property public abstract boolean isContiguous;
property public abstract boolean isDetached;
property public boolean isImmutable;
property public abstract Object? lastKey;
@@ -248,13 +243,10 @@
method public void invalidate();
method public abstract boolean isRetryableError(Throwable error);
method public abstract suspend Object load(androidx.paging.PagedSource.LoadParams<Key> params, kotlin.coroutines.Continuation<? super androidx.paging.PagedSource.LoadResult<Key,Value>> p);
+ method public final void registerInvalidatedCallback(kotlin.jvm.functions.Function0<kotlin.Unit> onInvalidatedCallback);
+ method public final void unregisterInvalidatedCallback(kotlin.jvm.functions.Function0<kotlin.Unit> onInvalidatedCallback);
property public final boolean invalid;
property public abstract androidx.paging.PagedSource.KeyProvider<Key,Value> keyProvider;
- field public static final int COUNT_UNDEFINED = -1; // 0xffffffff
- field public static final androidx.paging.PagedSource.Companion! Companion;
- }
-
- public static final class PagedSource.Companion {
}
public abstract static sealed class PagedSource.KeyProvider<Key, Value> {
@@ -289,22 +281,25 @@
}
public static final class PagedSource.LoadResult<Key, Value> {
- ctor public PagedSource.LoadResult(int itemsBefore, int itemsAfter, Key? nextKey, Key? prevKey, java.util.List<? extends Value> data, int offset, boolean counted);
+ ctor public PagedSource.LoadResult(@IntRange(from=null) int itemsBefore, @IntRange(from=null) int itemsAfter, Key? nextKey, Key? prevKey, java.util.List<? extends Value> data, int offset);
method public int component1();
method public int component2();
method public Key? component3();
method public Key? component4();
method public java.util.List<Value> component5();
method public int component6();
- method public boolean component7();
- method public androidx.paging.PagedSource.LoadResult<Key,Value> copy(int itemsBefore, int itemsAfter, Key? nextKey, Key? prevKey, java.util.List<? extends Value> data, int offset, boolean counted);
- method public boolean getCounted();
+ method public androidx.paging.PagedSource.LoadResult<Key,Value> copy(int itemsBefore, int itemsAfter, Key? nextKey, Key? prevKey, java.util.List<? extends Value> data, int offset);
method public java.util.List<Value> getData();
method public int getItemsAfter();
method public int getItemsBefore();
method public Key? getNextKey();
method public int getOffset();
method public Key? getPrevKey();
+ field public static final int COUNT_UNDEFINED = -1; // 0xffffffff
+ field public static final androidx.paging.PagedSource.LoadResult.Companion! Companion;
+ }
+
+ public static final class PagedSource.LoadResult.Companion {
}
public enum PagedSource.LoadType {
@@ -313,6 +308,10 @@
enum_constant public static final androidx.paging.PagedSource.LoadType START;
}
+ public final class PagedSourceKt {
+ ctor public PagedSourceKt();
+ }
+
public abstract class PositionalDataSource<T> extends androidx.paging.DataSource<java.lang.Integer,T> {
ctor public PositionalDataSource();
method public static final int computeInitialLoadPosition(androidx.paging.PositionalDataSource.LoadInitialParams params, int totalCount);
@@ -329,7 +328,6 @@
public abstract static class PositionalDataSource.LoadInitialCallback<T> {
ctor public PositionalDataSource.LoadInitialCallback();
- method public void onError(Throwable error);
method public abstract void onResult(java.util.List<? extends T> data, int position, int totalCount);
method public abstract void onResult(java.util.List<? extends T> data, int position);
}
@@ -344,7 +342,6 @@
public abstract static class PositionalDataSource.LoadRangeCallback<T> {
ctor public PositionalDataSource.LoadRangeCallback();
- method public void onError(Throwable error);
method public abstract void onResult(java.util.List<? extends T> data);
}
diff --git a/paging/common/api/restricted_3.0.0-alpha01.txt b/paging/common/api/restricted_3.0.0-alpha01.txt
index 5e1eb16..73c6e1a 100644
--- a/paging/common/api/restricted_3.0.0-alpha01.txt
+++ b/paging/common/api/restricted_3.0.0-alpha01.txt
@@ -75,7 +75,6 @@
public abstract static class ItemKeyedDataSource.LoadCallback<Value> {
ctor public ItemKeyedDataSource.LoadCallback();
- method public void onError(Throwable error);
method public abstract void onResult(java.util.List<? extends Value> data);
}
@@ -115,13 +114,11 @@
public abstract static class PageKeyedDataSource.LoadCallback<Key, Value> {
ctor public PageKeyedDataSource.LoadCallback();
- method public void onError(Throwable error);
method public abstract void onResult(java.util.List<? extends Value> data, Key? adjacentPageKey);
}
public abstract static class PageKeyedDataSource.LoadInitialCallback<Key, Value> {
ctor public PageKeyedDataSource.LoadInitialCallback();
- method public void onError(Throwable error);
method public abstract void onResult(java.util.List<? extends Value> data, int position, int totalCount, Key? previousPageKey, Key? nextPageKey);
method public abstract void onResult(java.util.List<? extends Value> data, Key? previousPageKey, Key? nextPageKey);
}
@@ -156,7 +153,6 @@
method public int getPositionOffset();
method public int getSize();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final androidx.paging.PagedStorage<T> getStorage();
- method public abstract boolean isContiguous();
method public abstract boolean isDetached();
method public boolean isImmutable();
method public void loadAround(int index);
@@ -167,7 +163,6 @@
method public java.util.List<T> snapshot();
property public androidx.paging.PagedList.Config config;
property @Deprecated public final androidx.paging.DataSource<?,T> dataSource;
- property public abstract boolean isContiguous;
property public abstract boolean isDetached;
property public boolean isImmutable;
property public abstract Object? lastKey;
@@ -271,13 +266,10 @@
method public void invalidate();
method public abstract boolean isRetryableError(Throwable error);
method public abstract suspend Object load(androidx.paging.PagedSource.LoadParams<Key> params, kotlin.coroutines.Continuation<? super androidx.paging.PagedSource.LoadResult<Key,Value>> p);
+ method public final void registerInvalidatedCallback(kotlin.jvm.functions.Function0<kotlin.Unit> onInvalidatedCallback);
+ method public final void unregisterInvalidatedCallback(kotlin.jvm.functions.Function0<kotlin.Unit> onInvalidatedCallback);
property public final boolean invalid;
property public abstract androidx.paging.PagedSource.KeyProvider<Key,Value> keyProvider;
- field public static final int COUNT_UNDEFINED = -1; // 0xffffffff
- field public static final androidx.paging.PagedSource.Companion! Companion;
- }
-
- public static final class PagedSource.Companion {
}
public abstract static sealed class PagedSource.KeyProvider<Key, Value> {
@@ -312,22 +304,25 @@
}
public static final class PagedSource.LoadResult<Key, Value> {
- ctor public PagedSource.LoadResult(int itemsBefore, int itemsAfter, Key? nextKey, Key? prevKey, java.util.List<? extends Value> data, int offset, boolean counted);
+ ctor public PagedSource.LoadResult(@IntRange(from=null) int itemsBefore, @IntRange(from=null) int itemsAfter, Key? nextKey, Key? prevKey, java.util.List<? extends Value> data, int offset);
method public int component1();
method public int component2();
method public Key? component3();
method public Key? component4();
method public java.util.List<Value> component5();
method public int component6();
- method public boolean component7();
- method public androidx.paging.PagedSource.LoadResult<Key,Value> copy(int itemsBefore, int itemsAfter, Key? nextKey, Key? prevKey, java.util.List<? extends Value> data, int offset, boolean counted);
- method public boolean getCounted();
+ method public androidx.paging.PagedSource.LoadResult<Key,Value> copy(int itemsBefore, int itemsAfter, Key? nextKey, Key? prevKey, java.util.List<? extends Value> data, int offset);
method public java.util.List<Value> getData();
method public int getItemsAfter();
method public int getItemsBefore();
method public Key? getNextKey();
method public int getOffset();
method public Key? getPrevKey();
+ field public static final int COUNT_UNDEFINED = -1; // 0xffffffff
+ field public static final androidx.paging.PagedSource.LoadResult.Companion! Companion;
+ }
+
+ public static final class PagedSource.LoadResult.Companion {
}
public enum PagedSource.LoadType {
@@ -336,6 +331,10 @@
enum_constant public static final androidx.paging.PagedSource.LoadType START;
}
+ public final class PagedSourceKt {
+ ctor public PagedSourceKt();
+ }
+
@RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final class PagedSourceWrapper<Key, Value> extends androidx.paging.PagedSource<Key,Value> {
ctor public PagedSourceWrapper(internal androidx.paging.DataSource<Key,Value> dataSource);
method public androidx.paging.PagedSource.KeyProvider<Key,Value> getKeyProvider();
@@ -363,7 +362,6 @@
public abstract static class PositionalDataSource.LoadInitialCallback<T> {
ctor public PositionalDataSource.LoadInitialCallback();
- method public void onError(Throwable error);
method public abstract void onResult(java.util.List<? extends T> data, int position, int totalCount);
method public abstract void onResult(java.util.List<? extends T> data, int position);
}
@@ -378,7 +376,6 @@
public abstract static class PositionalDataSource.LoadRangeCallback<T> {
ctor public PositionalDataSource.LoadRangeCallback();
- method public void onError(Throwable error);
method public abstract void onResult(java.util.List<? extends T> data);
}
diff --git a/paging/common/api/restricted_current.txt b/paging/common/api/restricted_current.txt
index 5e1eb16..73c6e1a 100644
--- a/paging/common/api/restricted_current.txt
+++ b/paging/common/api/restricted_current.txt
@@ -75,7 +75,6 @@
public abstract static class ItemKeyedDataSource.LoadCallback<Value> {
ctor public ItemKeyedDataSource.LoadCallback();
- method public void onError(Throwable error);
method public abstract void onResult(java.util.List<? extends Value> data);
}
@@ -115,13 +114,11 @@
public abstract static class PageKeyedDataSource.LoadCallback<Key, Value> {
ctor public PageKeyedDataSource.LoadCallback();
- method public void onError(Throwable error);
method public abstract void onResult(java.util.List<? extends Value> data, Key? adjacentPageKey);
}
public abstract static class PageKeyedDataSource.LoadInitialCallback<Key, Value> {
ctor public PageKeyedDataSource.LoadInitialCallback();
- method public void onError(Throwable error);
method public abstract void onResult(java.util.List<? extends Value> data, int position, int totalCount, Key? previousPageKey, Key? nextPageKey);
method public abstract void onResult(java.util.List<? extends Value> data, Key? previousPageKey, Key? nextPageKey);
}
@@ -156,7 +153,6 @@
method public int getPositionOffset();
method public int getSize();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final androidx.paging.PagedStorage<T> getStorage();
- method public abstract boolean isContiguous();
method public abstract boolean isDetached();
method public boolean isImmutable();
method public void loadAround(int index);
@@ -167,7 +163,6 @@
method public java.util.List<T> snapshot();
property public androidx.paging.PagedList.Config config;
property @Deprecated public final androidx.paging.DataSource<?,T> dataSource;
- property public abstract boolean isContiguous;
property public abstract boolean isDetached;
property public boolean isImmutable;
property public abstract Object? lastKey;
@@ -271,13 +266,10 @@
method public void invalidate();
method public abstract boolean isRetryableError(Throwable error);
method public abstract suspend Object load(androidx.paging.PagedSource.LoadParams<Key> params, kotlin.coroutines.Continuation<? super androidx.paging.PagedSource.LoadResult<Key,Value>> p);
+ method public final void registerInvalidatedCallback(kotlin.jvm.functions.Function0<kotlin.Unit> onInvalidatedCallback);
+ method public final void unregisterInvalidatedCallback(kotlin.jvm.functions.Function0<kotlin.Unit> onInvalidatedCallback);
property public final boolean invalid;
property public abstract androidx.paging.PagedSource.KeyProvider<Key,Value> keyProvider;
- field public static final int COUNT_UNDEFINED = -1; // 0xffffffff
- field public static final androidx.paging.PagedSource.Companion! Companion;
- }
-
- public static final class PagedSource.Companion {
}
public abstract static sealed class PagedSource.KeyProvider<Key, Value> {
@@ -312,22 +304,25 @@
}
public static final class PagedSource.LoadResult<Key, Value> {
- ctor public PagedSource.LoadResult(int itemsBefore, int itemsAfter, Key? nextKey, Key? prevKey, java.util.List<? extends Value> data, int offset, boolean counted);
+ ctor public PagedSource.LoadResult(@IntRange(from=null) int itemsBefore, @IntRange(from=null) int itemsAfter, Key? nextKey, Key? prevKey, java.util.List<? extends Value> data, int offset);
method public int component1();
method public int component2();
method public Key? component3();
method public Key? component4();
method public java.util.List<Value> component5();
method public int component6();
- method public boolean component7();
- method public androidx.paging.PagedSource.LoadResult<Key,Value> copy(int itemsBefore, int itemsAfter, Key? nextKey, Key? prevKey, java.util.List<? extends Value> data, int offset, boolean counted);
- method public boolean getCounted();
+ method public androidx.paging.PagedSource.LoadResult<Key,Value> copy(int itemsBefore, int itemsAfter, Key? nextKey, Key? prevKey, java.util.List<? extends Value> data, int offset);
method public java.util.List<Value> getData();
method public int getItemsAfter();
method public int getItemsBefore();
method public Key? getNextKey();
method public int getOffset();
method public Key? getPrevKey();
+ field public static final int COUNT_UNDEFINED = -1; // 0xffffffff
+ field public static final androidx.paging.PagedSource.LoadResult.Companion! Companion;
+ }
+
+ public static final class PagedSource.LoadResult.Companion {
}
public enum PagedSource.LoadType {
@@ -336,6 +331,10 @@
enum_constant public static final androidx.paging.PagedSource.LoadType START;
}
+ public final class PagedSourceKt {
+ ctor public PagedSourceKt();
+ }
+
@RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final class PagedSourceWrapper<Key, Value> extends androidx.paging.PagedSource<Key,Value> {
ctor public PagedSourceWrapper(internal androidx.paging.DataSource<Key,Value> dataSource);
method public androidx.paging.PagedSource.KeyProvider<Key,Value> getKeyProvider();
@@ -363,7 +362,6 @@
public abstract static class PositionalDataSource.LoadInitialCallback<T> {
ctor public PositionalDataSource.LoadInitialCallback();
- method public void onError(Throwable error);
method public abstract void onResult(java.util.List<? extends T> data, int position, int totalCount);
method public abstract void onResult(java.util.List<? extends T> data, int position);
}
@@ -378,7 +376,6 @@
public abstract static class PositionalDataSource.LoadRangeCallback<T> {
ctor public PositionalDataSource.LoadRangeCallback();
- method public void onError(Throwable error);
method public abstract void onResult(java.util.List<? extends T> data);
}
diff --git a/paging/common/src/main/kotlin/androidx/paging/ContiguousPagedList.kt b/paging/common/src/main/kotlin/androidx/paging/ContiguousPagedList.kt
index c80e97d..1cde4c0 100644
--- a/paging/common/src/main/kotlin/androidx/paging/ContiguousPagedList.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/ContiguousPagedList.kt
@@ -18,8 +18,8 @@
import androidx.annotation.MainThread
import androidx.annotation.RestrictTo
-import androidx.paging.PagedSource.Companion.COUNT_UNDEFINED
import androidx.paging.PagedSource.KeyProvider
+import androidx.paging.PagedSource.LoadResult.Companion.COUNT_UNDEFINED
import kotlinx.coroutines.CoroutineScope
import java.util.concurrent.Executor
@@ -84,8 +84,6 @@
override val isDetached
get() = pager.isDetached
- override val isContiguous = true
-
override val lastKey
get() = when (val keyProvider = pagedSource.keyProvider) {
is KeyProvider.Positional -> {
@@ -207,9 +205,9 @@
if (config.enablePlaceholders) {
// Placeholders enabled, pass raw data to storage init
storage.init(
- initialResult.itemsBefore,
+ if (initialResult.itemsBefore != COUNT_UNDEFINED) initialResult.itemsBefore else 0,
initialResult.data,
- initialResult.itemsAfter,
+ if (initialResult.itemsAfter != COUNT_UNDEFINED) initialResult.itemsAfter else 0,
initialResult.offset,
this
)
@@ -220,7 +218,7 @@
0,
initialResult.data,
0,
- initialResult.offset + initialResult.itemsBefore,
+ initialResult.itemsBefore + initialResult.offset,
this
)
}
@@ -228,10 +226,9 @@
if (this.lastLoad == LAST_LOAD_UNSPECIFIED) {
// Because the ContiguousPagedList wasn't initialized with a last load position,
// initialize it to the middle of the initial load
-
val itemsBefore =
if (initialResult.itemsBefore != COUNT_UNDEFINED) initialResult.itemsBefore else 0
- this.lastLoad = (itemsBefore + initialResult.data.size / 2)
+ this.lastLoad = itemsBefore + initialResult.data.size / 2
}
triggerBoundaryCallback(LoadType.REFRESH, initialResult.data)
}
diff --git a/paging/common/src/main/kotlin/androidx/paging/DataSource.kt b/paging/common/src/main/kotlin/androidx/paging/DataSource.kt
index 7992157..5330acb 100644
--- a/paging/common/src/main/kotlin/androidx/paging/DataSource.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/DataSource.kt
@@ -21,6 +21,7 @@
import androidx.annotation.VisibleForTesting
import androidx.annotation.WorkerThread
import androidx.arch.core.util.Function
+import androidx.paging.PagedSource.LoadResult.Companion.COUNT_UNDEFINED
import java.util.concurrent.CopyOnWriteArrayList
import java.util.concurrent.Executor
import java.util.concurrent.atomic.AtomicBoolean
@@ -521,13 +522,12 @@
*/
@Suppress("UNCHECKED_CAST") // Guaranteed to be the correct Key type.
internal fun <Key : Any> toLoadResult() = PagedSource.LoadResult(
- leadingNulls,
- trailingNulls,
+ if (counted) leadingNulls else COUNT_UNDEFINED,
+ if (counted) trailingNulls else COUNT_UNDEFINED,
nextKey as Key?,
prevKey as Key?,
data,
- offset,
- counted
+ offset
)
override fun equals(other: Any?) = when (other) {
diff --git a/paging/common/src/main/kotlin/androidx/paging/ItemKeyedDataSource.kt b/paging/common/src/main/kotlin/androidx/paging/ItemKeyedDataSource.kt
index 5acb252..969e289 100644
--- a/paging/common/src/main/kotlin/androidx/paging/ItemKeyedDataSource.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/ItemKeyedDataSource.kt
@@ -23,7 +23,6 @@
import kotlinx.coroutines.CancellableContinuation
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.coroutines.resume
-import kotlin.coroutines.resumeWithException
/**
* Incremental data loader for paging keyed content, where loaded content uses previously loaded
@@ -177,21 +176,6 @@
* @param data List of items loaded from the [ItemKeyedDataSource].
*/
abstract fun onResult(data: List<Value>)
-
- /**
- * Called to report an error from a DataSource.
- *
- * Call this method to report an error from [loadInitial], [loadBefore], or [loadAfter]
- * methods.
- *
- * @param error The error that occurred during loading.
- */
- open fun onError(error: Throwable) {
- // TODO: remove default implementation in 3.0
- throw IllegalStateException(
- "You must implement onError if implementing your own load callback"
- )
- }
}
@Suppress("RedundantVisibilityModifier") // Metalava doesn't inherit visibility properly.
@@ -220,10 +204,6 @@
override fun onResult(data: List<Value>) {
cont.resume(InitialResult(data))
}
-
- override fun onError(error: Throwable) {
- cont.resumeWithException(error)
- }
})
}
@@ -346,8 +326,4 @@
override fun onResult(data: List<Value>) {
resume(Result(data))
}
-
- override fun onError(error: Throwable) {
- resumeWithException(error)
- }
}
diff --git a/paging/common/src/main/kotlin/androidx/paging/PageKeyedDataSource.kt b/paging/common/src/main/kotlin/androidx/paging/PageKeyedDataSource.kt
index 4d133ac..25d5f3e 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PageKeyedDataSource.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PageKeyedDataSource.kt
@@ -22,7 +22,6 @@
import kotlinx.coroutines.CancellableContinuation
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.coroutines.resume
-import kotlin.coroutines.resumeWithException
/**
* Incremental data loader for page-keyed content, where requests return keys for next/previous
@@ -170,20 +169,6 @@
* can be loaded after.
*/
abstract fun onResult(data: List<Value>, previousPageKey: Key?, nextPageKey: Key?)
-
- /**
- * Called to report an error from a DataSource.
- *
- * Call this method to report an error from [loadInitial].
- *
- * @param error The error that occurred during loading.
- */
- open fun onError(error: Throwable) {
- // TODO: remove default implementation in 3.0
- throw IllegalStateException(
- "You must implement onError if implementing your own load callback"
- )
- }
}
/**
@@ -219,21 +204,6 @@
* direction.
*/
abstract fun onResult(data: List<Value>, adjacentPageKey: Key?)
-
- /**
- * Called to report an error from a DataSource.
- *
- * Call this method to report an error from your PageKeyedDataSource's [loadBefore] and
- * [loadAfter] methods.
- *
- * @param error The error that occurred during loading.
- */
- open fun onError(error: Throwable) {
- // TODO: remove default implementation in 3.0
- throw IllegalStateException(
- "You must implement onError if implementing your own load callback"
- )
- }
}
/**
@@ -271,10 +241,6 @@
override fun onResult(data: List<Value>, previousPageKey: Key?, nextPageKey: Key?) {
cont.resume(InitialResult(data, previousPageKey, nextPageKey))
}
-
- override fun onError(error: Throwable) {
- cont.resumeWithException(error)
- }
})
}
@@ -377,8 +343,4 @@
override fun onResult(data: List<Value>, adjacentPageKey: Key?) {
resume(Result(data, adjacentPageKey))
}
-
- override fun onError(error: Throwable) {
- resumeWithException(error)
- }
}
diff --git a/paging/common/src/main/kotlin/androidx/paging/PagedList.kt b/paging/common/src/main/kotlin/androidx/paging/PagedList.kt
index 6cd5774..6d56579 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PagedList.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PagedList.kt
@@ -20,7 +20,6 @@
import androidx.annotation.IntRange
import androidx.annotation.MainThread
import androidx.annotation.RestrictTo
-import androidx.annotation.VisibleForTesting
import androidx.annotation.WorkerThread
import androidx.paging.PagedList.Callback
import androidx.paging.PagedList.Config
@@ -1004,9 +1003,6 @@
override val size
get() = storage.size
- @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
- abstract val isContiguous: Boolean
-
/**
* The [PagedSource] that provides data to this [PagedList].
*/
@@ -1131,8 +1127,8 @@
* Retry any retryable errors associated with this [PagedList].
*
* If for example a network [PagedSource] append timed out, calling this method will retry the
- * failed append load. Note that your [PagedSource] will need to pass `true` to `onError()` to
- * signify the error as retryable.
+ * failed append load. Note that your [PagedSource] will need to implement
+ * [PagedSource.isRetryableError] to return `true` for errors that are retryable.
*
* You can observe loading state via [addWeakLoadStateListener], though generally this is done
* through the [PagedListAdapter][androidx.paging.PagedListAdapter] or
diff --git a/paging/common/src/main/kotlin/androidx/paging/PagedSource.kt b/paging/common/src/main/kotlin/androidx/paging/PagedSource.kt
index b69c824..90542b1 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PagedSource.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PagedSource.kt
@@ -16,13 +16,26 @@
package androidx.paging
+import androidx.annotation.IntRange
import androidx.paging.PagedSource.KeyProvider
import androidx.paging.PagedSource.KeyProvider.ItemKey
import androidx.paging.PagedSource.KeyProvider.PageKey
import androidx.paging.PagedSource.KeyProvider.Positional
+import java.util.concurrent.CopyOnWriteArrayList
import java.util.concurrent.atomic.AtomicBoolean
/**
+ * Factory for [PagedSource]s.
+ *
+ * Data-loading systems of an application or library can implement provide this type to allow
+ * `LiveData<PagedList>`s to be created.
+ *
+ * @param Key Key identifying items in PagedSource.
+ * @param Value Type of items in the list loaded by the PagedSources.
+ */
+typealias PagedSourceFactory<Key, Value> = () -> PagedSource<Key, Value>
+
+/**
* Base class for an abstraction of pageable static data from some source, where loading pages data
* is typically an expensive operation. Some examples of common [PagedSource]s might be from network
* or DB.
@@ -123,10 +136,12 @@
/**
* Optional count of items before the loaded data.
*/
+ @IntRange(from = COUNT_UNDEFINED.toLong())
val itemsBefore: Int = COUNT_UNDEFINED,
/**
* Optional count of items after the loaded data.
*/
+ @IntRange(from = COUNT_UNDEFINED.toLong())
val itemsAfter: Int = COUNT_UNDEFINED,
/**
* Key for next page - ignored unless you're using [KeyProvider.PageKey]
@@ -147,18 +162,15 @@
*
* TODO: Investigate refactoring this out of the API now that tiling has been removed.
*/
- val offset: Int,
- /**
- * `true` if the result is an initial load that is passed to
- * [DataSource.BaseResult.totalCount]. This is a temporary placeholder shadowing
- * [DataSource.BaseResult.counted] which simply forwards the params to backing
- * implementations of [PagedSource].
- */
- val counted: Boolean = itemsBefore != COUNT_UNDEFINED && itemsAfter != COUNT_UNDEFINED
+ val offset: Int
) {
- internal companion object {
+ internal val counted = itemsBefore != COUNT_UNDEFINED && itemsAfter != COUNT_UNDEFINED
+
+ companion object {
+ const val COUNT_UNDEFINED = -1
+
@Suppress("MemberVisibilityCanBePrivate") // Prevent synthetic accessor generation.
- internal val EMPTY = LoadResult(0, 0, null, null, emptyList(), 0, true)
+ internal val EMPTY = LoadResult(0, 0, null, null, emptyList(), 0)
@Suppress("UNCHECKED_CAST") // Can safely ignore, since the list is empty.
internal fun <Key : Any, Value : Any> empty() = EMPTY as LoadResult<Key, Value>
@@ -183,6 +195,8 @@
abstract val keyProvider: KeyProvider<Key, Value>
+ private val onInvalidatedCallbacks = CopyOnWriteArrayList<() -> Unit>()
+
private val _invalid = AtomicBoolean(false)
/**
* Whether this [PagedSource] has been invalidated, which should happen when the data this
@@ -199,7 +213,35 @@
*
* TODO(b/137971356): Investigate making this not open when able to remove [PagedSourceWrapper].
*/
- open fun invalidate() = _invalid.set(true)
+ open fun invalidate() {
+ if (_invalid.compareAndSet(false, true)) {
+ onInvalidatedCallbacks.forEach { it.invoke() }
+ }
+ }
+
+ /**
+ * Add a callback to invoke when the [PagedSource] is first invalidated.
+ *
+ * Once invalidated, a [PagedSource] will not become valid again.
+ *
+ * A [PagedSource] will only invoke its callbacks once - the first time [invalidate] is called,
+ * on that thread.
+ *
+ * @param onInvalidatedCallback The callback that will be invoked on thread that invalidates the
+ * [PagedSource].
+ */
+ fun registerInvalidatedCallback(onInvalidatedCallback: () -> Unit) {
+ onInvalidatedCallbacks.add(onInvalidatedCallback)
+ }
+
+ /**
+ * Remove a previously added invalidate callback.
+ *
+ * @param onInvalidatedCallback The previously added callback.
+ */
+ fun unregisterInvalidatedCallback(onInvalidatedCallback: () -> Unit) {
+ onInvalidatedCallbacks.remove(onInvalidatedCallback)
+ }
/**
* Loading API for [PagedSource].
@@ -215,9 +257,4 @@
* @return `false` if the observed error should never be retried, `true` otherwise.
*/
abstract fun isRetryableError(error: Throwable): Boolean
-
- companion object {
- // TODO: Remove this by making itemsBefore and itemsAfter nullable before releasing 3.0.0
- const val COUNT_UNDEFINED = -1
- }
}
diff --git a/paging/common/src/main/kotlin/androidx/paging/PagedSourceWrapper.kt b/paging/common/src/main/kotlin/androidx/paging/PagedSourceWrapper.kt
index 7f12702..38c7a8d 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PagedSourceWrapper.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PagedSourceWrapper.kt
@@ -17,9 +17,6 @@
package androidx.paging
import androidx.annotation.RestrictTo
-import androidx.paging.PagedSource.Companion.COUNT_UNDEFINED
-import kotlinx.coroutines.asCoroutineDispatcher
-import kotlinx.coroutines.withContext
/**
* TODO: Move all call-sites dependent on this to use [PagedSource] directly.
@@ -72,71 +69,3 @@
dataSource.invalidate()
}
}
-
-/**
- * TODO: This should no longer be necessary once internal implementation has been moved to used
- * [PagedSource] directly.
- *
- * A wrapper around [PagedSource] which adapts it to the [DataSource] API.
- */
-internal class DataSourceWrapper<Key : Any, Value : Any>(
- internal val pagedSource: PagedSource<Key, Value>
-) : DataSource<Key, Value>(
- when (pagedSource.keyProvider) {
- is PagedSource.KeyProvider.Positional -> KeyType.POSITIONAL
- is PagedSource.KeyProvider.PageKey -> KeyType.PAGE_KEYED
- is PagedSource.KeyProvider.ItemKey -> KeyType.ITEM_KEYED
- }
-) {
- override suspend fun load(params: Params<Key>): BaseResult<Value> {
- val loadType = when (params.type) {
- LoadType.INITIAL -> PagedSource.LoadType.INITIAL
- LoadType.START -> PagedSource.LoadType.START
- LoadType.END -> PagedSource.LoadType.END
- }
-
- val dataSourceParams = PagedSource.LoadParams(
- loadType,
- params.key,
- params.initialLoadSize,
- params.placeholdersEnabled,
- params.pageSize
- )
-
- val initialResult = withContext(executor.asCoroutineDispatcher()) {
- pagedSource.load(dataSourceParams)
- }
-
- return BaseResult(
- initialResult.data,
- initialResult.prevKey,
- initialResult.nextKey,
- if (initialResult.itemsBefore != COUNT_UNDEFINED) initialResult.itemsBefore else 0,
- if (initialResult.itemsAfter != COUNT_UNDEFINED) initialResult.itemsAfter else 0,
- initialResult.offset,
- initialResult.counted
- )
- }
-
- /**
- * @throws IllegalStateException
- */
- override fun getKeyInternal(item: Value): Key {
- return when (val keyProvider = pagedSource.keyProvider) {
- is PagedSource.KeyProvider.Positional ->
- throw IllegalStateException("Cannot get key by item in positionalDataSource")
- is PagedSource.KeyProvider.PageKey ->
- throw IllegalStateException("Cannot get key by item in pageKeyedDataSource")
- is PagedSource.KeyProvider.ItemKey -> keyProvider.getKey(item)
- }
- }
-
- override fun isRetryableError(error: Throwable): Boolean = pagedSource.isRetryableError(error)
-
- override val isInvalid: Boolean
- get() = pagedSource.invalid
-
- override fun invalidate() {
- pagedSource.invalidate()
- }
-}
\ No newline at end of file
diff --git a/paging/common/src/main/kotlin/androidx/paging/Pager.kt b/paging/common/src/main/kotlin/androidx/paging/Pager.kt
index 557a333..ca811d1 100644
--- a/paging/common/src/main/kotlin/androidx/paging/Pager.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/Pager.kt
@@ -18,7 +18,7 @@
import androidx.paging.PagedList.LoadState
import androidx.paging.PagedList.LoadType
-import androidx.paging.PagedSource.Companion.COUNT_UNDEFINED
+import androidx.paging.PagedSource.LoadResult.Companion.COUNT_UNDEFINED
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.launch
@@ -267,8 +267,8 @@
firstLoadedItem = result.data[0]
lastLoadedItem = result.data.last()
- if (result.counted) {
- counted = true
+ counted = result.counted
+ if (counted) {
leadingUnloadedCount = result.itemsBefore
trailingUnloadedCount = result.itemsAfter
}
diff --git a/paging/common/src/main/kotlin/androidx/paging/PositionalDataSource.kt b/paging/common/src/main/kotlin/androidx/paging/PositionalDataSource.kt
index 6365c7c..64b9fb4 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PositionalDataSource.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PositionalDataSource.kt
@@ -24,7 +24,6 @@
import androidx.paging.PositionalDataSource.LoadInitialCallback
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.coroutines.resume
-import kotlin.coroutines.resumeWithException
/**
* Position-based data loader for a fixed-size, countable data set, supporting fixed-size loads at
@@ -182,20 +181,6 @@
* before the items in data that can be provided by this [DataSource], pass N.
*/
abstract fun onResult(data: List<T>, position: Int)
-
- /**
- * Called to report an error from a DataSource.
- *
- * Call this method to report an error from [loadInitial].
- *
- * @param error The error that occurred during loading.
- */
- open fun onError(error: Throwable) {
- // TODO: remove default implementation in 3.0
- throw IllegalStateException(
- "You must implement onError if implementing your own load callback"
- )
- }
}
/**
@@ -217,20 +202,6 @@
* unless at end of list.
*/
abstract fun onResult(data: List<T>)
-
- /**
- * Called to report an error from a [DataSource].
- *
- * Call this method to report an error from [loadRange].
- *
- * @param error The error that occurred during loading.
- */
- open fun onError(error: Throwable) {
- // TODO: remove default implementation in 3.0
- throw IllegalStateException(
- "You must implement onError if implementing your own load callback"
- )
- }
}
/**
@@ -424,10 +395,6 @@
}
}
- override fun onError(error: Throwable) {
- cont.resumeWithException(error)
- }
-
private fun resume(params: LoadInitialParams, result: InitialResult<T>) {
if (params.placeholdersEnabled) {
result.validateForInitialTiling(params.pageSize)
@@ -459,10 +426,6 @@
else -> cont.resume(RangeResult(data))
}
}
-
- override fun onError(error: Throwable) {
- cont.resumeWithException(error)
- }
})
}
diff --git a/paging/common/src/main/kotlin/androidx/paging/SnapshotPagedList.kt b/paging/common/src/main/kotlin/androidx/paging/SnapshotPagedList.kt
index 3a74fda..5688543 100644
--- a/paging/common/src/main/kotlin/androidx/paging/SnapshotPagedList.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/SnapshotPagedList.kt
@@ -28,9 +28,6 @@
lastLoad = pagedList.lastLoad
}
- override val isContiguous
- get() = pagedList.isContiguous
-
override val isImmutable = true
override val lastKey
diff --git a/paging/common/src/main/kotlin/androidx/paging/WrapperItemKeyedDataSource.kt b/paging/common/src/main/kotlin/androidx/paging/WrapperItemKeyedDataSource.kt
index 65047b9..386a370 100644
--- a/paging/common/src/main/kotlin/androidx/paging/WrapperItemKeyedDataSource.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/WrapperItemKeyedDataSource.kt
@@ -54,7 +54,7 @@
}
override fun loadInitial(params: LoadInitialParams<K>, callback: LoadInitialCallback<B>) {
- source.loadInitial(params, object : ItemKeyedDataSource.LoadInitialCallback<A>() {
+ source.loadInitial(params, object : LoadInitialCallback<A>() {
override fun onResult(data: List<A>, position: Int, totalCount: Int) {
callback.onResult(convertWithStashedKeys(data), position, totalCount)
}
@@ -62,34 +62,22 @@
override fun onResult(data: List<A>) {
callback.onResult(convertWithStashedKeys(data))
}
-
- override fun onError(error: Throwable) {
- callback.onError(error)
- }
})
}
override fun loadAfter(params: LoadParams<K>, callback: LoadCallback<B>) {
- source.loadAfter(params, object : ItemKeyedDataSource.LoadCallback<A>() {
+ source.loadAfter(params, object : LoadCallback<A>() {
override fun onResult(data: List<A>) {
callback.onResult(convertWithStashedKeys(data))
}
-
- override fun onError(error: Throwable) {
- callback.onError(error)
- }
})
}
override fun loadBefore(params: LoadParams<K>, callback: LoadCallback<B>) {
- source.loadBefore(params, object : ItemKeyedDataSource.LoadCallback<A>() {
+ source.loadBefore(params, object : LoadCallback<A>() {
override fun onResult(data: List<A>) {
callback.onResult(convertWithStashedKeys(data))
}
-
- override fun onError(error: Throwable) {
- callback.onError(error)
- }
})
}
diff --git a/paging/common/src/main/kotlin/androidx/paging/WrapperPageKeyedDataSource.kt b/paging/common/src/main/kotlin/androidx/paging/WrapperPageKeyedDataSource.kt
index c49b480..2f17e3a 100644
--- a/paging/common/src/main/kotlin/androidx/paging/WrapperPageKeyedDataSource.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/WrapperPageKeyedDataSource.kt
@@ -34,7 +34,7 @@
override fun invalidate() = source.invalidate()
override fun loadInitial(params: LoadInitialParams<K>, callback: LoadInitialCallback<K, B>) {
- source.loadInitial(params, object : PageKeyedDataSource.LoadInitialCallback<K, A>() {
+ source.loadInitial(params, object : LoadInitialCallback<K, A>() {
override fun onResult(
data: List<A>,
position: Int,
@@ -50,26 +50,20 @@
val convertedData = convert(listFunction, data)
callback.onResult(convertedData, previousPageKey, nextPageKey)
}
-
- override fun onError(error: Throwable) = callback.onError(error)
})
}
override fun loadBefore(params: LoadParams<K>, callback: LoadCallback<K, B>) {
- source.loadBefore(params, object : PageKeyedDataSource.LoadCallback<K, A>() {
+ source.loadBefore(params, object : LoadCallback<K, A>() {
override fun onResult(data: List<A>, adjacentPageKey: K?) =
callback.onResult(convert(listFunction, data), adjacentPageKey)
-
- override fun onError(error: Throwable) = callback.onError(error)
})
}
override fun loadAfter(params: LoadParams<K>, callback: LoadCallback<K, B>) {
- source.loadAfter(params, object : PageKeyedDataSource.LoadCallback<K, A>() {
+ source.loadAfter(params, object : LoadCallback<K, A>() {
override fun onResult(data: List<A>, adjacentPageKey: K?) =
callback.onResult(convert(listFunction, data), adjacentPageKey)
-
- override fun onError(error: Throwable) = callback.onError(error)
})
}
}
diff --git a/paging/common/src/main/kotlin/androidx/paging/WrapperPositionalDataSource.kt b/paging/common/src/main/kotlin/androidx/paging/WrapperPositionalDataSource.kt
index 683d670..8308e87 100644
--- a/paging/common/src/main/kotlin/androidx/paging/WrapperPositionalDataSource.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/WrapperPositionalDataSource.kt
@@ -40,16 +40,12 @@
override fun onResult(data: List<A>, position: Int) =
callback.onResult(convert(listFunction, data), position)
-
- override fun onError(error: Throwable) = callback.onError(error)
})
}
override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<B>) {
source.loadRange(params, object : LoadRangeCallback<A>() {
override fun onResult(data: List<A>) = callback.onResult(convert(listFunction, data))
-
- override fun onError(error: Throwable) = callback.onError(error)
})
}
}
diff --git a/paging/common/src/test/kotlin/androidx/paging/ContiguousPagedListTest.kt b/paging/common/src/test/kotlin/androidx/paging/ContiguousPagedListTest.kt
index 24fa860..c548b73 100644
--- a/paging/common/src/test/kotlin/androidx/paging/ContiguousPagedListTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/ContiguousPagedListTest.kt
@@ -87,23 +87,22 @@
itemsBefore = start,
itemsAfter = listData.size - result.size - start,
data = result,
- offset = start,
- counted = false
+ offset = start
)
- else -> LoadResult(data = result, offset = 0, counted = false)
+ else -> LoadResult(data = result, offset = 0)
}
}
private fun loadAfter(params: LoadParams<Int>): LoadResult<Int, Item> {
val result = getClampedRange(params.key!! + 1, params.key!! + 1 + params.loadSize)
?: throw Exception()
- return LoadResult(data = result, offset = 0, counted = false)
+ return LoadResult(data = result, offset = 0)
}
private fun loadBefore(params: LoadParams<Int>): LoadResult<Int, Item> {
val result =
getClampedRange(params.key!! - params.loadSize, params.key!!) ?: throw Exception()
- return LoadResult(data = result, offset = 0, counted = false)
+ return LoadResult(data = result, offset = 0)
}
private fun getClampedRange(startInc: Int, endExc: Int): List<Item>? {
diff --git a/paging/common/src/test/kotlin/androidx/paging/ItemKeyedDataSourceTest.kt b/paging/common/src/test/kotlin/androidx/paging/ItemKeyedDataSourceTest.kt
index b847ea6..4068366 100644
--- a/paging/common/src/test/kotlin/androidx/paging/ItemKeyedDataSourceTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/ItemKeyedDataSourceTest.kt
@@ -88,7 +88,6 @@
fun loadInitial_nullKey() = runBlocking {
val dataSource = ItemDataSource()
- // dispatchLoadInitial(null, count) == dispatchLoadInitial(count)
val result = loadInitial(dataSource, null, 10, true)
assertEquals(0, result.leadingNulls)
@@ -212,18 +211,10 @@
private val counted: Boolean = true,
private val items: List<Item> = ITEMS_BY_NAME_ID
) : ItemKeyedDataSource<Key, Item>() {
- private var error = false
-
override fun loadInitial(
params: LoadInitialParams<Key>,
callback: LoadInitialCallback<Item>
) {
- if (error) {
- callback.onError(EXCEPTION)
- error = false
- return
- }
-
val key = params.requestedInitialKey ?: Key("", Int.MAX_VALUE)
val start = maxOf(0, findFirstIndexAfter(key) - params.requestedLoadSize / 2)
val endExclusive = minOf(start + params.requestedLoadSize, items.size)
@@ -236,12 +227,6 @@
}
override fun loadAfter(params: LoadParams<Key>, callback: LoadCallback<Item>) {
- if (error) {
- callback.onError(EXCEPTION)
- error = false
- return
- }
-
val start = findFirstIndexAfter(params.key)
val endExclusive = minOf(start + params.requestedLoadSize, items.size)
@@ -249,12 +234,6 @@
}
override fun loadBefore(params: LoadParams<Key>, callback: LoadCallback<Item>) {
- if (error) {
- callback.onError(EXCEPTION)
- error = false
- return
- }
-
val firstIndexBefore = findFirstIndexBefore(params.key)
val endExclusive = maxOf(0, firstIndexBefore + 1)
val start = maxOf(0, firstIndexBefore - params.requestedLoadSize + 1)
@@ -277,10 +256,6 @@
KEY_COMPARATOR.compare(key, getKey(items[it])) > 0
} ?: -1
}
-
- fun enqueueError() {
- error = true
- }
}
private fun performLoadInitial(
@@ -408,10 +383,6 @@
override fun onResult(data: List<A>) {
callback.onResult(convert(data))
}
-
- override fun onError(error: Throwable) {
- callback.onError(error)
- }
})
}
@@ -420,10 +391,6 @@
override fun onResult(data: List<A>) {
callback.onResult(convert(data))
}
-
- override fun onError(error: Throwable) {
- callback.onError(error)
- }
})
}
@@ -432,10 +399,6 @@
override fun onResult(data: List<A>) {
callback.onResult(convert(data))
}
-
- override fun onError(error: Throwable) {
- callback.onError(error)
- }
})
}
@@ -473,12 +436,8 @@
loadInitialCallback
)
verify(loadInitialCallback).onResult(
- ITEMS_BY_NAME_ID.subList(0, 10).map { DecoratedItem(it) })
- // error
- orig.enqueueError()
- wrapper.loadInitial(initParams, loadInitialCallback)
- verify(loadInitialCallback).onError(EXCEPTION)
- verifyNoMoreInteractions(loadInitialCallback)
+ ITEMS_BY_NAME_ID.subList(0, 10).map { DecoratedItem(it) }
+ )
val key = orig.getKey(ITEMS_BY_NAME_ID[20])
@Suppress("UNCHECKED_CAST")
@@ -487,22 +446,12 @@
// load after
wrapper.loadAfter(ItemKeyedDataSource.LoadParams(key, 10), loadCallback)
verify(loadCallback).onResult(ITEMS_BY_NAME_ID.subList(21, 31).map { DecoratedItem(it) })
- // load after - error
- orig.enqueueError()
- wrapper.loadAfter(ItemKeyedDataSource.LoadParams(key, 10), loadCallback)
- verify(loadCallback).onError(EXCEPTION)
- verifyNoMoreInteractions(loadCallback)
// load before
@Suppress("UNCHECKED_CAST")
loadCallback = mock()
wrapper.loadBefore(ItemKeyedDataSource.LoadParams(key, 10), loadCallback)
verify(loadCallback).onResult(ITEMS_BY_NAME_ID.subList(10, 20).map { DecoratedItem(it) })
- // load before - error
- orig.enqueueError()
- wrapper.loadBefore(ItemKeyedDataSource.LoadParams(key, 10), loadCallback)
- verify(loadCallback).onError(EXCEPTION)
- verifyNoMoreInteractions(loadCallback)
// verify invalidation
orig.invalidate()
@@ -555,7 +504,5 @@
(Math.random() * 200).toInt().toString() + " fake st."
)
}.sortedWith(ITEM_COMPARATOR)
-
- private val EXCEPTION = Exception()
}
}
diff --git a/paging/common/src/test/kotlin/androidx/paging/PageKeyedDataSourceTest.kt b/paging/common/src/test/kotlin/androidx/paging/PageKeyedDataSourceTest.kt
index fc82d9a..af58970 100644
--- a/paging/common/src/test/kotlin/androidx/paging/PageKeyedDataSourceTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/PageKeyedDataSourceTest.kt
@@ -57,7 +57,6 @@
callback: LoadInitialCallback<String, Item>
) {
if (error) {
- callback.onError(EXCEPTION)
error = false
return
}
@@ -68,7 +67,6 @@
override fun loadBefore(params: LoadParams<String>, callback: LoadCallback<String, Item>) {
if (error) {
- callback.onError(EXCEPTION)
error = false
return
}
@@ -79,7 +77,6 @@
override fun loadAfter(params: LoadParams<String>, callback: LoadCallback<String, Item>) {
if (error) {
- callback.onError(EXCEPTION)
error = false
return
}
@@ -269,7 +266,7 @@
val pagedListJob = testCoroutineScope.async(executor.asCoroutineDispatcher()) {
PagedList.create(
PagedSourceWrapper(dataSource),
- GlobalScope,
+ testCoroutineScope,
executor,
executor,
executor,
@@ -399,10 +396,6 @@
override fun onResult(data: List<A>, previousPageKey: K?, nextPageKey: K?) {
callback.onResult(convert(data), previousPageKey, nextPageKey)
}
-
- override fun onError(error: Throwable) {
- callback.onError(error)
- }
})
}
@@ -411,10 +404,6 @@
override fun onResult(data: List<A>, adjacentPageKey: K?) {
callback.onResult(convert(data), adjacentPageKey)
}
-
- override fun onError(error: Throwable) {
- callback.onError(error)
- }
})
}
@@ -423,10 +412,6 @@
override fun onResult(data: List<A>, adjacentPageKey: K?) {
callback.onResult(convert(data), adjacentPageKey)
}
-
- override fun onError(error: Throwable) {
- callback.onError(error)
- }
})
}
@@ -471,7 +456,6 @@
// load after - error
orig.enqueueError()
wrapper.loadAfter(PageKeyedDataSource.LoadParams(expectedInitial.next, 4), loadCallback)
- verify(loadCallback).onError(EXCEPTION)
verifyNoMoreInteractions(loadCallback)
// load before
@@ -487,7 +471,6 @@
// load before - error
orig.enqueueError()
wrapper.loadBefore(PageKeyedDataSource.LoadParams(expectedAfter.prev, 4), loadCallback)
- verify(loadCallback).onError(EXCEPTION)
verifyNoMoreInteractions(loadCallback)
// verify invalidation
diff --git a/paging/common/src/test/kotlin/androidx/paging/PagedListTest.kt b/paging/common/src/test/kotlin/androidx/paging/PagedListTest.kt
index 7d514f3..4cad7b8 100644
--- a/paging/common/src/test/kotlin/androidx/paging/PagedListTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/PagedListTest.kt
@@ -23,7 +23,6 @@
import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
-import org.junit.Assert.fail
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@@ -45,8 +44,7 @@
0,
0,
data = listOf("a"),
- offset = 0,
- counted = true
+ offset = 0
)
else -> throw NotImplementedError("Test should fail if we get here")
}
@@ -102,17 +100,14 @@
@Test
fun createAsyncThrow() {
- val dataSource = object : PositionalDataSource<String>() {
- override fun loadInitial(
- params: LoadInitialParams,
- callback: LoadInitialCallback<String>
- ) {
- callback.onError(Exception())
+ val pagedSource = object : PagedSource<Int, String>() {
+ override val keyProvider = KeyProvider.Positional<String>()
+
+ override suspend fun load(params: LoadParams<Int>): LoadResult<Int, String> {
+ throw Exception()
}
- override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<String>) {
- fail("no load range expected")
- }
+ override fun isRetryableError(error: Throwable) = false
}
val config = PagedList.Config.Builder()
@@ -123,7 +118,7 @@
assertFails {
val job = testCoroutineScope.async(backgroundThread.asCoroutineDispatcher()) {
PagedList.create(
- PagedSourceWrapper(dataSource),
+ pagedSource,
testCoroutineScope,
mainThread,
backgroundThread,
diff --git a/paging/common/src/test/kotlin/androidx/paging/PagedSourceTest.kt b/paging/common/src/test/kotlin/androidx/paging/PagedSourceTest.kt
new file mode 100644
index 0000000..3ef86fb
--- /dev/null
+++ b/paging/common/src/test/kotlin/androidx/paging/PagedSourceTest.kt
@@ -0,0 +1,344 @@
+/*
+ * 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.paging
+
+import androidx.paging.PagedSource.LoadParams
+import androidx.paging.PagedSource.LoadResult
+import androidx.paging.PagedSource.LoadResult.Companion.COUNT_UNDEFINED
+import androidx.paging.PagedSource.LoadType
+import kotlinx.coroutines.runBlocking
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import kotlin.test.assertFailsWith
+
+@RunWith(JUnit4::class)
+class PagedSourceTest {
+
+ // ----- STANDARD -----
+
+ private suspend fun loadInitial(
+ pagedSource: ItemDataSource,
+ key: Key?,
+ initialLoadSize: Int,
+ enablePlaceholders: Boolean
+ ): LoadResult<Key, Item> {
+ return pagedSource.load(
+ LoadParams(
+ LoadType.INITIAL,
+ key,
+ initialLoadSize,
+ enablePlaceholders,
+ 10
+ )
+ )
+ }
+
+ @Test
+ fun loadInitial() {
+ runBlocking {
+ val pagedSource = ItemDataSource()
+ val key = pagedSource.keyProvider.getKey(ITEMS_BY_NAME_ID[49])
+ val result = loadInitial(pagedSource, key, 10, true)
+
+ assertEquals(45, result.itemsBefore)
+ assertEquals(ITEMS_BY_NAME_ID.subList(45, 55), result.data)
+ assertEquals(45, result.itemsAfter)
+
+ // Verify error is propagated correctly.
+ pagedSource.enqueueError()
+ val errorParams = LoadParams(LoadType.INITIAL, key, 10, false, 10)
+ assertFailsWith<CustomException> {
+ pagedSource.load(errorParams)
+ }
+ }
+ }
+
+ @Test
+ fun loadInitial_keyMatchesSingleItem() = runBlocking {
+ val pagedSource = ItemDataSource(items = ITEMS_BY_NAME_ID.subList(0, 1))
+
+ // this is tricky, since load after and load before with the passed key will fail
+ val result =
+ loadInitial(pagedSource, pagedSource.keyProvider.getKey(ITEMS_BY_NAME_ID[0]), 20, true)
+
+ assertEquals(0, result.itemsBefore)
+ assertEquals(ITEMS_BY_NAME_ID.subList(0, 1), result.data)
+ assertEquals(0, result.itemsAfter)
+ }
+
+ @Test
+ fun loadInitial_keyMatchesLastItem() = runBlocking {
+ val pagedSource = ItemDataSource()
+
+ // tricky, because load after key is empty, so another load before and load after required
+ val key = pagedSource.keyProvider.getKey(ITEMS_BY_NAME_ID.last())
+ val result = loadInitial(pagedSource, key, 20, true)
+
+ assertEquals(90, result.itemsBefore)
+ assertEquals(ITEMS_BY_NAME_ID.subList(90, 100), result.data)
+ assertEquals(0, result.itemsAfter)
+ }
+
+ @Test
+ fun loadInitial_nullKey() = runBlocking {
+ val dataSource = ItemDataSource()
+
+ val result = loadInitial(dataSource, null, 10, true)
+
+ assertEquals(0, result.itemsBefore)
+ assertEquals(ITEMS_BY_NAME_ID.subList(0, 10), result.data)
+ assertEquals(90, result.itemsAfter)
+ }
+
+ @Test
+ fun loadInitial_keyPastEndOfList() = runBlocking {
+ val dataSource = ItemDataSource()
+
+ // if key is past entire data set, should return last items in data set
+ val key = Key("fz", 0)
+ val result = loadInitial(dataSource, key, 10, true)
+
+ // NOTE: ideally we'd load 10 items here, but it adds complexity and unpredictability to
+ // do: load after was empty, so pass full size to load before, since this can incur larger
+ // loads than requested (see keyMatchesLastItem test)
+ assertEquals(95, result.itemsBefore)
+ assertEquals(ITEMS_BY_NAME_ID.subList(95, 100), result.data)
+ assertEquals(0, result.itemsAfter)
+ }
+
+ // ----- UNCOUNTED -----
+
+ @Test
+ fun loadInitial_disablePlaceholders() = runBlocking {
+ val dataSource = ItemDataSource()
+
+ // dispatchLoadInitial(key, count) == null padding, loadAfter(key, count), null padding
+ val key = dataSource.keyProvider.getKey(ITEMS_BY_NAME_ID[49])
+ val result = loadInitial(dataSource, key, 10, false)
+
+ assertEquals(COUNT_UNDEFINED, result.itemsBefore)
+ assertEquals(ITEMS_BY_NAME_ID.subList(45, 55), result.data)
+ assertEquals(COUNT_UNDEFINED, result.itemsAfter)
+ }
+
+ @Test
+ fun loadInitial_uncounted() = runBlocking {
+ val dataSource = ItemDataSource(counted = false)
+
+ // dispatchLoadInitial(key, count) == null padding, loadAfter(key, count), null padding
+ val key = dataSource.keyProvider.getKey(ITEMS_BY_NAME_ID[49])
+ val result = loadInitial(dataSource, key, 10, true)
+
+ assertEquals(COUNT_UNDEFINED, result.itemsBefore)
+ assertEquals(ITEMS_BY_NAME_ID.subList(45, 55), result.data)
+ assertEquals(COUNT_UNDEFINED, result.itemsAfter)
+ }
+
+ @Test
+ fun loadInitial_nullKey_uncounted() = runBlocking {
+ val dataSource = ItemDataSource(counted = false)
+
+ val result = loadInitial(dataSource, null, 10, true)
+
+ assertEquals(COUNT_UNDEFINED, result.itemsBefore)
+ assertEquals(ITEMS_BY_NAME_ID.subList(0, 10), result.data)
+ assertEquals(COUNT_UNDEFINED, result.itemsAfter)
+ }
+
+ // ----- EMPTY -----
+
+ @Test
+ fun loadInitial_empty() = runBlocking {
+ val dataSource = ItemDataSource(items = ArrayList())
+
+ // dispatchLoadInitial(key, count) == null padding, loadAfter(key, count), null padding
+ val key = dataSource.keyProvider.getKey(ITEMS_BY_NAME_ID[49])
+ val result = loadInitial(dataSource, key, 10, true)
+
+ assertEquals(0, result.itemsBefore)
+ assertTrue(result.data.isEmpty())
+ assertEquals(0, result.itemsAfter)
+ }
+
+ @Test
+ fun loadInitial_nullKey_empty() = runBlocking {
+ val dataSource = ItemDataSource(items = ArrayList())
+ val result = loadInitial(dataSource, null, 10, true)
+
+ assertEquals(0, result.itemsBefore)
+ assertTrue(result.data.isEmpty())
+ assertEquals(0, result.itemsAfter)
+ }
+
+ // ----- Other behavior -----
+
+ @Test
+ fun loadBefore() {
+ val dataSource = ItemDataSource()
+
+ runBlocking {
+ val key = dataSource.keyProvider.getKey(ITEMS_BY_NAME_ID[5])
+ val params = LoadParams(LoadType.START, key, 5, false, 5)
+ val observed = dataSource.load(params).data
+
+ assertEquals(ITEMS_BY_NAME_ID.subList(0, 5), observed)
+
+ // Verify error is propagated correctly.
+ dataSource.enqueueError()
+ assertFailsWith<CustomException> {
+ val errorParams = LoadParams(LoadType.START, key, 5, false, 5)
+ dataSource.load(errorParams)
+ }
+ }
+ }
+
+ @Test
+ fun loadAfter() {
+ val dataSource = ItemDataSource()
+
+ runBlocking {
+ val key = dataSource.keyProvider.getKey(ITEMS_BY_NAME_ID[5])
+ val params = LoadParams(LoadType.END, key, 5, false, 5)
+ val observed = dataSource.load(params).data
+
+ assertEquals(ITEMS_BY_NAME_ID.subList(6, 11), observed)
+
+ // Verify error is propagated correctly.
+ dataSource.enqueueError()
+ assertFailsWith<CustomException> {
+ val errorParams = LoadParams(LoadType.END, key, 5, false, 5)
+ dataSource.load(errorParams)
+ }
+ }
+ }
+
+ internal data class Key(val name: String, val id: Int)
+
+ internal data class Item(
+ val name: String,
+ val id: Int,
+ val balance: Double,
+ val address: String
+ )
+
+ internal class ItemDataSource(
+ private val counted: Boolean = true,
+ private val items: List<Item> = ITEMS_BY_NAME_ID
+ ) : PagedSource<Key, Item>() {
+ private var error = false
+
+ override val keyProvider = object : KeyProvider.ItemKey<Key, Item>() {
+ override fun getKey(item: Item) = Key(item.name, item.id)
+ }
+
+ override suspend fun load(params: LoadParams<Key>): LoadResult<Key, Item> {
+ return when (params.loadType) {
+ LoadType.INITIAL -> loadInitial(params)
+ LoadType.START -> loadBefore(params)
+ LoadType.END -> loadAfter(params)
+ }
+ }
+
+ override fun isRetryableError(error: Throwable) = false
+
+ private fun loadInitial(params: LoadParams<Key>): LoadResult<Key, Item> {
+ if (error) {
+ error = false
+ throw EXCEPTION
+ }
+
+ val key = params.key ?: Key("", Int.MAX_VALUE)
+ val start = maxOf(0, findFirstIndexAfter(key) - params.loadSize / 2)
+ val endExclusive = minOf(start + params.loadSize, items.size)
+
+ return if (params.placeholdersEnabled && counted) {
+ val data = items.subList(start, endExclusive)
+ LoadResult(
+ itemsBefore = start,
+ itemsAfter = items.size - data.size - start,
+ data = data,
+ offset = start
+ )
+ } else {
+ LoadResult(data = items.subList(start, endExclusive), offset = 0)
+ }
+ }
+
+ private fun loadAfter(params: LoadParams<Key>): LoadResult<Key, Item> {
+ if (error) {
+ error = false
+ throw EXCEPTION
+ }
+
+ val start = findFirstIndexAfter(params.key!!)
+ val endExclusive = minOf(start + params.loadSize, items.size)
+
+ return LoadResult(data = items.subList(start, endExclusive), offset = 0)
+ }
+
+ private fun loadBefore(params: LoadParams<Key>): LoadResult<Key, Item> {
+ if (error) {
+ error = false
+ throw EXCEPTION
+ }
+
+ val firstIndexBefore = findFirstIndexBefore(params.key!!)
+ val endExclusive = maxOf(0, firstIndexBefore + 1)
+ val start = maxOf(0, firstIndexBefore - params.loadSize + 1)
+
+ return LoadResult(data = items.subList(start, endExclusive), offset = 0)
+ }
+
+ private fun findFirstIndexAfter(key: Key): Int {
+ return items.indices.firstOrNull {
+ KEY_COMPARATOR.compare(key, keyProvider.getKey(items[it])) < 0
+ } ?: items.size
+ }
+
+ private fun findFirstIndexBefore(key: Key): Int {
+ return items.indices.reversed().firstOrNull {
+ KEY_COMPARATOR.compare(key, keyProvider.getKey(items[it])) > 0
+ } ?: -1
+ }
+
+ fun enqueueError() {
+ error = true
+ }
+ }
+
+ class CustomException : Exception()
+
+ companion object {
+ private val ITEM_COMPARATOR = compareBy<Item> { it.name }.thenByDescending { it.id }
+ private val KEY_COMPARATOR = compareBy<Key> { it.name }.thenByDescending { it.id }
+
+ private val ITEMS_BY_NAME_ID = List(100) {
+ val names = Array(10) { index -> "f" + ('a' + index) }
+ Item(
+ names[it % 10],
+ it,
+ Math.random() * 1000,
+ (Math.random() * 200).toInt().toString() + " fake st."
+ )
+ }.sortedWith(ITEM_COMPARATOR)
+
+ private val EXCEPTION = CustomException()
+ }
+}
diff --git a/paging/common/src/test/kotlin/androidx/paging/PositionalDataSourceTest.kt b/paging/common/src/test/kotlin/androidx/paging/PositionalDataSourceTest.kt
index 5c449fe..475fc4f 100644
--- a/paging/common/src/test/kotlin/androidx/paging/PositionalDataSourceTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/PositionalDataSourceTest.kt
@@ -287,10 +287,6 @@
override fun onResult(data: List<A>, position: Int) {
callback.onResult(convert(data), position)
}
-
- override fun onError(error: Throwable) {
- callback.onError(error)
- }
})
}
@@ -299,10 +295,6 @@
override fun onResult(data: List<A>) {
callback.onResult(convert(data))
}
-
- override fun onError(error: Throwable) {
- callback.onError(error)
- }
})
}
@@ -321,7 +313,6 @@
override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback<T>) {
if (error) {
- callback.onError(ERROR)
error = false
return
}
@@ -338,7 +329,6 @@
override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<T>) {
if (error) {
- callback.onError(ERROR)
error = false
return
}
@@ -368,7 +358,6 @@
// load initial - error
orig.enqueueError()
wrapper.loadInitial(initParams, loadInitialCallback)
- verify(loadInitialCallback).onError(ERROR)
verifyNoMoreInteractions(loadInitialCallback)
// load range
@@ -380,7 +369,6 @@
// load range - error
orig.enqueueError()
wrapper.loadRange(PositionalDataSource.LoadRangeParams(2, 3), loadRangeCallback)
- verify(loadRangeCallback).onError(ERROR)
verifyNoMoreInteractions(loadRangeCallback)
// check invalidation behavior
diff --git a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/ItemDataSource.kt b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/ItemDataSource.kt
index cc831a3..44b20ff 100644
--- a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/ItemDataSource.kt
+++ b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/ItemDataSource.kt
@@ -18,22 +18,30 @@
import android.graphics.Color
import androidx.annotation.ColorInt
-import androidx.paging.PositionalDataSource
+import androidx.paging.PagedSource
import java.util.ArrayList
import java.util.concurrent.atomic.AtomicBoolean
val dataSourceError = AtomicBoolean(false)
+
/**
* Sample data source with artificial data.
*/
-internal class ItemDataSource : PositionalDataSource<Item>() {
+internal class ItemDataSource : PagedSource<Int, Item>() {
+ override val keyProvider = KeyProvider.Positional<Item>()
+
+ override suspend fun load(params: LoadParams<Int>) = when (params.loadType) {
+ LoadType.INITIAL -> loadInitial(params)
+ else -> loadRange(params)
+ }
+
class RetryableItemError : Exception()
private val mGenerationId = sGenerationId++
- private fun loadRangeInternal(startPosition: Int, loadCount: Int): List<Item>? {
+ private fun loadRangeInternal(startPosition: Int, loadCount: Int): List<Item> {
val items = ArrayList<Item>()
- val end = Math.min(COUNT, startPosition + loadCount)
+ val end = minOf(COUNT, startPosition + loadCount)
val bgColor = COLORS[mGenerationId % COLORS.size]
Thread.sleep(1000)
@@ -45,41 +53,68 @@
items.add(Item(i, "item $i", bgColor))
}
if (dataSourceError.compareAndSet(true, false)) {
- return null
+ throw RetryableItemError()
}
return items
}
+ override fun isRetryableError(error: Throwable): Boolean {
+ return error is RetryableItemError
+ }
+
companion object {
private const val COUNT = 60
@ColorInt
private val COLORS = intArrayOf(Color.RED, Color.BLUE, Color.BLACK)
-
private var sGenerationId: Int = 0
}
- override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback<Item>) {
- val position = computeInitialLoadPosition(params, COUNT)
- val loadSize = computeInitialLoadSize(params, position, COUNT)
+ // TODO: Clean up logic only pertinent to tiling.
+ private fun computeStartPosition(params: LoadParams<Int>): Int {
+ val requestedStartPosition = params.key?.let { key ->
+ var initialPosition = key
+
+ if (params.placeholdersEnabled) {
+ // snap load size to page multiple (minimum two)
+ val initialLoadSize = maxOf(params.loadSize / params.pageSize, 2) * params.pageSize
+
+ // move start so the load is centered around the key, not starting at it
+ val idealStart = initialPosition - initialLoadSize / 2
+ initialPosition = maxOf(0, idealStart / params.pageSize * params.pageSize)
+ } else {
+ // not tiled, so don't try to snap or force multiple of a page size
+ initialPosition -= params.loadSize / 2
+ }
+
+ initialPosition
+ } ?: 0
+
+ var pageStart = requestedStartPosition / params.pageSize * params.pageSize
+
+ // maximum start pos is that which will encompass end of list
+ val maximumLoadPage =
+ (COUNT - params.loadSize + params.pageSize - 1) / params.pageSize * params.pageSize
+ pageStart = minOf(maximumLoadPage, pageStart)
+
+ // minimum start position is 0
+ return maxOf(0, pageStart)
+ }
+
+ private fun loadInitial(params: LoadParams<Int>): LoadResult<Int, Item> {
+ val position = computeStartPosition(params)
+ val loadSize = minOf(COUNT - position, params.loadSize)
val data = loadRangeInternal(position, loadSize)
- if (data == null) {
- callback.onError(RetryableItemError())
- } else {
- callback.onResult(data, position, COUNT)
- }
+ return LoadResult(
+ itemsBefore = position,
+ itemsAfter = COUNT - data.size - position,
+ data = data,
+ offset = 0
+ )
}
- override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<Item>) {
- val data = loadRangeInternal(params.startPosition, params.loadSize)
- if (data == null) {
- callback.onError(RetryableItemError())
- } else {
- callback.onResult(data)
- }
- }
-
- override fun isRetryableError(error: Throwable): Boolean {
- return error is RetryableItemError
+ private fun loadRange(params: LoadParams<Int>): LoadResult<Int, Item> {
+ val data = loadRangeInternal(params.key ?: 0, params.loadSize)
+ return LoadResult(data = data, offset = 0)
}
}
diff --git a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/PagedListItemViewModel.java b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/PagedListItemViewModel.java
index 5f2c5f3..31263ea 100644
--- a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/PagedListItemViewModel.java
+++ b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/PagedListItemViewModel.java
@@ -16,12 +16,16 @@
package androidx.paging.integration.testapp.custom;
+import android.annotation.SuppressLint;
+
import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModel;
-import androidx.paging.DataSource;
import androidx.paging.LivePagedListBuilder;
import androidx.paging.PagedList;
+import androidx.paging.PagedSource;
+
+import kotlin.jvm.functions.Function0;
/**
* Sample ViewModel backed by an artificial data source
@@ -30,22 +34,22 @@
private ItemDataSource mDataSource;
private final Object mDataSourceLock = new Object();
- private final DataSource.Factory<Integer, Item> mFactory =
- new DataSource.Factory<Integer, Item>() {
- @NonNull
- @Override
- public DataSource<Integer, Item> create() {
- ItemDataSource newDataSource = new ItemDataSource();
- synchronized (mDataSourceLock) {
- mDataSource = newDataSource;
- return mDataSource;
- }
- }
- };
+ private final Function0<PagedSource<Integer, Item>> mFactory =
+ new Function0<PagedSource<Integer, Item>>() {
+ @SuppressLint("SyntheticAccessor")
+ @NonNull
+ @Override
+ public PagedSource<Integer, Item> invoke() {
+ ItemDataSource newDataSource = new ItemDataSource();
+ synchronized (mDataSourceLock) {
+ mDataSource = newDataSource;
+ return mDataSource;
+ }
+ }
+ };
private LiveData<PagedList<Item>> mLivePagedList =
- new LivePagedListBuilder<>(mFactory, 10)
- .build();
+ new LivePagedListBuilder<>(mFactory, 10).build();
void invalidateList() {
synchronized (mDataSourceLock) {
diff --git a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/room/RoomPagedListActivity.java b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/room/RoomPagedListActivity.java
index 9f3d041..501ec82 100644
--- a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/room/RoomPagedListActivity.java
+++ b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/room/RoomPagedListActivity.java
@@ -43,8 +43,7 @@
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_room_recycler_view);
// TODO use by viewModels() once this class switches to Kotlin
- final CustomerViewModel viewModel = new ViewModelProvider(this,
- ViewModelProvider.AndroidViewModelFactory.getInstance(getApplication()))
+ final CustomerViewModel viewModel = new ViewModelProvider(this)
.get(CustomerViewModel.class);
mRecyclerView = findViewById(R.id.recyclerview);
diff --git a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/room/RoomPagedListRxActivity.java b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/room/RoomPagedListRxActivity.java
index 54fe3bf..359c9b9 100644
--- a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/room/RoomPagedListRxActivity.java
+++ b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/room/RoomPagedListRxActivity.java
@@ -41,8 +41,7 @@
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_recycler_view);
// TODO use by viewModels() once this class switches to Kotlin
- mViewModel = new ViewModelProvider(this,
- ViewModelProvider.AndroidViewModelFactory.getInstance(getApplication()))
+ mViewModel = new ViewModelProvider(this)
.get(CustomerViewModel.class);
RecyclerView recyclerView = findViewById(R.id.recyclerview);
diff --git a/paging/runtime/api/3.0.0-alpha01.txt b/paging/runtime/api/3.0.0-alpha01.txt
index b5b45fe..f035ad8 100644
--- a/paging/runtime/api/3.0.0-alpha01.txt
+++ b/paging/runtime/api/3.0.0-alpha01.txt
@@ -28,8 +28,10 @@
}
public final class LivePagedListBuilder<Key, Value> {
- ctor public LivePagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, androidx.paging.PagedList.Config config);
- ctor public LivePagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, int pageSize);
+ ctor @Deprecated public LivePagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, androidx.paging.PagedList.Config config);
+ ctor @Deprecated public LivePagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, int pageSize);
+ ctor public LivePagedListBuilder(kotlin.jvm.functions.Function0<? extends androidx.paging.PagedSource<Key,Value>> pagedSourceFactory, androidx.paging.PagedList.Config config);
+ ctor public LivePagedListBuilder(kotlin.jvm.functions.Function0<? extends androidx.paging.PagedSource<Key,Value>> pagedSourceFactory, int pageSize);
method public androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> build();
method public androidx.paging.LivePagedListBuilder<Key,Value> setBoundaryCallback(androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback);
method public androidx.paging.LivePagedListBuilder<Key,Value> setCoroutineScope(kotlinx.coroutines.CoroutineScope coroutineScope);
@@ -39,8 +41,10 @@
public final class LivePagedListKt {
ctor public LivePagedListKt();
- method public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(androidx.paging.DataSource.Factory<Key,Value>, androidx.paging.PagedList.Config config, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, java.util.concurrent.Executor fetchExecutor = ArchTaskExecutor.getIOThreadExecutor());
- method public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(androidx.paging.DataSource.Factory<Key,Value>, int pageSize, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, java.util.concurrent.Executor fetchExecutor = ArchTaskExecutor.getIOThreadExecutor());
+ method @Deprecated public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(androidx.paging.DataSource.Factory<Key,Value>, androidx.paging.PagedList.Config config, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, java.util.concurrent.Executor fetchExecutor = ArchTaskExecutor.getIOThreadExecutor());
+ method @Deprecated public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(androidx.paging.DataSource.Factory<Key,Value>, int pageSize, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, java.util.concurrent.Executor fetchExecutor = ArchTaskExecutor.getIOThreadExecutor());
+ method public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(kotlin.jvm.functions.Function0<? extends androidx.paging.PagedSource<Key,Value>>, androidx.paging.PagedList.Config config, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, java.util.concurrent.Executor fetchExecutor = ArchTaskExecutor.getIOThreadExecutor());
+ method public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(kotlin.jvm.functions.Function0<? extends androidx.paging.PagedSource<Key,Value>>, int pageSize, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, java.util.concurrent.Executor fetchExecutor = ArchTaskExecutor.getIOThreadExecutor());
}
public abstract class PagedListAdapter<T, VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
diff --git a/paging/runtime/api/api_lint.ignore b/paging/runtime/api/api_lint.ignore
index 68bf03a..24e84aa 100644
--- a/paging/runtime/api/api_lint.ignore
+++ b/paging/runtime/api/api_lint.ignore
@@ -2,4 +2,4 @@
DocumentExceptions: androidx.paging.AsyncPagedListDiffer#getItem(int):
Method AsyncPagedListDiffer.getItem appears to be throwing java.lang.IndexOutOfBoundsException; this should be listed in the documentation; see https://android.github.io/kotlin-guides/interop.html#document-exceptions
DocumentExceptions: androidx.paging.AsyncPagedListDiffer#submitList(androidx.paging.PagedList<T>, Runnable):
- Method AsyncPagedListDiffer.submitList appears to be throwing java.lang.IllegalArgumentException; this should be listed in the documentation; see https://android.github.io/kotlin-guides/interop.html#document-exceptions
+ Method AsyncPagedListDiffer.submitList appears to be throwing java.lang.IllegalStateException; this should be listed in the documentation; see https://android.github.io/kotlin-guides/interop.html#document-exceptions
diff --git a/paging/runtime/api/current.txt b/paging/runtime/api/current.txt
index b5b45fe..f035ad8 100644
--- a/paging/runtime/api/current.txt
+++ b/paging/runtime/api/current.txt
@@ -28,8 +28,10 @@
}
public final class LivePagedListBuilder<Key, Value> {
- ctor public LivePagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, androidx.paging.PagedList.Config config);
- ctor public LivePagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, int pageSize);
+ ctor @Deprecated public LivePagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, androidx.paging.PagedList.Config config);
+ ctor @Deprecated public LivePagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, int pageSize);
+ ctor public LivePagedListBuilder(kotlin.jvm.functions.Function0<? extends androidx.paging.PagedSource<Key,Value>> pagedSourceFactory, androidx.paging.PagedList.Config config);
+ ctor public LivePagedListBuilder(kotlin.jvm.functions.Function0<? extends androidx.paging.PagedSource<Key,Value>> pagedSourceFactory, int pageSize);
method public androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> build();
method public androidx.paging.LivePagedListBuilder<Key,Value> setBoundaryCallback(androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback);
method public androidx.paging.LivePagedListBuilder<Key,Value> setCoroutineScope(kotlinx.coroutines.CoroutineScope coroutineScope);
@@ -39,8 +41,10 @@
public final class LivePagedListKt {
ctor public LivePagedListKt();
- method public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(androidx.paging.DataSource.Factory<Key,Value>, androidx.paging.PagedList.Config config, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, java.util.concurrent.Executor fetchExecutor = ArchTaskExecutor.getIOThreadExecutor());
- method public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(androidx.paging.DataSource.Factory<Key,Value>, int pageSize, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, java.util.concurrent.Executor fetchExecutor = ArchTaskExecutor.getIOThreadExecutor());
+ method @Deprecated public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(androidx.paging.DataSource.Factory<Key,Value>, androidx.paging.PagedList.Config config, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, java.util.concurrent.Executor fetchExecutor = ArchTaskExecutor.getIOThreadExecutor());
+ method @Deprecated public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(androidx.paging.DataSource.Factory<Key,Value>, int pageSize, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, java.util.concurrent.Executor fetchExecutor = ArchTaskExecutor.getIOThreadExecutor());
+ method public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(kotlin.jvm.functions.Function0<? extends androidx.paging.PagedSource<Key,Value>>, androidx.paging.PagedList.Config config, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, java.util.concurrent.Executor fetchExecutor = ArchTaskExecutor.getIOThreadExecutor());
+ method public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(kotlin.jvm.functions.Function0<? extends androidx.paging.PagedSource<Key,Value>>, int pageSize, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, java.util.concurrent.Executor fetchExecutor = ArchTaskExecutor.getIOThreadExecutor());
}
public abstract class PagedListAdapter<T, VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
diff --git a/paging/runtime/api/restricted_3.0.0-alpha01.txt b/paging/runtime/api/restricted_3.0.0-alpha01.txt
index b5b45fe..f035ad8 100644
--- a/paging/runtime/api/restricted_3.0.0-alpha01.txt
+++ b/paging/runtime/api/restricted_3.0.0-alpha01.txt
@@ -28,8 +28,10 @@
}
public final class LivePagedListBuilder<Key, Value> {
- ctor public LivePagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, androidx.paging.PagedList.Config config);
- ctor public LivePagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, int pageSize);
+ ctor @Deprecated public LivePagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, androidx.paging.PagedList.Config config);
+ ctor @Deprecated public LivePagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, int pageSize);
+ ctor public LivePagedListBuilder(kotlin.jvm.functions.Function0<? extends androidx.paging.PagedSource<Key,Value>> pagedSourceFactory, androidx.paging.PagedList.Config config);
+ ctor public LivePagedListBuilder(kotlin.jvm.functions.Function0<? extends androidx.paging.PagedSource<Key,Value>> pagedSourceFactory, int pageSize);
method public androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> build();
method public androidx.paging.LivePagedListBuilder<Key,Value> setBoundaryCallback(androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback);
method public androidx.paging.LivePagedListBuilder<Key,Value> setCoroutineScope(kotlinx.coroutines.CoroutineScope coroutineScope);
@@ -39,8 +41,10 @@
public final class LivePagedListKt {
ctor public LivePagedListKt();
- method public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(androidx.paging.DataSource.Factory<Key,Value>, androidx.paging.PagedList.Config config, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, java.util.concurrent.Executor fetchExecutor = ArchTaskExecutor.getIOThreadExecutor());
- method public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(androidx.paging.DataSource.Factory<Key,Value>, int pageSize, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, java.util.concurrent.Executor fetchExecutor = ArchTaskExecutor.getIOThreadExecutor());
+ method @Deprecated public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(androidx.paging.DataSource.Factory<Key,Value>, androidx.paging.PagedList.Config config, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, java.util.concurrent.Executor fetchExecutor = ArchTaskExecutor.getIOThreadExecutor());
+ method @Deprecated public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(androidx.paging.DataSource.Factory<Key,Value>, int pageSize, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, java.util.concurrent.Executor fetchExecutor = ArchTaskExecutor.getIOThreadExecutor());
+ method public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(kotlin.jvm.functions.Function0<? extends androidx.paging.PagedSource<Key,Value>>, androidx.paging.PagedList.Config config, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, java.util.concurrent.Executor fetchExecutor = ArchTaskExecutor.getIOThreadExecutor());
+ method public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(kotlin.jvm.functions.Function0<? extends androidx.paging.PagedSource<Key,Value>>, int pageSize, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, java.util.concurrent.Executor fetchExecutor = ArchTaskExecutor.getIOThreadExecutor());
}
public abstract class PagedListAdapter<T, VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
diff --git a/paging/runtime/api/restricted_current.txt b/paging/runtime/api/restricted_current.txt
index b5b45fe..f035ad8 100644
--- a/paging/runtime/api/restricted_current.txt
+++ b/paging/runtime/api/restricted_current.txt
@@ -28,8 +28,10 @@
}
public final class LivePagedListBuilder<Key, Value> {
- ctor public LivePagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, androidx.paging.PagedList.Config config);
- ctor public LivePagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, int pageSize);
+ ctor @Deprecated public LivePagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, androidx.paging.PagedList.Config config);
+ ctor @Deprecated public LivePagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, int pageSize);
+ ctor public LivePagedListBuilder(kotlin.jvm.functions.Function0<? extends androidx.paging.PagedSource<Key,Value>> pagedSourceFactory, androidx.paging.PagedList.Config config);
+ ctor public LivePagedListBuilder(kotlin.jvm.functions.Function0<? extends androidx.paging.PagedSource<Key,Value>> pagedSourceFactory, int pageSize);
method public androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> build();
method public androidx.paging.LivePagedListBuilder<Key,Value> setBoundaryCallback(androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback);
method public androidx.paging.LivePagedListBuilder<Key,Value> setCoroutineScope(kotlinx.coroutines.CoroutineScope coroutineScope);
@@ -39,8 +41,10 @@
public final class LivePagedListKt {
ctor public LivePagedListKt();
- method public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(androidx.paging.DataSource.Factory<Key,Value>, androidx.paging.PagedList.Config config, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, java.util.concurrent.Executor fetchExecutor = ArchTaskExecutor.getIOThreadExecutor());
- method public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(androidx.paging.DataSource.Factory<Key,Value>, int pageSize, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, java.util.concurrent.Executor fetchExecutor = ArchTaskExecutor.getIOThreadExecutor());
+ method @Deprecated public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(androidx.paging.DataSource.Factory<Key,Value>, androidx.paging.PagedList.Config config, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, java.util.concurrent.Executor fetchExecutor = ArchTaskExecutor.getIOThreadExecutor());
+ method @Deprecated public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(androidx.paging.DataSource.Factory<Key,Value>, int pageSize, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, java.util.concurrent.Executor fetchExecutor = ArchTaskExecutor.getIOThreadExecutor());
+ method public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(kotlin.jvm.functions.Function0<? extends androidx.paging.PagedSource<Key,Value>>, androidx.paging.PagedList.Config config, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, java.util.concurrent.Executor fetchExecutor = ArchTaskExecutor.getIOThreadExecutor());
+ method public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(kotlin.jvm.functions.Function0<? extends androidx.paging.PagedSource<Key,Value>>, int pageSize, Key? initialLoadKey = null, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, java.util.concurrent.Executor fetchExecutor = ArchTaskExecutor.getIOThreadExecutor());
}
public abstract class PagedListAdapter<T, VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
diff --git a/paging/runtime/src/androidTest/java/androidx/paging/LivePagedListBuilderTest.kt b/paging/runtime/src/androidTest/java/androidx/paging/LivePagedListBuilderTest.kt
index c60ab6f..d10028a 100644
--- a/paging/runtime/src/androidTest/java/androidx/paging/LivePagedListBuilderTest.kt
+++ b/paging/runtime/src/androidTest/java/androidx/paging/LivePagedListBuilderTest.kt
@@ -80,9 +80,9 @@
ArchTaskExecutor.getInstance().setDelegate(null)
}
- class MockDataSourceFactory : DataSource.Factory<Int, String>() {
- override fun create(): DataSource<Int, String> {
- return MockDataSource()
+ class MockDataSourceFactory {
+ fun create(): PagedSource<Int, String> {
+ return MockPagedSource()
}
var throwable: Throwable? = null
@@ -91,28 +91,35 @@
throwable = RETRYABLE_EXCEPTION
}
- private inner class MockDataSource : PositionalDataSource<String>() {
- override fun loadInitial(
- params: LoadInitialParams,
- callback: LoadInitialCallback<String>
- ) {
+ private inner class MockPagedSource : PagedSource<Int, String>() {
+ override val keyProvider = KeyProvider.Positional<String>()
+
+ override suspend fun load(params: LoadParams<Int>) = when (params.loadType) {
+ LoadType.INITIAL -> loadInitial(params)
+ else -> loadRange()
+ }
+
+ override fun isRetryableError(error: Throwable) = error === RETRYABLE_EXCEPTION
+
+ private fun loadInitial(params: LoadParams<Int>): LoadResult<Int, String> {
assertEquals(2, params.pageSize)
- if (throwable != null) {
-
- callback.onError(throwable!!)
+ throwable?.let { error ->
throwable = null
- } else {
- callback.onResult(listOf("a", "b"), 0, 4)
+ throw error
}
+
+ val data = listOf("a", "b")
+ return LoadResult(
+ itemsBefore = 0,
+ itemsAfter = 4 - data.size,
+ data = data,
+ offset = 0
+ )
}
- override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<String>) {
- callback.onResult(listOf("c", "d"))
- }
-
- override fun isRetryableError(error: Throwable): Boolean {
- return error === RETRYABLE_EXCEPTION
+ private fun loadRange(): LoadResult<Int, String> {
+ return LoadResult(data = listOf("c", "d"), offset = 0)
}
}
}
@@ -121,7 +128,7 @@
fun executorBehavior() {
// specify a background executor via builder, and verify it gets used for all loads,
// overriding default arch IO executor
- val livePagedList = LivePagedListBuilder(MockDataSourceFactory(), 2)
+ val livePagedList = LivePagedListBuilder(MockDataSourceFactory()::create, 2)
.setFetchExecutor(backgroundExecutor)
.build()
@@ -160,7 +167,7 @@
val factory = MockDataSourceFactory()
factory.enqueueRetryableError()
- val livePagedList = LivePagedListBuilder(factory, 2)
+ val livePagedList = LivePagedListBuilder(factory::create, 2)
.setFetchExecutor(backgroundExecutor)
.build()
diff --git a/paging/runtime/src/androidTest/java/androidx/paging/LivePagedListTest.kt b/paging/runtime/src/androidTest/java/androidx/paging/LivePagedListTest.kt
index 1d48562..995d1a8 100644
--- a/paging/runtime/src/androidTest/java/androidx/paging/LivePagedListTest.kt
+++ b/paging/runtime/src/androidTest/java/androidx/paging/LivePagedListTest.kt
@@ -33,7 +33,8 @@
val instantTaskExecutorRule = InstantTaskExecutorRule()
@Test
- fun toLiveData_config() {
+ fun toLiveData_dataSourceConfig() {
+ @Suppress("DEPRECATION")
val livePagedList = dataSourceFactory.toLiveData(config)
livePagedList.observeForever {}
assertNotNull(livePagedList.value)
@@ -41,13 +42,31 @@
}
@Test
- fun toLiveData_pageSize() {
+ fun toLiveData_dataSourcePageSize() {
+ @Suppress("DEPRECATION")
val livePagedList = dataSourceFactory.toLiveData(24)
livePagedList.observeForever {}
assertNotNull(livePagedList.value)
assertEquals(24, livePagedList.value!!.config.pageSize)
}
+ @Test
+ fun toLiveData_pagedSourceConfig() {
+ @Suppress("DEPRECATION")
+ val livePagedList = pagedSourceFactory.toLiveData(config)
+ livePagedList.observeForever {}
+ assertNotNull(livePagedList.value)
+ assertEquals(config, livePagedList.value!!.config)
+ }
+
+ @Test
+ fun toLiveData_pagedSourcePageSize() {
+ val livePagedList = pagedSourceFactory.toLiveData(24)
+ livePagedList.observeForever {}
+ assertNotNull(livePagedList.value)
+ assertEquals(24, livePagedList.value!!.config.pageSize)
+ }
+
companion object {
private val dataSource = object : PositionalDataSource<String>() {
override fun loadInitial(
@@ -66,6 +85,10 @@
}
}
+ private val pagedSource = PagedSourceWrapper(dataSource)
+
+ private val pagedSourceFactory = { pagedSource }
+
private val config = Config(10)
}
}
diff --git a/paging/runtime/src/androidTest/java/androidx/paging/StringPagedList.kt b/paging/runtime/src/androidTest/java/androidx/paging/StringPagedList.kt
index e626f99..20c42d4 100644
--- a/paging/runtime/src/androidTest/java/androidx/paging/StringPagedList.kt
+++ b/paging/runtime/src/androidTest/java/androidx/paging/StringPagedList.kt
@@ -44,8 +44,6 @@
)
}
- override val isContiguous = true
-
override val lastKey: Any? = null
override val isDetached
diff --git a/paging/runtime/src/main/java/androidx/paging/AsyncPagedListDiffer.kt b/paging/runtime/src/main/java/androidx/paging/AsyncPagedListDiffer.kt
index 9f737ba..39754e2 100644
--- a/paging/runtime/src/main/java/androidx/paging/AsyncPagedListDiffer.kt
+++ b/paging/runtime/src/main/java/androidx/paging/AsyncPagedListDiffer.kt
@@ -68,7 +68,7 @@
* @Override
* public void onCreate(Bundle savedState) {
* super.onCreate(savedState);
- * MyViewModel viewModel = ViewModelProviders.of(this).get(MyViewModel.class);
+ * MyViewModel viewModel = new ViewModelProvider(this).get(MyViewModel.class);
* RecyclerView recyclerView = findViewById(R.id.user_list);
* final UserAdapter adapter = new UserAdapter();
* viewModel.usersList.observe(this, pagedList -> adapter.submitList(pagedList));
@@ -131,7 +131,6 @@
@VisibleForTesting
internal val listeners = CopyOnWriteArrayList<PagedListListener<T>>()
- private var isContiguous: Boolean = false
private var pagedList: PagedList<T>? = null
private var snapshot: PagedList<T>? = null
@@ -282,16 +281,6 @@
* it is committed.
*/
open fun submitList(pagedList: PagedList<T>?, commitCallback: Runnable?) {
- if (pagedList != null) {
- if (currentList == null) {
- isContiguous = pagedList.isContiguous
- } else if (pagedList.isContiguous != isContiguous) {
- throw IllegalArgumentException(
- "AsyncPagedListDiffer cannot handle both contiguous and non-contiguous lists."
- )
- }
- }
-
// incrementing generation means any currently-running diffs are discarded when they finish
val runGeneration = ++maxScheduledGeneration
diff --git a/paging/runtime/src/main/java/androidx/paging/LivePagedList.kt b/paging/runtime/src/main/java/androidx/paging/LivePagedList.kt
index 95da82c..ce6fcde6d 100644
--- a/paging/runtime/src/main/java/androidx/paging/LivePagedList.kt
+++ b/paging/runtime/src/main/java/androidx/paging/LivePagedList.kt
@@ -30,7 +30,7 @@
initialKey: Key?,
private val config: PagedList.Config,
private val boundaryCallback: PagedList.BoundaryCallback<Value>?,
- private val dataSourceFactory: DataSource.Factory<Key, Value>,
+ private val pagedSourceFactory: PagedSourceFactory<Key, Value>,
private val notifyExecutor: Executor,
private val fetchExecutor: Executor
) : LiveData<PagedList<Value>>() {
@@ -43,7 +43,7 @@
init {
currentData = InitialPagedList(
- PagedSourceWrapper(dataSourceFactory.create()),
+ pagedSourceFactory(),
coroutineScope,
config,
initialKey
@@ -97,16 +97,15 @@
}
private suspend fun createPagedList(): PagedList<Value> {
- val dataSource = dataSourceFactory.create()
- @Suppress("DEPRECATION")
- currentData.dataSource.removeInvalidatedCallback(callback)
- dataSource.addInvalidatedCallback(callback)
+ val pagedSource = pagedSourceFactory()
+ currentData.pagedSource.unregisterInvalidatedCallback(callback)
+ pagedSource.registerInvalidatedCallback(callback)
currentData.setInitialLoadState(PagedList.LoadState.LOADING, null)
@Suppress("UNCHECKED_CAST") // getLastKey guaranteed to be of 'Key' type
val lastKey = currentData.lastKey as Key?
return PagedList.create(
- PagedSourceWrapper(dataSource),
+ pagedSource,
coroutineScope,
notifyExecutor,
fetchExecutor,
@@ -132,12 +131,14 @@
*
* @see LivePagedListBuilder
*/
+@Deprecated("DataSource is deprecated and has been replaced by PagedSource")
fun <Key : Any, Value : Any> DataSource.Factory<Key, Value>.toLiveData(
config: PagedList.Config,
initialLoadKey: Key? = null,
boundaryCallback: PagedList.BoundaryCallback<Value>? = null,
fetchExecutor: Executor = ArchTaskExecutor.getIOThreadExecutor()
): LiveData<PagedList<Value>> {
+ @Suppress("DEPRECATION")
return LivePagedListBuilder(this, config)
.setInitialLoadKey(initialLoadKey)
.setBoundaryCallback(boundaryCallback)
@@ -159,12 +160,68 @@
*
* @see LivePagedListBuilder
*/
+@Deprecated("DataSource is deprecated and has been replaced by PagedSource")
fun <Key : Any, Value : Any> DataSource.Factory<Key, Value>.toLiveData(
pageSize: Int,
initialLoadKey: Key? = null,
boundaryCallback: PagedList.BoundaryCallback<Value>? = null,
fetchExecutor: Executor = ArchTaskExecutor.getIOThreadExecutor()
): LiveData<PagedList<Value>> {
+ @Suppress("DEPRECATION")
+ return LivePagedListBuilder(this, Config(pageSize))
+ .setInitialLoadKey(initialLoadKey)
+ .setBoundaryCallback(boundaryCallback)
+ .setFetchExecutor(fetchExecutor)
+ .build()
+}
+
+/**
+ * Constructs a `LiveData<PagedList>`, from this [PagedSourceFactory], convenience for
+ * [LivePagedListBuilder].
+ *
+ * No work (such as loading) is done immediately, the creation of the first PagedList is is
+ * deferred until the LiveData is observed.
+ *
+ * @param config Paging configuration.
+ * @param initialLoadKey Initial load key passed to the first PagedList/PagedSource.
+ * @param boundaryCallback The boundary callback for listening to PagedList load state.
+ * @param fetchExecutor Executor for fetching data from PagedSources.
+ *
+ * @see LivePagedListBuilder
+ */
+fun <Key : Any, Value : Any> PagedSourceFactory<Key, Value>.toLiveData(
+ config: PagedList.Config,
+ initialLoadKey: Key? = null,
+ boundaryCallback: PagedList.BoundaryCallback<Value>? = null,
+ fetchExecutor: Executor = ArchTaskExecutor.getIOThreadExecutor()
+): LiveData<PagedList<Value>> {
+ return LivePagedListBuilder(this, config)
+ .setInitialLoadKey(initialLoadKey)
+ .setBoundaryCallback(boundaryCallback)
+ .setFetchExecutor(fetchExecutor)
+ .build()
+}
+
+/**
+ * Constructs a `LiveData<PagedList>`, from this [PagedSourceFactory], convenience for
+ * [LivePagedListBuilder].
+ *
+ * No work (such as loading) is done immediately, the creation of the first PagedList is is
+ * deferred until the LiveData is observed.
+ *
+ * @param pageSize Page size.
+ * @param initialLoadKey Initial load key passed to the first PagedList/PagedSource.
+ * @param boundaryCallback The boundary callback for listening to PagedList load state.
+ * @param fetchExecutor Executor for fetching data from PagedSources.
+ *
+ * @see LivePagedListBuilder
+ */
+fun <Key : Any, Value : Any> PagedSourceFactory<Key, Value>.toLiveData(
+ pageSize: Int,
+ initialLoadKey: Key? = null,
+ boundaryCallback: PagedList.BoundaryCallback<Value>? = null,
+ fetchExecutor: Executor = ArchTaskExecutor.getIOThreadExecutor()
+): LiveData<PagedList<Value>> {
return LivePagedListBuilder(this, Config(pageSize))
.setInitialLoadKey(initialLoadKey)
.setBoundaryCallback(boundaryCallback)
diff --git a/paging/runtime/src/main/java/androidx/paging/LivePagedListBuilder.kt b/paging/runtime/src/main/java/androidx/paging/LivePagedListBuilder.kt
index afd1183..ae1c147 100644
--- a/paging/runtime/src/main/java/androidx/paging/LivePagedListBuilder.kt
+++ b/paging/runtime/src/main/java/androidx/paging/LivePagedListBuilder.kt
@@ -29,25 +29,32 @@
* The required parameters are in the constructor, so you can simply construct and build, or
* optionally enable extra features (such as initial load key, or BoundaryCallback).
*
- * @param Key Type of input valued used to load data from the DataSource. Must be integer if you're
- * using PositionalDataSource.
+ * @param Key Type of input valued used to load data from the [DataSource]. Must be integer if
+ * you're using [PositionalDataSource].
* @param Value Item type being presented.
- *
- * @constructor Creates a LivePagedListBuilder with required parameters.
- * @param dataSourceFactory DataSource factory providing DataSource generations.
- * @param config Paging configuration.
*/
-class LivePagedListBuilder<Key : Any, Value : Any>(
- private val dataSourceFactory: DataSource.Factory<Key, Value>,
+class LivePagedListBuilder<Key : Any, Value : Any> {
+ private val pagedSourceFactory: PagedSourceFactory<Key, Value>
private val config: PagedList.Config
-) {
private var coroutineScope: CoroutineScope = GlobalScope
private var initialLoadKey: Key? = null
private var boundaryCallback: PagedList.BoundaryCallback<Value>? = null
private var fetchExecutor = ArchTaskExecutor.getIOThreadExecutor()
/**
- * Creates a LivePagedListBuilder with required parameters.
+ * Creates a [LivePagedListBuilder] with required parameters.
+ *
+ * @param dataSourceFactory [DataSource] factory providing DataSource generations.
+ * @param config Paging configuration.
+ */
+ @Deprecated("DataSource is deprecated and has been replaced by PagedSource")
+ constructor(dataSourceFactory: DataSource.Factory<Key, Value>, config: PagedList.Config) {
+ this.pagedSourceFactory = { PagedSourceWrapper(dataSourceFactory.create()) }
+ this.config = config
+ }
+
+ /**
+ * Creates a [LivePagedListBuilder] with required parameters.
*
* This method is a convenience for:
* ```
@@ -58,12 +65,58 @@
* @param dataSourceFactory [DataSource.Factory] providing DataSource generations.
* @param pageSize Size of pages to load.
*/
+ @Suppress("DEPRECATION")
+ @Deprecated("DataSource is deprecated and has been replaced by PagedSource")
constructor(dataSourceFactory: DataSource.Factory<Key, Value>, pageSize: Int) : this(
dataSourceFactory,
PagedList.Config.Builder().setPageSize(pageSize).build()
)
/**
+ * Creates a [LivePagedListBuilder] with required parameters.
+ *
+ * @param pagedSourceFactory [PagedSource] factory providing [PagedSource] generations.
+ *
+ * The returned [PagedSource] should invalidate itself if the snapshot is no longer valid. If a
+ * [PagedSource] becomes invalid, the only way to query more data is to create a new
+ * [PagedSource] by invoking the supplied [pagedSourceFactory].
+ *
+ * [pagedSourceFactory] will invoked to construct a new [PagedList] and [PagedSource] when the
+ * current [PagedSource] is invalidated, and pass the new [PagedList] through the
+ * `LiveData<PagedList>` to observers.
+ * @param config Paging configuration.
+ */
+ constructor(pagedSourceFactory: PagedSourceFactory<Key, Value>, config: PagedList.Config) {
+ this.pagedSourceFactory = pagedSourceFactory
+ this.config = config
+ }
+
+ /**
+ * Creates a [LivePagedListBuilder] with required parameters.
+ *
+ * This method is a convenience for:
+ * ```
+ * LivePagedListBuilder(pagedSourceFactory,
+ * new PagedList.Config.Builder().setPageSize(pageSize).build())
+ * ```
+ *
+ * @param pagedSourceFactory [PagedSource] factory providing [PagedSource] generations.
+ *
+ * The returned [PagedSource] should invalidate itself if the snapshot is no longer valid. If a
+ * [PagedSource] becomes invalid, the only way to query more data is to create a new
+ * [PagedSource] by invoking the supplied [pagedSourceFactory].
+ *
+ * [pagedSourceFactory] will invoked to construct a new [PagedList] and [PagedSource] when the
+ * current [PagedSource] is invalidated, and pass the new [PagedList] through the
+ * `LiveData<PagedList>` to observers.
+ * @param pageSize Size of pages to load.
+ */
+ constructor(pagedSourceFactory: PagedSourceFactory<Key, Value>, pageSize: Int) : this(
+ pagedSourceFactory,
+ PagedList.Config.Builder().setPageSize(pageSize).build()
+ )
+
+ /**
* Set the [CoroutineScope] that page loads should be launched within. The set [coroutineScope]
* allows a [PagedSource] to cancel running load operations when the results are no longer
* needed - for example, when the containing activity is destroyed.
@@ -141,7 +194,7 @@
initialLoadKey,
config,
boundaryCallback,
- dataSourceFactory,
+ pagedSourceFactory,
ArchTaskExecutor.getMainThreadExecutor(),
fetchExecutor
)
diff --git a/paging/runtime/src/main/java/androidx/paging/PagedListAdapter.kt b/paging/runtime/src/main/java/androidx/paging/PagedListAdapter.kt
index e3849ad..a40db41 100644
--- a/paging/runtime/src/main/java/androidx/paging/PagedListAdapter.kt
+++ b/paging/runtime/src/main/java/androidx/paging/PagedListAdapter.kt
@@ -59,7 +59,7 @@
* @Override
* public void onCreate(Bundle savedState) {
* super.onCreate(savedState);
- * MyViewModel viewModel = ViewModelProviders.of(this).get(MyViewModel.class);
+ * MyViewModel viewModel = new ViewModelProvider(this).get(MyViewModel.class);
* RecyclerView recyclerView = findViewById(R.id.user_list);
* UserAdapter<User> adapter = new UserAdapter();
* viewModel.usersList.observe(this, pagedList -> adapter.submitList(pagedList));
diff --git a/recyclerview/recyclerview-benchmark/build.gradle b/recyclerview/recyclerview-benchmark/build.gradle
index 87a0edb..8b18e14 100644
--- a/recyclerview/recyclerview-benchmark/build.gradle
+++ b/recyclerview/recyclerview-benchmark/build.gradle
@@ -19,12 +19,13 @@
id("AndroidXPlugin")
id("com.android.library")
id("kotlin-android")
+ id("androidx.benchmark")
}
dependencies {
androidTestImplementation(project(":appcompat"))
androidTestImplementation(project(":recyclerview:recyclerview"))
- androidTestImplementation(project(":benchmark"))
+ androidTestImplementation(project(":benchmark:benchmark-junit4"))
androidTestImplementation(JUNIT)
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
androidTestImplementation(ANDROIDX_TEST_CORE)
diff --git a/recyclerview/recyclerview-benchmark/src/androidTest/java/androidx/recyclerview/benchmark/ScrollBenchmark.kt b/recyclerview/recyclerview-benchmark/src/androidTest/java/androidx/recyclerview/benchmark/ScrollBenchmark.kt
index 2dec083..8acf597 100644
--- a/recyclerview/recyclerview-benchmark/src/androidTest/java/androidx/recyclerview/benchmark/ScrollBenchmark.kt
+++ b/recyclerview/recyclerview-benchmark/src/androidTest/java/androidx/recyclerview/benchmark/ScrollBenchmark.kt
@@ -20,8 +20,8 @@
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
-import androidx.benchmark.BenchmarkRule
-import androidx.benchmark.measureRepeated
+import androidx.benchmark.junit4.BenchmarkRule
+import androidx.benchmark.junit4.measureRepeated
import androidx.recyclerview.benchmark.test.R
import androidx.recyclerview.widget.RecyclerView
import androidx.test.annotation.UiThreadTest
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/LinearLayoutManagerExtraLayoutSpaceTest.java b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/LinearLayoutManagerExtraLayoutSpaceTest.java
index 40e4916..70b1225 100644
--- a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/LinearLayoutManagerExtraLayoutSpaceTest.java
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/LinearLayoutManagerExtraLayoutSpaceTest.java
@@ -304,7 +304,7 @@
mRecordExtraLayoutSpace = false;
mRecordedExtraLayoutSpace[0] = extraLayoutSpace[0];
mRecordedExtraLayoutSpace[1] = extraLayoutSpace[1];
- getViewTreeObserver().addOnDrawListener(mLayoutRecorder);
+ getViewTreeObserver().addOnPreDrawListener(mLayoutRecorder);
}
}
@@ -316,7 +316,7 @@
}
}
- class LayoutBoundsRecorder implements ViewTreeObserver.OnDrawListener {
+ class LayoutBoundsRecorder implements ViewTreeObserver.OnPreDrawListener {
private final OrientationHelper mHelper;
private final int[][] mBounds;
@@ -331,16 +331,17 @@
}
@Override
- public void onDraw() {
+ public boolean onPreDraw() {
if (!mHasRecorded) {
recordBounds();
mRecyclerView.post(new Runnable() {
@Override
public void run() {
- getViewTreeObserver().removeOnDrawListener(LayoutBoundsRecorder.this);
+ getViewTreeObserver().removeOnPreDrawListener(LayoutBoundsRecorder.this);
}
});
}
+ return true;
}
private void recordBounds() {
diff --git a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/AsyncListDiffer.java b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/AsyncListDiffer.java
index 1c345be03..c1fdb87 100644
--- a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/AsyncListDiffer.java
+++ b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/AsyncListDiffer.java
@@ -67,7 +67,7 @@
* {@literal @}Override
* public void onCreate(Bundle savedState) {
* super.onCreate(savedState);
- * MyViewModel viewModel = ViewModelProviders.of(this).get(MyViewModel.class);
+ * MyViewModel viewModel = new ViewModelProvider(this).get(MyViewModel.class);
* RecyclerView recyclerView = findViewById(R.id.user_list);
* UserAdapter adapter = new UserAdapter();
* viewModel.usersList.observe(this, list -> adapter.submitList(list));
diff --git a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/ListAdapter.java b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/ListAdapter.java
index d00e84c..6b1ad73 100644
--- a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/ListAdapter.java
+++ b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/ListAdapter.java
@@ -50,7 +50,7 @@
* {@literal @}Override
* public void onCreate(Bundle savedState) {
* super.onCreate(savedState);
- * MyViewModel viewModel = ViewModelProviders.of(this).get(MyViewModel.class);
+ * MyViewModel viewModel = new ViewModelProvider(this).get(MyViewModel.class);
* RecyclerView recyclerView = findViewById(R.id.user_list);
* UserAdapter<User> adapter = new UserAdapter();
* viewModel.usersList.observe(this, list -> adapter.submitList(list));
diff --git a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java
index 74cd10a..0d1f6d9 100644
--- a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java
+++ b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java
@@ -5140,11 +5140,13 @@
void dispatchOnScrolled(int hresult, int vresult) {
mDispatchScrollCounter++;
- // Pass the current scrollX/scrollY values; no actual change in these properties occurred
- // but some general-purpose code may choose to respond to changes this way.
+ // Pass the current scrollX/scrollY values as current values. No actual change in these
+ // properties occurred. Pass negative hresult and vresult as old values so that
+ // postSendViewScrolledAccessibilityEventCallback(l - oldl, t - oldt) in onScrollChanged
+ // sends the scrolled accessibility event correctly.
final int scrollX = getScrollX();
final int scrollY = getScrollY();
- onScrollChanged(scrollX, scrollY, scrollX, scrollY);
+ onScrollChanged(scrollX, scrollY, scrollX - hresult, scrollY - vresult);
// Pass the real deltas to onScrolled, the RecyclerView-specific method.
onScrolled(hresult, vresult);
@@ -11354,7 +11356,9 @@
@Override
public String toString() {
- final StringBuilder sb = new StringBuilder("ViewHolder{"
+ String className =
+ getClass().isAnonymousClass() ? "ViewHolder" : getClass().getSimpleName();
+ final StringBuilder sb = new StringBuilder(className + "{"
+ Integer.toHexString(hashCode()) + " position=" + mPosition + " id=" + mItemId
+ ", oldPos=" + mOldPosition + ", pLpos:" + mPreLayoutPosition);
if (isScrap()) {
diff --git a/room/benchmark/build.gradle b/room/benchmark/build.gradle
index 77c626c..b13c3b9 100644
--- a/room/benchmark/build.gradle
+++ b/room/benchmark/build.gradle
@@ -20,6 +20,7 @@
id("com.android.library")
id("kotlin-android")
id("kotlin-kapt")
+ id("androidx.benchmark")
}
dependencies {
@@ -30,7 +31,7 @@
androidTestImplementation(project(":sqlite:sqlite"))
androidTestImplementation(project(":sqlite:sqlite-framework"))
androidTestImplementation(ARCH_CORE_RUNTIME)
- androidTestImplementation(project(":benchmark"))
+ androidTestImplementation(project(":benchmark:benchmark-junit4"))
androidTestImplementation(RX_JAVA)
androidTestImplementation(JUNIT)
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
diff --git a/room/benchmark/src/androidTest/java/androidx/room/benchmark/InvalidationTrackerBenchmark.kt b/room/benchmark/src/androidTest/java/androidx/room/benchmark/InvalidationTrackerBenchmark.kt
index 7537d99..861da78 100644
--- a/room/benchmark/src/androidTest/java/androidx/room/benchmark/InvalidationTrackerBenchmark.kt
+++ b/room/benchmark/src/androidTest/java/androidx/room/benchmark/InvalidationTrackerBenchmark.kt
@@ -17,8 +17,8 @@
package androidx.room.benchmark
import android.os.Build
-import androidx.benchmark.BenchmarkRule
-import androidx.benchmark.measureRepeated
+import androidx.benchmark.junit4.BenchmarkRule
+import androidx.benchmark.junit4.measureRepeated
import androidx.room.Dao
import androidx.room.Database
import androidx.room.Entity
diff --git a/room/benchmark/src/androidTest/java/androidx/room/benchmark/RelationBenchmark.kt b/room/benchmark/src/androidTest/java/androidx/room/benchmark/RelationBenchmark.kt
index ac4c427..0fea703 100644
--- a/room/benchmark/src/androidTest/java/androidx/room/benchmark/RelationBenchmark.kt
+++ b/room/benchmark/src/androidTest/java/androidx/room/benchmark/RelationBenchmark.kt
@@ -17,8 +17,8 @@
package androidx.room.benchmark
import android.os.Build
-import androidx.benchmark.BenchmarkRule
-import androidx.benchmark.measureRepeated
+import androidx.benchmark.junit4.BenchmarkRule
+import androidx.benchmark.junit4.measureRepeated
import androidx.room.Dao
import androidx.room.Database
import androidx.room.Embedded
diff --git a/room/common/api/2.2.0-alpha02.ignore b/room/common/api/2.2.0-alpha02.ignore
index 64ce5cf..51b51d5 100644
--- a/room/common/api/2.2.0-alpha02.ignore
+++ b/room/common/api/2.2.0-alpha02.ignore
@@ -1,16 +1,4 @@
// Baseline format: 1.0
-AddedAbstractMethod: androidx.room.ColumnInfo#defaultValue():
- Added method androidx.room.ColumnInfo.defaultValue()
-AddedAbstractMethod: androidx.room.Delete#entity():
- Added method androidx.room.Delete.entity()
-AddedAbstractMethod: androidx.room.Insert#entity():
- Added method androidx.room.Insert.entity()
-AddedAbstractMethod: androidx.room.Relation#associateBy():
- Added method androidx.room.Relation.associateBy()
-AddedAbstractMethod: androidx.room.Update#entity():
- Added method androidx.room.Update.entity()
-
-
ChangedType: androidx.room.Database#entities():
Method androidx.room.Database.entities has changed return type from Class[] to Class<?>[]
ChangedType: androidx.room.Database#views():
diff --git a/room/compiler/src/main/kotlin/androidx/room/ext/javapoet_ext.kt b/room/compiler/src/main/kotlin/androidx/room/ext/javapoet_ext.kt
index 70e30ff..6e7a114 100644
--- a/room/compiler/src/main/kotlin/androidx/room/ext/javapoet_ext.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/ext/javapoet_ext.kt
@@ -165,6 +165,10 @@
val UNIT = ClassName.get("kotlin", "Unit")
val CONTINUATION = ClassName.get("kotlin.coroutines", "Continuation")
val COROUTINE_SCOPE = ClassName.get("kotlinx.coroutines", "CoroutineScope")
+ val CHANNEL = ClassName.get("kotlinx.coroutines.channels", "Channel")
+ val RECEIVE_CHANNEL = ClassName.get("kotlinx.coroutines.channels", "ReceiveChannel")
+ val SEND_CHANNEL = ClassName.get("kotlinx.coroutines.channels", "SendChannel")
+ val FLOW = ClassName.get("kotlinx.coroutines.flow", "Flow")
}
fun TypeName.defaultValue(): String {
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/FieldProcessor.kt b/room/compiler/src/main/kotlin/androidx/room/processor/FieldProcessor.kt
index 07d1f2c..5d04025 100644
--- a/room/compiler/src/main/kotlin/androidx/room/processor/FieldProcessor.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/FieldProcessor.kt
@@ -32,8 +32,8 @@
val containing: DeclaredType,
val element: Element,
val bindingScope: BindingScope,
- // pass only if this is processed as a child of Embedded field
- val fieldParent: EmbeddedField?
+ val fieldParent: EmbeddedField?, // pass only if this is processed as a child of Embedded field
+ val onBindingError: (field: Field, errorMsg: String) -> Unit
) {
val context = baseContext.fork(element)
fun process(): Field {
@@ -82,20 +82,23 @@
field.statementBinder = adapter
field.cursorValueReader = adapter
field.affinity = adapterAffinity
- context.checker.check(adapter != null, field.element,
- ProcessorErrors.CANNOT_FIND_COLUMN_TYPE_ADAPTER)
+ if (adapter == null) {
+ onBindingError(field, ProcessorErrors.CANNOT_FIND_COLUMN_TYPE_ADAPTER)
+ }
}
BindingScope.BIND_TO_STMT -> {
field.statementBinder = context.typeAdapterStore
.findStatementValueBinder(field.type, field.affinity)
- context.checker.check(field.statementBinder != null, field.element,
- ProcessorErrors.CANNOT_FIND_STMT_BINDER)
+ if (field.statementBinder == null) {
+ onBindingError(field, ProcessorErrors.CANNOT_FIND_STMT_BINDER)
+ }
}
BindingScope.READ_FROM_CURSOR -> {
field.cursorValueReader = context.typeAdapterStore
.findCursorValueReader(field.type, field.affinity)
- context.checker.check(field.cursorValueReader != null, field.element,
- ProcessorErrors.CANNOT_FIND_CURSOR_READER)
+ if (field.cursorValueReader == null) {
+ onBindingError(field, ProcessorErrors.CANNOT_FIND_CURSOR_READER)
+ }
}
}
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/FtsTableEntityProcessor.kt b/room/compiler/src/main/kotlin/androidx/room/processor/FtsTableEntityProcessor.kt
index 40869db..1059368 100644
--- a/room/compiler/src/main/kotlin/androidx/room/processor/FtsTableEntityProcessor.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/FtsTableEntityProcessor.kt
@@ -61,17 +61,14 @@
ProcessorErrors.ENTITY_MUST_BE_ANNOTATED_WITH_ENTITY)
val entityAnnotation = element.toAnnotationBox(androidx.room.Entity::class)
val tableName: String
- val ignoredColumns: Set<String>
if (entityAnnotation != null) {
tableName = extractTableName(element, entityAnnotation.value)
- ignoredColumns = entityAnnotation.value.ignoredColumns.toSet()
context.checker.check(extractIndices(entityAnnotation, tableName).isEmpty(),
element, ProcessorErrors.INDICES_IN_FTS_ENTITY)
context.checker.check(extractForeignKeys(entityAnnotation).isEmpty(),
element, ProcessorErrors.FOREIGN_KEYS_IN_FTS_ENTITY)
} else {
tableName = element.simpleName.toString()
- ignoredColumns = emptySet()
}
val pojo = PojoProcessor.createFor(
@@ -79,8 +76,7 @@
element = element,
bindingScope = FieldProcessor.BindingScope.TWO_WAY,
parent = null,
- referenceStack = referenceStack,
- ignoredColumns = ignoredColumns).process()
+ referenceStack = referenceStack).process()
context.checker.check(pojo.relations.isEmpty(), element, ProcessorErrors.RELATION_IN_ENTITY)
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/PojoProcessor.kt b/room/compiler/src/main/kotlin/androidx/room/processor/PojoProcessor.kt
index efebfac..235a457 100644
--- a/room/compiler/src/main/kotlin/androidx/room/processor/PojoProcessor.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/PojoProcessor.kt
@@ -79,7 +79,6 @@
val bindingScope: FieldProcessor.BindingScope,
val parent: EmbeddedField?,
val referenceStack: LinkedHashSet<Name> = LinkedHashSet(),
- val ignoredColumns: Set<String>,
private val delegate: Delegate
) {
val context = baseContext.fork(element)
@@ -97,8 +96,7 @@
element: TypeElement,
bindingScope: FieldProcessor.BindingScope,
parent: EmbeddedField?,
- referenceStack: LinkedHashSet<Name> = LinkedHashSet(),
- ignoredColumns: Set<String> = emptySet()
+ referenceStack: LinkedHashSet<Name> = LinkedHashSet()
): PojoProcessor {
val (pojoElement, delegate) = if (element.hasAnnotation(AutoValue::class)) {
val elementUtils = context.processingEnv.elementUtils
@@ -117,7 +115,6 @@
bindingScope = bindingScope,
parent = parent,
referenceStack = referenceStack,
- ignoredColumns = ignoredColumns,
delegate = delegate)
}
}
@@ -161,6 +158,10 @@
}
}
+ val ignoredColumns =
+ element.toAnnotationBox(androidx.room.Entity::class)?.value?.ignoredColumns?.toSet()
+ ?: emptySet()
+ val fieldBindingErrors = mutableMapOf<Field, String>()
val unfilteredMyFields = allFields[null]
?.map {
FieldProcessor(
@@ -168,10 +169,17 @@
containing = declaredType,
element = it,
bindingScope = bindingScope,
- fieldParent = parent).process()
+ fieldParent = parent,
+ onBindingError = { field, errorMsg ->
+ fieldBindingErrors[field] = errorMsg
+ }).process()
} ?: emptyList()
val myFields = unfilteredMyFields.filterNot { ignoredColumns.contains(it.columnName) }
-
+ myFields.forEach { field ->
+ fieldBindingErrors[field]?.let {
+ context.logger.e(field.element, it)
+ }
+ }
val unfilteredEmbeddedFields =
allFields[Embedded::class]
?.mapNotNull {
@@ -707,7 +715,9 @@
context.logger.e(field.element,
ProcessorErrors.tooManyMatchingGetters(field, matching))
})
- context.checker.check(success, field.element, CANNOT_FIND_GETTER_FOR_FIELD)
+ context.checker.check(
+ success || bindingScope == FieldProcessor.BindingScope.READ_FROM_CURSOR,
+ field.element, CANNOT_FIND_GETTER_FOR_FIELD)
}
private fun assignSetters(
@@ -756,7 +766,9 @@
context.logger.e(field.element,
ProcessorErrors.tooManyMatchingSetter(field, matching))
})
- context.checker.check(success, field.element, CANNOT_FIND_SETTER_FOR_FIELD)
+ context.checker.check(
+ success || bindingScope == FieldProcessor.BindingScope.BIND_TO_STMT,
+ field.element, CANNOT_FIND_SETTER_FOR_FIELD)
}
/**
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt b/room/compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt
index e313569..d331aa1 100644
--- a/room/compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt
@@ -21,6 +21,7 @@
import androidx.room.Query
import androidx.room.RawQuery
import androidx.room.Update
+import androidx.room.ext.KotlinTypeNames
import androidx.room.ext.RoomTypeNames
import androidx.room.ext.SupportDbTypeNames
import androidx.room.parser.QueryType
@@ -725,4 +726,8 @@
" (https://bugs.openjdk.java.net/browse/JDK-8007720)" +
" that prevents Room from being incremental." +
" Consider using JDK 11+ or the embedded JDK shipped with Android Studio 3.5+."
+
+ fun invalidChannelType(typeName: String) = "'$typeName' is not supported as a return type. " +
+ "Instead declare return type as ${KotlinTypeNames.FLOW} and use Flow transforming " +
+ "functions that converts the Flow into a Channel."
}
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/TableEntityProcessor.kt b/room/compiler/src/main/kotlin/androidx/room/processor/TableEntityProcessor.kt
index a0b0c29..f570457 100644
--- a/room/compiler/src/main/kotlin/androidx/room/processor/TableEntityProcessor.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/TableEntityProcessor.kt
@@ -65,19 +65,16 @@
val entityIndices: List<IndexInput>
val foreignKeyInputs: List<ForeignKeyInput>
val inheritSuperIndices: Boolean
- val ignoredColumns: Set<String>
if (annotationBox != null) {
tableName = extractTableName(element, annotationBox.value)
entityIndices = extractIndices(annotationBox, tableName)
inheritSuperIndices = annotationBox.value.inheritSuperIndices
foreignKeyInputs = extractForeignKeys(annotationBox)
- ignoredColumns = annotationBox.value.ignoredColumns.toSet()
} else {
tableName = element.simpleName.toString()
foreignKeyInputs = emptyList()
entityIndices = emptyList()
inheritSuperIndices = false
- ignoredColumns = emptySet()
}
context.checker.notBlank(tableName, element,
ProcessorErrors.ENTITY_TABLE_NAME_CANNOT_BE_EMPTY)
@@ -85,12 +82,12 @@
ProcessorErrors.ENTITY_TABLE_NAME_CANNOT_START_WITH_SQLITE)
val pojo = PojoProcessor.createFor(
- context = context,
- element = element,
- bindingScope = FieldProcessor.BindingScope.TWO_WAY,
- parent = null,
- referenceStack = referenceStack,
- ignoredColumns = ignoredColumns).process()
+ context = context,
+ element = element,
+ bindingScope = FieldProcessor.BindingScope.TWO_WAY,
+ parent = null,
+ referenceStack = referenceStack
+ ).process()
context.checker.check(pojo.relations.isEmpty(), element, RELATION_IN_ENTITY)
val fieldIndices = pojo.fields
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt b/room/compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt
index ddfdc4e..d5a8ce3 100644
--- a/room/compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt
@@ -28,6 +28,7 @@
import androidx.room.processor.EntityProcessor
import androidx.room.processor.FieldProcessor
import androidx.room.processor.PojoProcessor
+import androidx.room.solver.binderprovider.CoroutineFlowResultBinderProvider
import androidx.room.solver.binderprovider.CursorQueryResultBinderProvider
import androidx.room.solver.binderprovider.DataSourceFactoryQueryResultBinderProvider
import androidx.room.solver.binderprovider.DataSourceQueryResultBinderProvider
@@ -175,6 +176,7 @@
RxSingleQueryResultBinderProvider(context),
DataSourceQueryResultBinderProvider(context),
DataSourceFactoryQueryResultBinderProvider(context),
+ CoroutineFlowResultBinderProvider(context),
InstantQueryResultBinderProvider(context)
)
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/binderprovider/CoroutineFlowResultBinderProvider.kt b/room/compiler/src/main/kotlin/androidx/room/solver/binderprovider/CoroutineFlowResultBinderProvider.kt
new file mode 100644
index 0000000..b419a57
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/binderprovider/CoroutineFlowResultBinderProvider.kt
@@ -0,0 +1,71 @@
+/*
+ * 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.room.solver.binderprovider
+
+import androidx.room.ext.KotlinTypeNames
+import androidx.room.ext.RoomCoroutinesTypeNames
+import androidx.room.ext.typeName
+import androidx.room.parser.ParsedQuery
+import androidx.room.processor.Context
+import androidx.room.processor.ProcessorErrors
+import androidx.room.solver.QueryResultBinderProvider
+import androidx.room.solver.query.result.CoroutineFlowResultBinder
+import androidx.room.solver.query.result.QueryResultBinder
+import javax.lang.model.type.DeclaredType
+
+class CoroutineFlowResultBinderProvider(val context: Context) : QueryResultBinderProvider {
+
+ companion object {
+ val CHANNEL_TYPE_NAMES = listOf(
+ KotlinTypeNames.CHANNEL,
+ KotlinTypeNames.SEND_CHANNEL,
+ KotlinTypeNames.RECEIVE_CHANNEL
+ )
+ }
+
+ private val hasCoroutinesArtifact by lazy {
+ context.processingEnv.elementUtils
+ .getTypeElement(RoomCoroutinesTypeNames.COROUTINES_ROOM.toString()) != null
+ }
+
+ override fun provide(declared: DeclaredType, query: ParsedQuery): QueryResultBinder {
+ val typeArg = declared.typeArguments.first()
+ val adapter = context.typeAdapterStore.findQueryResultAdapter(typeArg, query)
+ val tableNames = ((adapter?.accessedTableNames() ?: emptyList()) +
+ query.tables.map { it.name }).toSet()
+ if (tableNames.isEmpty()) {
+ context.logger.e(ProcessorErrors.OBSERVABLE_QUERY_NOTHING_TO_OBSERVE)
+ }
+ return CoroutineFlowResultBinder(typeArg, tableNames, adapter)
+ }
+
+ override fun matches(declared: DeclaredType): Boolean {
+ if (declared.typeArguments.size != 1) {
+ return false
+ }
+ val typeName = context.processingEnv.typeUtils.erasure(declared).typeName()
+ if (typeName in CHANNEL_TYPE_NAMES) {
+ context.logger.e(ProcessorErrors.invalidChannelType(typeName.toString()))
+ return false
+ }
+ val match = typeName == KotlinTypeNames.FLOW
+ if (match && !hasCoroutinesArtifact) {
+ context.logger.e(ProcessorErrors.MISSING_ROOM_COROUTINE_ARTIFACT)
+ }
+ return match
+ }
+}
\ No newline at end of file
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/BaseObservableQueryResultBinder.kt b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/BaseObservableQueryResultBinder.kt
index f33bef3..887fec8 100644
--- a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/BaseObservableQueryResultBinder.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/BaseObservableQueryResultBinder.kt
@@ -46,7 +46,8 @@
roomSQLiteQueryVar: String,
dbField: FieldSpec,
inTransaction: Boolean,
- scope: CodeGenScope
+ scope: CodeGenScope,
+ cancellationSignalVar: String
) {
val transactionWrapper = if (inTransaction) {
builder.transactionWrapper(dbField)
@@ -58,13 +59,16 @@
val cursorVar = scope.getTmpVar("_cursor")
transactionWrapper?.beginTransactionWithControlFlow()
builder.apply {
- addStatement("final $T $L = $T.query($N, $L, $L)",
- AndroidTypeNames.CURSOR,
- cursorVar,
- RoomTypeNames.DB_UTIL,
- dbField,
- roomSQLiteQueryVar,
- if (shouldCopyCursor) "true" else "false")
+ addStatement(
+ "final $T $L = $T.query($N, $L, $L, $L)",
+ AndroidTypeNames.CURSOR,
+ cursorVar,
+ RoomTypeNames.DB_UTIL,
+ dbField,
+ roomSQLiteQueryVar,
+ if (shouldCopyCursor) "true" else "false",
+ cancellationSignalVar
+ )
beginControlFlow("try").apply {
val adapterScope = scope.fork()
adapter?.convert(outVar, cursorVar, adapterScope)
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/CoroutineFlowResultBinder.kt b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/CoroutineFlowResultBinder.kt
new file mode 100644
index 0000000..127d925
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/CoroutineFlowResultBinder.kt
@@ -0,0 +1,118 @@
+/*
+ * 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.room.solver.query.result
+
+import androidx.room.ext.AndroidTypeNames
+import androidx.room.ext.CallableTypeSpecBuilder
+import androidx.room.ext.L
+import androidx.room.ext.N
+import androidx.room.ext.RoomCoroutinesTypeNames
+import androidx.room.ext.RoomTypeNames
+import androidx.room.ext.T
+import androidx.room.ext.arrayTypeName
+import androidx.room.ext.typeName
+import androidx.room.solver.CodeGenScope
+import com.squareup.javapoet.FieldSpec
+import com.squareup.javapoet.MethodSpec
+import javax.lang.model.type.TypeMirror
+
+/**
+ * Binds the result of a of a Kotlin Coroutine Flow<T>
+ */
+class CoroutineFlowResultBinder(
+ val typeArg: TypeMirror,
+ val tableNames: Set<String>,
+ adapter: QueryResultAdapter?
+) : QueryResultBinder(adapter) {
+
+ override fun convertAndReturn(
+ roomSQLiteQueryVar: String,
+ canReleaseQuery: Boolean,
+ dbField: FieldSpec,
+ inTransaction: Boolean,
+ scope: CodeGenScope,
+ cancellationSignalVar: String
+ ) {
+ val callableImpl = CallableTypeSpecBuilder(typeArg.typeName()) {
+ createRunQueryAndReturnStatements(
+ builder = this,
+ roomSQLiteQueryVar = roomSQLiteQueryVar,
+ canReleaseQuery = canReleaseQuery,
+ dbField = dbField,
+ inTransaction = inTransaction,
+ scope = scope,
+ cancellationSignalVar = cancellationSignalVar)
+ }.build()
+
+ scope.builder().apply {
+ val tableNamesList = tableNames.joinToString(",") { "\"$it\"" }
+ addStatement(
+ "return $T.createFlow($N, $L, new $T{$L}, $L)",
+ RoomCoroutinesTypeNames.COROUTINES_ROOM,
+ dbField,
+ if (inTransaction) "true" else "false",
+ String::class.arrayTypeName(),
+ tableNamesList,
+ callableImpl)
+ }
+ }
+
+ private fun createRunQueryAndReturnStatements(
+ builder: MethodSpec.Builder,
+ roomSQLiteQueryVar: String,
+ canReleaseQuery: Boolean,
+ dbField: FieldSpec,
+ inTransaction: Boolean,
+ scope: CodeGenScope,
+ cancellationSignalVar: String
+ ) {
+ val transactionWrapper = if (inTransaction) {
+ builder.transactionWrapper(dbField)
+ } else {
+ null
+ }
+ val shouldCopyCursor = adapter?.shouldCopyCursor() == true
+ val outVar = scope.getTmpVar("_result")
+ val cursorVar = scope.getTmpVar("_cursor")
+ transactionWrapper?.beginTransactionWithControlFlow()
+ builder.apply {
+ addStatement("final $T $L = $T.query($N, $L, $L, $L)",
+ AndroidTypeNames.CURSOR,
+ cursorVar,
+ RoomTypeNames.DB_UTIL,
+ dbField,
+ roomSQLiteQueryVar,
+ if (shouldCopyCursor) "true" else "false",
+ cancellationSignalVar)
+ beginControlFlow("try").apply {
+ val adapterScope = scope.fork()
+ adapter?.convert(outVar, cursorVar, adapterScope)
+ addCode(adapterScope.builder().build())
+ transactionWrapper?.commitTransaction()
+ addStatement("return $L", outVar)
+ }
+ nextControlFlow("finally").apply {
+ addStatement("$L.close()", cursorVar)
+ if (canReleaseQuery) {
+ addStatement("$L.release()", roomSQLiteQueryVar)
+ }
+ }
+ endControlFlow()
+ }
+ transactionWrapper?.endTransactionWithControlFlow()
+ }
+}
\ No newline at end of file
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/CoroutineResultBinder.kt b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/CoroutineResultBinder.kt
index d97ba61..906db3b 100644
--- a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/CoroutineResultBinder.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/CoroutineResultBinder.kt
@@ -43,7 +43,8 @@
canReleaseQuery: Boolean,
dbField: FieldSpec,
inTransaction: Boolean,
- scope: CodeGenScope
+ scope: CodeGenScope,
+ cancellationSignalVar: String
) {
val callableImpl = CallableTypeSpecBuilder(typeArg.typeName()) {
createRunQueryAndReturnStatements(
@@ -52,7 +53,8 @@
canReleaseQuery = canReleaseQuery,
dbField = dbField,
inTransaction = inTransaction,
- scope = scope)
+ scope = scope,
+ cancellationSignalVar = cancellationSignalVar)
}.build()
scope.builder().apply {
@@ -72,7 +74,8 @@
canReleaseQuery: Boolean,
dbField: FieldSpec,
inTransaction: Boolean,
- scope: CodeGenScope
+ scope: CodeGenScope,
+ cancellationSignalVar: String
) {
val transactionWrapper = if (inTransaction) {
builder.transactionWrapper(dbField)
@@ -84,13 +87,16 @@
val cursorVar = scope.getTmpVar("_cursor")
transactionWrapper?.beginTransactionWithControlFlow()
builder.apply {
- addStatement("final $T $L = $T.query($N, $L, $L)",
+ addStatement(
+ "final $T $L = $T.query($N, $L, $L, $L)",
AndroidTypeNames.CURSOR,
cursorVar,
RoomTypeNames.DB_UTIL,
dbField,
roomSQLiteQueryVar,
- if (shouldCopyCursor) "true" else "false")
+ if (shouldCopyCursor) "true" else "false",
+ cancellationSignalVar
+ )
beginControlFlow("try").apply {
val adapterScope = scope.fork()
adapter?.convert(outVar, cursorVar, adapterScope)
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/CursorQueryResultBinder.kt b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/CursorQueryResultBinder.kt
index 9f8c9e7..9e9eb9c 100644
--- a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/CursorQueryResultBinder.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/CursorQueryResultBinder.kt
@@ -29,7 +29,8 @@
canReleaseQuery: Boolean,
dbField: FieldSpec,
inTransaction: Boolean,
- scope: CodeGenScope
+ scope: CodeGenScope,
+ cancellationSignalVar: String
) {
val builder = scope.builder()
val transactionWrapper = if (inTransaction) {
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/DataSourceFactoryQueryResultBinder.kt b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/DataSourceFactoryQueryResultBinder.kt
index 9bf9860..2609a29 100644
--- a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/DataSourceFactoryQueryResultBinder.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/DataSourceFactoryQueryResultBinder.kt
@@ -37,7 +37,8 @@
canReleaseQuery: Boolean,
dbField: FieldSpec,
inTransaction: Boolean,
- scope: CodeGenScope
+ scope: CodeGenScope,
+ cancellationSignalVar: String
) {
scope.builder().apply {
val pagedListProvider = TypeSpec
@@ -48,7 +49,8 @@
roomSQLiteQueryVar = roomSQLiteQueryVar,
dbField = dbField,
inTransaction = inTransaction,
- scope = scope))
+ scope = scope,
+ cancellationSignalVar = cancellationSignalVar))
}.build()
addStatement("return $L", pagedListProvider)
}
@@ -58,7 +60,8 @@
roomSQLiteQueryVar: String,
dbField: FieldSpec,
inTransaction: Boolean,
- scope: CodeGenScope
+ scope: CodeGenScope,
+ cancellationSignalVar: String
): MethodSpec = MethodSpec.methodBuilder("create").apply {
addAnnotation(Override::class.java)
addModifiers(Modifier.PUBLIC)
@@ -69,7 +72,8 @@
canReleaseQuery = true,
dbField = dbField,
inTransaction = inTransaction,
- scope = countedBinderScope)
+ scope = countedBinderScope,
+ cancellationSignalVar = cancellationSignalVar)
addCode(countedBinderScope.builder().build())
}.build()
}
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/GuavaListenableFutureQueryResultBinder.kt b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/GuavaListenableFutureQueryResultBinder.kt
index 722242e..b3d8c2c 100644
--- a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/GuavaListenableFutureQueryResultBinder.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/GuavaListenableFutureQueryResultBinder.kt
@@ -41,7 +41,8 @@
canReleaseQuery: Boolean,
dbField: FieldSpec,
inTransaction: Boolean,
- scope: CodeGenScope
+ scope: CodeGenScope,
+ cancellationSignalVar: String
) {
// Callable<T> // Note that this callable does not release the query object.
val callableImpl = CallableTypeSpecBuilder(typeArg.typeName()) {
@@ -50,7 +51,8 @@
roomSQLiteQueryVar = roomSQLiteQueryVar,
dbField = dbField,
inTransaction = inTransaction,
- scope = scope
+ scope = scope,
+ cancellationSignalVar = cancellationSignalVar
)
}.build()
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/InstantQueryResultBinder.kt b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/InstantQueryResultBinder.kt
index 156117c..48e322c 100644
--- a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/InstantQueryResultBinder.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/InstantQueryResultBinder.kt
@@ -33,7 +33,8 @@
canReleaseQuery: Boolean,
dbField: FieldSpec,
inTransaction: Boolean,
- scope: CodeGenScope
+ scope: CodeGenScope,
+ cancellationSignalVar: String
) {
scope.builder().apply {
addStatement("$N.assertNotSuspendingTransaction()", DaoWriter.dbField)
@@ -48,13 +49,16 @@
val shouldCopyCursor = adapter?.shouldCopyCursor() == true
val outVar = scope.getTmpVar("_result")
val cursorVar = scope.getTmpVar("_cursor")
- addStatement("final $T $L = $T.query($N, $L, $L)",
- AndroidTypeNames.CURSOR,
- cursorVar,
- RoomTypeNames.DB_UTIL,
- dbField,
- roomSQLiteQueryVar,
- if (shouldCopyCursor) "true" else "false")
+ addStatement(
+ "final $T $L = $T.query($N, $L, $L, $L)",
+ AndroidTypeNames.CURSOR,
+ cursorVar,
+ RoomTypeNames.DB_UTIL,
+ dbField,
+ roomSQLiteQueryVar,
+ if (shouldCopyCursor) "true" else "false",
+ cancellationSignalVar
+ )
beginControlFlow("try").apply {
adapter?.convert(outVar, cursorVar, scope)
transactionWrapper?.commitTransaction()
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/LiveDataQueryResultBinder.kt b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/LiveDataQueryResultBinder.kt
index cd84015..e4e77d2 100644
--- a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/LiveDataQueryResultBinder.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/LiveDataQueryResultBinder.kt
@@ -48,7 +48,8 @@
canReleaseQuery: Boolean,
dbField: FieldSpec,
inTransaction: Boolean,
- scope: CodeGenScope
+ scope: CodeGenScope,
+ cancellationSignalVar: String
) {
val callableImpl = CallableTypeSpecBuilder(typeArg.typeName()) {
createRunQueryAndReturnStatements(
@@ -56,7 +57,8 @@
roomSQLiteQueryVar = roomSQLiteQueryVar,
inTransaction = inTransaction,
dbField = dbField,
- scope = scope
+ scope = scope,
+ cancellationSignalVar = "null" // LiveData can't be cancelled
)
}.apply {
if (canReleaseQuery) {
@@ -83,7 +85,8 @@
observerField: FieldSpec,
dbField: FieldSpec,
inTransaction: Boolean,
- scope: CodeGenScope
+ scope: CodeGenScope,
+ cancellationSignal: String
): MethodSpec {
return MethodSpec.methodBuilder("compute").apply {
addAnnotation(Override::class.java)
@@ -104,7 +107,8 @@
roomSQLiteQueryVar = roomSQLiteQueryVar,
dbField = dbField,
inTransaction = inTransaction,
- scope = scope
+ scope = scope,
+ cancellationSignalVar = cancellationSignal
)
}.build()
}
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/PositionalDataSourceQueryResultBinder.kt b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/PositionalDataSourceQueryResultBinder.kt
index 8ddf99f..1efee13 100644
--- a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/PositionalDataSourceQueryResultBinder.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/PositionalDataSourceQueryResultBinder.kt
@@ -43,7 +43,8 @@
canReleaseQuery: Boolean,
dbField: FieldSpec,
inTransaction: Boolean,
- scope: CodeGenScope
+ scope: CodeGenScope,
+ cancellationSignalVar: String
) {
// first comma for table names comes from the string since it might be empty in which case
// we don't need a comma. If list is empty, this prevents generating bad code (it is still
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/QueryResultBinder.kt b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/QueryResultBinder.kt
index d3d6d8c..faa3854 100644
--- a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/QueryResultBinder.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/QueryResultBinder.kt
@@ -36,6 +36,7 @@
canReleaseQuery: Boolean, // false if query is provided by the user
dbField: FieldSpec,
inTransaction: Boolean,
- scope: CodeGenScope
+ scope: CodeGenScope,
+ cancellationSignalVar: String // "null" if cancellation isn't supported
)
}
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/RxCallableQueryResultBinder.kt b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/RxCallableQueryResultBinder.kt
index 71a4a2c..fc454fe 100644
--- a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/RxCallableQueryResultBinder.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/RxCallableQueryResultBinder.kt
@@ -46,14 +46,16 @@
canReleaseQuery: Boolean,
dbField: FieldSpec,
inTransaction: Boolean,
- scope: CodeGenScope
+ scope: CodeGenScope,
+ cancellationSignalVar: String
) {
val callable = CallableTypeSpecBuilder(typeArg.typeName()) {
fillInCallMethod(
roomSQLiteQueryVar = roomSQLiteQueryVar,
dbField = dbField,
inTransaction = inTransaction,
- scope = scope)
+ scope = scope,
+ cancellationSignalVar = cancellationSignalVar)
}.apply {
if (canReleaseQuery) {
addMethod(createFinalizeMethod(roomSQLiteQueryVar))
@@ -72,7 +74,8 @@
roomSQLiteQueryVar: String,
dbField: FieldSpec,
inTransaction: Boolean,
- scope: CodeGenScope
+ scope: CodeGenScope,
+ cancellationSignalVar: String
) {
val adapterScope = scope.fork()
val transactionWrapper = if (inTransaction) {
@@ -84,13 +87,14 @@
val shouldCopyCursor = adapter?.shouldCopyCursor() == true
val outVar = scope.getTmpVar("_result")
val cursorVar = scope.getTmpVar("_cursor")
- addStatement("final $T $L = $T.query($N, $L, $L)",
+ addStatement("final $T $L = $T.query($N, $L, $L, $L)",
AndroidTypeNames.CURSOR,
cursorVar,
RoomTypeNames.DB_UTIL,
dbField,
roomSQLiteQueryVar,
- if (shouldCopyCursor) "true" else "false")
+ if (shouldCopyCursor) "true" else "false",
+ cancellationSignalVar)
beginControlFlow("try").apply {
adapter?.convert(outVar, cursorVar, adapterScope)
addCode(adapterScope.generate())
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/RxQueryResultBinder.kt b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/RxQueryResultBinder.kt
index d52e804..f042089 100644
--- a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/RxQueryResultBinder.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/RxQueryResultBinder.kt
@@ -43,14 +43,16 @@
canReleaseQuery: Boolean,
dbField: FieldSpec,
inTransaction: Boolean,
- scope: CodeGenScope
+ scope: CodeGenScope,
+ cancellationSignalVar: String
) {
val callableImpl = CallableTypeSpecBuilder(typeArg.typeName()) {
createRunQueryAndReturnStatements(builder = this,
roomSQLiteQueryVar = roomSQLiteQueryVar,
inTransaction = inTransaction,
dbField = dbField,
- scope = scope)
+ scope = scope,
+ cancellationSignalVar = cancellationSignalVar)
}.apply {
if (canReleaseQuery) {
addMethod(createFinalizeMethod(roomSQLiteQueryVar))
diff --git a/room/compiler/src/main/kotlin/androidx/room/writer/DaoWriter.kt b/room/compiler/src/main/kotlin/androidx/room/writer/DaoWriter.kt
index 5d9dc90..d3a2106 100644
--- a/room/compiler/src/main/kotlin/androidx/room/writer/DaoWriter.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/writer/DaoWriter.kt
@@ -273,7 +273,8 @@
canReleaseQuery = shouldReleaseQuery,
dbField = dbField,
inTransaction = method.inTransaction,
- scope = scope)
+ scope = scope,
+ cancellationSignalVar = "null")
}
addCode(scope.builder().build())
}.build()
@@ -424,7 +425,8 @@
canReleaseQuery = true,
dbField = dbField,
inTransaction = method.inTransaction,
- scope = scope)
+ scope = scope,
+ cancellationSignalVar = "null")
return scope.builder().build()
}
diff --git a/room/compiler/src/main/kotlin/androidx/room/writer/RelationCollectorMethodWriter.kt b/room/compiler/src/main/kotlin/androidx/room/writer/RelationCollectorMethodWriter.kt
index ae44b51..b2d3410 100644
--- a/room/compiler/src/main/kotlin/androidx/room/writer/RelationCollectorMethodWriter.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/writer/RelationCollectorMethodWriter.kt
@@ -133,13 +133,14 @@
val shouldCopyCursor = collector.rowAdapter.let {
it is PojoRowAdapter && it.relationCollectors.isNotEmpty()
}
- addStatement("final $T $L = $T.query($N, $L, $L)",
+ addStatement("final $T $L = $T.query($N, $L, $L, $L)",
AndroidTypeNames.CURSOR,
cursorVar,
RoomTypeNames.DB_UTIL,
DaoWriter.dbField,
stmtVar,
- if (shouldCopyCursor) "true" else "false")
+ if (shouldCopyCursor) "true" else "false",
+ "null")
beginControlFlow("try").apply {
if (relation.junction != null) {
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/JoinedKey.kt b/room/compiler/src/test/data/common/input/coroutines/Channel.java
similarity index 83%
copy from compose/compose-runtime/src/main/java/androidx/compose/JoinedKey.kt
copy to room/compiler/src/test/data/common/input/coroutines/Channel.java
index 601fbf1..092782a 100644
--- a/compose/compose-runtime/src/main/java/androidx/compose/JoinedKey.kt
+++ b/room/compiler/src/test/data/common/input/coroutines/Channel.java
@@ -14,9 +14,8 @@
* limitations under the License.
*/
-package androidx.compose
+package kotlinx.coroutines.channels;
-internal data class JoinedKey(
- @JvmField val left: Any?,
- @JvmField val right: Any?
-)
\ No newline at end of file
+public interface Channel<T> extends SendChannel<T>, ReceiveChannel<T> {
+
+}
\ No newline at end of file
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/JoinedKey.kt b/room/compiler/src/test/data/common/input/coroutines/ReceiveChannel.java
similarity index 83%
copy from compose/compose-runtime/src/main/java/androidx/compose/JoinedKey.kt
copy to room/compiler/src/test/data/common/input/coroutines/ReceiveChannel.java
index 601fbf1..a822127 100644
--- a/compose/compose-runtime/src/main/java/androidx/compose/JoinedKey.kt
+++ b/room/compiler/src/test/data/common/input/coroutines/ReceiveChannel.java
@@ -14,9 +14,8 @@
* limitations under the License.
*/
-package androidx.compose
+package kotlinx.coroutines.channels;
-internal data class JoinedKey(
- @JvmField val left: Any?,
- @JvmField val right: Any?
-)
\ No newline at end of file
+public interface ReceiveChannel<T> {
+
+}
\ No newline at end of file
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/JoinedKey.kt b/room/compiler/src/test/data/common/input/coroutines/SendChannel.java
similarity index 83%
copy from compose/compose-runtime/src/main/java/androidx/compose/JoinedKey.kt
copy to room/compiler/src/test/data/common/input/coroutines/SendChannel.java
index 601fbf1..a8c4b11 100644
--- a/compose/compose-runtime/src/main/java/androidx/compose/JoinedKey.kt
+++ b/room/compiler/src/test/data/common/input/coroutines/SendChannel.java
@@ -14,9 +14,8 @@
* limitations under the License.
*/
-package androidx.compose
+package kotlinx.coroutines.channels;
-internal data class JoinedKey(
- @JvmField val left: Any?,
- @JvmField val right: Any?
-)
\ No newline at end of file
+public interface SendChannel<T> {
+
+}
\ No newline at end of file
diff --git a/room/compiler/src/test/data/daoWriter/output/ComplexDao.java b/room/compiler/src/test/data/daoWriter/output/ComplexDao.java
index b3399a9..72e3734 100644
--- a/room/compiler/src/test/data/daoWriter/output/ComplexDao.java
+++ b/room/compiler/src/test/data/daoWriter/output/ComplexDao.java
@@ -47,7 +47,7 @@
int _argIndex = 1;
_statement.bindLong(_argIndex, id);
__db.assertNotSuspendingTransaction();
- final Cursor _cursor = DBUtil.query(__db, _statement, false);
+ final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
try {
final int _cursorIndexOfFullName = CursorUtil.getColumnIndexOrThrow(_cursor, "fullName");
final int _cursorIndexOfId = CursorUtil.getColumnIndexOrThrow(_cursor, "id");
@@ -73,7 +73,7 @@
int _argIndex = 1;
_statement.bindLong(_argIndex, id);
__db.assertNotSuspendingTransaction();
- final Cursor _cursor = DBUtil.query(__db, _statement, false);
+ final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
try {
final int _cursorIndexOfUid = CursorUtil.getColumnIndexOrThrow(_cursor, "uid");
final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
@@ -115,7 +115,7 @@
_statement.bindString(_argIndex, lastName);
}
__db.assertNotSuspendingTransaction();
- final Cursor _cursor = DBUtil.query(__db, _statement, false);
+ final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
try {
final int _cursorIndexOfUid = CursorUtil.getColumnIndexOrThrow(_cursor, "uid");
final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
@@ -158,7 +158,7 @@
_argIndex ++;
}
__db.assertNotSuspendingTransaction();
- final Cursor _cursor = DBUtil.query(__db, _statement, false);
+ final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
try {
final int _cursorIndexOfUid = CursorUtil.getColumnIndexOrThrow(_cursor, "uid");
final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
@@ -190,7 +190,7 @@
int _argIndex = 1;
_statement.bindLong(_argIndex, id);
__db.assertNotSuspendingTransaction();
- final Cursor _cursor = DBUtil.query(__db, _statement, false);
+ final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
try {
final int _result;
if(_cursor.moveToFirst()) {
@@ -221,7 +221,7 @@
_argIndex ++;
}
__db.assertNotSuspendingTransaction();
- final Cursor _cursor = DBUtil.query(__db, _statement, false);
+ final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
try {
final int[] _result = new int[_cursor.getCount()];
int _index = 0;
@@ -258,7 +258,7 @@
_argIndex ++;
}
__db.assertNotSuspendingTransaction();
- final Cursor _cursor = DBUtil.query(__db, _statement, false);
+ final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
try {
final List<Integer> _result = new ArrayList<Integer>(_cursor.getCount());
while(_cursor.moveToNext()) {
@@ -286,7 +286,7 @@
return __db.getInvalidationTracker().createLiveData(new String[]{"user"}, false, new Callable<User>() {
@Override
public User call() throws Exception {
- final Cursor _cursor = DBUtil.query(__db, _statement, false);
+ final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
try {
final int _cursorIndexOfUid = CursorUtil.getColumnIndexOrThrow(_cursor, "uid");
final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
@@ -337,7 +337,7 @@
return __db.getInvalidationTracker().createLiveData(new String[]{"user"}, false, new Callable<List<User>>() {
@Override
public List<User> call() throws Exception {
- final Cursor _cursor = DBUtil.query(__db, _statement, false);
+ final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
try {
final int _cursorIndexOfUid = CursorUtil.getColumnIndexOrThrow(_cursor, "uid");
final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
@@ -405,7 +405,7 @@
_argIndex ++;
}
__db.assertNotSuspendingTransaction();
- final Cursor _cursor = DBUtil.query(__db, _statement, false);
+ final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
try {
final List<Integer> _result = new ArrayList<Integer>(_cursor.getCount());
while(_cursor.moveToNext()) {
@@ -429,7 +429,7 @@
final String _sql = "SELECT * FROM Child1";
final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0);
__db.assertNotSuspendingTransaction();
- final Cursor _cursor = DBUtil.query(__db, _statement, false);
+ final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
try {
final int _cursorIndexOfId = CursorUtil.getColumnIndexOrThrow(_cursor, "id");
final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
@@ -465,7 +465,7 @@
final String _sql = "SELECT * FROM Child2";
final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0);
__db.assertNotSuspendingTransaction();
- final Cursor _cursor = DBUtil.query(__db, _statement, false);
+ final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
try {
final int _cursorIndexOfId = CursorUtil.getColumnIndexOrThrow(_cursor, "id");
final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
diff --git a/room/compiler/src/test/kotlin/androidx/room/processor/FieldProcessorTest.kt b/room/compiler/src/test/kotlin/androidx/room/processor/FieldProcessorTest.kt
index a685e40..71d2a72f 100644
--- a/room/compiler/src/test/kotlin/androidx/room/processor/FieldProcessorTest.kt
+++ b/room/compiler/src/test/kotlin/androidx/room/processor/FieldProcessorTest.kt
@@ -435,7 +435,7 @@
.processedWith(TestProcessor.builder()
.forAnnotations(androidx.room.Entity::class)
.nextRunHandler { invocation ->
- val (owner, field) = invocation.roundEnv
+ val (owner, fieldElement) = invocation.roundEnv
.getElementsAnnotatedWith(Entity::class.java)
.map {
Pair(it, invocation.processingEnv.elementUtils
@@ -451,9 +451,12 @@
val parser = FieldProcessor(
baseContext = entityContext,
containing = MoreTypes.asDeclared(owner.asType()),
- element = field!!,
+ element = fieldElement!!,
bindingScope = FieldProcessor.BindingScope.TWO_WAY,
- fieldParent = null)
+ fieldParent = null,
+ onBindingError = { field, errorMsg ->
+ invocation.context.logger.e(field.element, errorMsg) }
+ )
handler(parser.process(), invocation)
true
}
diff --git a/room/compiler/src/test/kotlin/androidx/room/processor/PojoProcessorTest.kt b/room/compiler/src/test/kotlin/androidx/room/processor/PojoProcessorTest.kt
index 4dd648a..e51aad4 100644
--- a/room/compiler/src/test/kotlin/androidx/room/processor/PojoProcessorTest.kt
+++ b/room/compiler/src/test/kotlin/androidx/room/processor/PojoProcessorTest.kt
@@ -1364,6 +1364,8 @@
"""
package foo.bar;
import androidx.room.*;
+
+ @Entity(ignoredColumns = {"bar"})
public class ${MY_POJO.simpleName()} {
public String foo;
public String bar;
@@ -1372,7 +1374,6 @@
val pojo = PojoProcessor.createFor(context = invocation.context,
element = invocation.typeElement(MY_POJO.toString()),
bindingScope = FieldProcessor.BindingScope.READ_FROM_CURSOR,
- ignoredColumns = setOf("bar"),
parent = null).process()
assertThat(pojo.fields.find { it.name == "foo" }, notNullValue())
assertThat(pojo.fields.find { it.name == "bar" }, nullValue())
@@ -1385,6 +1386,8 @@
"""
package foo.bar;
import androidx.room.*;
+
+ @Entity(ignoredColumns = {"bar"})
public class ${MY_POJO.simpleName()} {
private final String foo;
private final String bar;
@@ -1402,8 +1405,7 @@
val pojo = PojoProcessor.createFor(context = invocation.context,
element = invocation.typeElement(MY_POJO.toString()),
bindingScope = FieldProcessor.BindingScope.READ_FROM_CURSOR,
- ignoredColumns = setOf("bar"),
- parent = null).process()
+ parent = null).process()
assertThat(pojo.fields.find { it.name == "foo" }, notNullValue())
assertThat(pojo.fields.find { it.name == "bar" }, nullValue())
}.compilesWithoutError()
@@ -1415,6 +1417,8 @@
"""
package foo.bar;
import androidx.room.*;
+
+ @Entity(ignoredColumns = {"bar"})
public class ${MY_POJO.simpleName()} {
private String foo;
private String bar;
@@ -1431,7 +1435,6 @@
val pojo = PojoProcessor.createFor(context = invocation.context,
element = invocation.typeElement(MY_POJO.toString()),
bindingScope = FieldProcessor.BindingScope.READ_FROM_CURSOR,
- ignoredColumns = setOf("bar"),
parent = null).process()
assertThat(pojo.fields.find { it.name == "foo" }, notNullValue())
assertThat(pojo.fields.find { it.name == "bar" }, nullValue())
@@ -1444,6 +1447,8 @@
"""
package foo.bar;
import androidx.room.*;
+
+ @Entity(ignoredColumns = {"my_bar"})
public class ${MY_POJO.simpleName()} {
public String foo;
@ColumnInfo(name = "my_bar")
@@ -1453,7 +1458,6 @@
val pojo = PojoProcessor.createFor(context = invocation.context,
element = invocation.typeElement(MY_POJO.toString()),
bindingScope = FieldProcessor.BindingScope.READ_FROM_CURSOR,
- ignoredColumns = setOf("my_bar"),
parent = null).process()
assertThat(pojo.fields.find { it.name == "foo" }, notNullValue())
assertThat(pojo.fields.find { it.name == "bar" }, nullValue())
@@ -1466,6 +1470,8 @@
"""
package foo.bar;
import androidx.room.*;
+
+ @Entity(ignoredColumns = {"no_such_column"})
public class ${MY_POJO.simpleName()} {
public String foo;
public String bar;
@@ -1474,7 +1480,6 @@
val pojo = PojoProcessor.createFor(context = invocation.context,
element = invocation.typeElement(MY_POJO.toString()),
bindingScope = FieldProcessor.BindingScope.READ_FROM_CURSOR,
- ignoredColumns = setOf("no_such_column"),
parent = null).process()
assertThat(pojo.fields.find { it.name == "foo" }, notNullValue())
assertThat(pojo.fields.find { it.name == "bar" }, notNullValue())
@@ -1482,6 +1487,132 @@
ProcessorErrors.missingIgnoredColumns(listOf("no_such_column")))
}
+ @Test
+ fun noSetter_scopeBindStmt() {
+ simpleRun(
+ """
+ package foo.bar;
+ import androidx.room.*;
+ public class ${MY_POJO.simpleName()} {
+ private String foo;
+ private String bar;
+
+ public String getFoo() { return foo; }
+ public String getBar() { return bar; }
+ }
+ """.toJFO(MY_POJO.toString())) { invocation ->
+ PojoProcessor.createFor(context = invocation.context,
+ element = invocation.typeElement(MY_POJO.toString()),
+ bindingScope = FieldProcessor.BindingScope.BIND_TO_STMT,
+ parent = null).process()
+ }.compilesWithoutError()
+ }
+
+ @Test
+ fun noSetter_scopeTwoWay() {
+ simpleRun(
+ """
+ package foo.bar;
+ import androidx.room.*;
+ public class ${MY_POJO.simpleName()} {
+ private String foo;
+ private String bar;
+
+ public String getFoo() { return foo; }
+ public String getBar() { return bar; }
+ }
+ """.toJFO(MY_POJO.toString())) { invocation ->
+ PojoProcessor.createFor(context = invocation.context,
+ element = invocation.typeElement(MY_POJO.toString()),
+ bindingScope = FieldProcessor.BindingScope.TWO_WAY,
+ parent = null).process()
+ }.failsToCompile().withErrorContaining("Cannot find setter for field.")
+ }
+
+ @Test
+ fun noSetter_scopeReadFromCursor() {
+ simpleRun(
+ """
+ package foo.bar;
+ import androidx.room.*;
+ public class ${MY_POJO.simpleName()} {
+ private String foo;
+ private String bar;
+
+ public String getFoo() { return foo; }
+ public String getBar() { return bar; }
+ }
+ """.toJFO(MY_POJO.toString())) { invocation ->
+ PojoProcessor.createFor(context = invocation.context,
+ element = invocation.typeElement(MY_POJO.toString()),
+ bindingScope = FieldProcessor.BindingScope.READ_FROM_CURSOR,
+ parent = null).process()
+ }.failsToCompile().withErrorContaining("Cannot find setter for field.")
+ }
+
+ @Test
+ fun noGetter_scopeBindStmt() {
+ simpleRun(
+ """
+ package foo.bar;
+ import androidx.room.*;
+ public class ${MY_POJO.simpleName()} {
+ private String foo;
+ private String bar;
+
+ public void setFoo(String foo) { this.foo = foo; }
+ public void setBar(String bar) { this.bar = bar; }
+ }
+ """.toJFO(MY_POJO.toString())) { invocation ->
+ PojoProcessor.createFor(context = invocation.context,
+ element = invocation.typeElement(MY_POJO.toString()),
+ bindingScope = FieldProcessor.BindingScope.BIND_TO_STMT,
+ parent = null).process()
+ }.failsToCompile().withErrorContaining("Cannot find getter for field.")
+ }
+
+ @Test
+ fun noGetter_scopeTwoWay() {
+ simpleRun(
+ """
+ package foo.bar;
+ import androidx.room.*;
+ public class ${MY_POJO.simpleName()} {
+ private String foo;
+ private String bar;
+
+ public void setFoo(String foo) { this.foo = foo; }
+ public void setBar(String bar) { this.bar = bar; }
+ }
+ """.toJFO(MY_POJO.toString())) { invocation ->
+ PojoProcessor.createFor(context = invocation.context,
+ element = invocation.typeElement(MY_POJO.toString()),
+ bindingScope = FieldProcessor.BindingScope.TWO_WAY,
+ parent = null).process()
+ }.failsToCompile().withErrorContaining("Cannot find getter for field.")
+ }
+
+ @Test
+ fun noGetter_scopeReadCursor() {
+ simpleRun(
+ """
+ package foo.bar;
+ import androidx.room.*;
+ public class ${MY_POJO.simpleName()} {
+ private String foo;
+ private String bar;
+
+ public void setFoo(String foo) { this.foo = foo; }
+ public void setBar(String bar) { this.bar = bar; }
+ }
+ """.toJFO(MY_POJO.toString())) { invocation ->
+ PojoProcessor.createFor(context = invocation.context,
+ element = invocation.typeElement(MY_POJO.toString()),
+ bindingScope = FieldProcessor.BindingScope.READ_FROM_CURSOR,
+ parent = null).process()
+ }.compilesWithoutError()
+ }
+
private fun singleRun(
code: String,
vararg jfos: JavaFileObject,
@@ -1523,7 +1654,7 @@
handler.invoke(
PojoProcessor.createFor(context = invocation.context,
element = invocation.typeElement(MY_POJO.toString()),
- bindingScope = FieldProcessor.BindingScope.READ_FROM_CURSOR,
+ bindingScope = FieldProcessor.BindingScope.TWO_WAY,
parent = null).process(),
invocation
)
diff --git a/room/compiler/src/test/kotlin/androidx/room/processor/QueryMethodProcessorTest.kt b/room/compiler/src/test/kotlin/androidx/room/processor/QueryMethodProcessorTest.kt
index 47d039d..aee9801 100644
--- a/room/compiler/src/test/kotlin/androidx/room/processor/QueryMethodProcessorTest.kt
+++ b/room/compiler/src/test/kotlin/androidx/room/processor/QueryMethodProcessorTest.kt
@@ -25,6 +25,7 @@
import androidx.room.Relation
import androidx.room.Transaction
import androidx.room.ext.CommonTypeNames
+import androidx.room.ext.KotlinTypeNames
import androidx.room.ext.LifecyclesTypeNames
import androidx.room.ext.PagingTypeNames
import androidx.room.ext.hasAnnotation
@@ -73,6 +74,7 @@
import javax.lang.model.type.DeclaredType
import javax.lang.model.type.TypeKind.INT
import javax.lang.model.type.TypeMirror
+import javax.tools.JavaFileObject
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
@RunWith(Parameterized::class)
@@ -560,6 +562,48 @@
}
@Test
+ fun testBadChannelReturnForQuery() {
+ singleQueryMethod<QueryMethod>(
+ """
+ @Query("select * from user")
+ abstract ${KotlinTypeNames.CHANNEL}<User> getUsersChannel();
+ """,
+ jfos = listOf(COMMON.CHANNEL)
+ ) { _, _ ->
+ }.failsToCompile()
+ .withErrorContaining(ProcessorErrors.invalidChannelType(
+ KotlinTypeNames.CHANNEL.toString()))
+ }
+
+ @Test
+ fun testBadSendChannelReturnForQuery() {
+ singleQueryMethod<QueryMethod>(
+ """
+ @Query("select * from user")
+ abstract ${KotlinTypeNames.SEND_CHANNEL}<User> getUsersChannel();
+ """,
+ jfos = listOf(COMMON.SEND_CHANNEL)
+ ) { _, _ ->
+ }.failsToCompile()
+ .withErrorContaining(ProcessorErrors.invalidChannelType(
+ KotlinTypeNames.SEND_CHANNEL.toString()))
+ }
+
+ @Test
+ fun testBadReceiveChannelReturnForQuery() {
+ singleQueryMethod<QueryMethod>(
+ """
+ @Query("select * from user")
+ abstract ${KotlinTypeNames.RECEIVE_CHANNEL}<User> getUsersChannel();
+ """,
+ jfos = listOf(COMMON.RECEIVE_CHANNEL)
+ ) { _, _ ->
+ }.failsToCompile()
+ .withErrorContaining(ProcessorErrors.invalidChannelType(
+ KotlinTypeNames.RECEIVE_CHANNEL.toString()))
+ }
+
+ @Test
fun query_detectTransaction_select() {
singleQueryMethod<ReadQueryMethod>(
"""
@@ -857,6 +901,7 @@
private fun <T : QueryMethod> singleQueryMethod(
vararg input: String,
+ jfos: Iterable<JavaFileObject> = emptyList(),
handler: (T, TestInvocation) -> Unit
): CompileTester {
return assertAbout(JavaSourcesSubjectFactory.javaSources())
@@ -866,7 +911,7 @@
"foo.bar.MyClass",
DAO_PREFIX + input.joinToString("\n") + DAO_SUFFIX
), COMMON.LIVE_DATA, COMMON.COMPUTABLE_LIVE_DATA, COMMON.USER, COMMON.BOOK
- )
+ ) + jfos
)
.processedWith(TestProcessor.builder()
.forAnnotations(
diff --git a/room/compiler/src/test/kotlin/androidx/room/testing/test_util.kt b/room/compiler/src/test/kotlin/androidx/room/testing/test_util.kt
index 1bf74f5..8e2c368 100644
--- a/room/compiler/src/test/kotlin/androidx/room/testing/test_util.kt
+++ b/room/compiler/src/test/kotlin/androidx/room/testing/test_util.kt
@@ -17,6 +17,7 @@
import androidx.room.DatabaseView
import androidx.room.Entity
import androidx.room.ext.GuavaUtilConcurrentTypeNames
+import androidx.room.ext.KotlinTypeNames
import androidx.room.ext.LifecyclesTypeNames
import androidx.room.ext.PagingTypeNames
import androidx.room.ext.ReactiveStreamsTypeNames
@@ -143,6 +144,21 @@
loadJavaCode("common/input/GuavaRoom.java",
RoomGuavaTypeNames.GUAVA_ROOM.toString())
}
+
+ val CHANNEL by lazy {
+ loadJavaCode("common/input/coroutines/Channel.java",
+ KotlinTypeNames.CHANNEL.toString())
+ }
+
+ val SEND_CHANNEL by lazy {
+ loadJavaCode("common/input/coroutines/SendChannel.java",
+ KotlinTypeNames.SEND_CHANNEL.toString())
+ }
+
+ val RECEIVE_CHANNEL by lazy {
+ loadJavaCode("common/input/coroutines/ReceiveChannel.java",
+ KotlinTypeNames.RECEIVE_CHANNEL.toString())
+ }
}
fun testCodeGenScope(): CodeGenScope {
return CodeGenScope(Mockito.mock(ClassWriter::class.java))
diff --git a/room/integration-tests/kotlintestapp/build.gradle b/room/integration-tests/kotlintestapp/build.gradle
index 61bfc6d..d247001 100644
--- a/room/integration-tests/kotlintestapp/build.gradle
+++ b/room/integration-tests/kotlintestapp/build.gradle
@@ -47,7 +47,7 @@
implementation(project(":arch:core-runtime"))
implementation(project(":lifecycle:lifecycle-livedata"))
implementation(KOTLIN_STDLIB)
- implementation(KOTLIN_COROUTINES)
+ implementation(KOTLIN_COROUTINES_PREVIEW)
kaptAndroidTest project(":room:room-compiler")
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/dao/BooksDao.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/dao/BooksDao.kt
index 290bdb1..8dae07b0 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/dao/BooksDao.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/dao/BooksDao.kt
@@ -33,6 +33,7 @@
import androidx.room.integration.kotlintestapp.vo.BookWithPublisher
import androidx.room.integration.kotlintestapp.vo.DateConverter
import androidx.room.integration.kotlintestapp.vo.Lang
+import androidx.room.integration.kotlintestapp.vo.MiniBook
import androidx.room.integration.kotlintestapp.vo.Publisher
import androidx.room.integration.kotlintestapp.vo.PublisherWithBookSales
import androidx.room.integration.kotlintestapp.vo.PublisherWithBooks
@@ -43,6 +44,7 @@
import io.reactivex.Flowable
import io.reactivex.Maybe
import io.reactivex.Single
+import kotlinx.coroutines.flow.Flow
import java.util.Date
@Dao
@@ -115,6 +117,9 @@
@Insert
fun addBooks(vararg books: Book)
+ @Insert(entity = Book::class)
+ fun addMiniBook(miniBook: MiniBook)
+
@Insert
fun addBookAuthors(vararg bookAuthors: BookAuthor)
@@ -376,4 +381,11 @@
insertBookSuspend(book)
}
}
+
+ @Query("SELECT * FROM book")
+ fun getBooksFlow(): Flow<List<Book>>
+
+ @Transaction
+ @Query("SELECT * FROM book")
+ fun getBooksFlowInTransaction(): Flow<List<Book>>
}
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/FlowQueryTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/FlowQueryTest.kt
new file mode 100644
index 0000000..0abc393
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/FlowQueryTest.kt
@@ -0,0 +1,257 @@
+/*
+ * 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.room.integration.kotlintestapp.test
+
+import androidx.room.integration.kotlintestapp.vo.Book
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.FlowPreview
+import kotlinx.coroutines.async
+import kotlinx.coroutines.cancelAndJoin
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.flow.buffer
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.produceIn
+import kotlinx.coroutines.flow.take
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.yield
+import org.junit.After
+import org.junit.Assert.fail
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.Phaser
+import java.util.concurrent.TimeUnit
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+@FlowPreview
+@ExperimentalCoroutinesApi
+class FlowQueryTest : TestDatabaseTest() {
+
+ @After
+ fun teardown() {
+ // At the end of all tests, query executor should be idle.
+ countingTaskExecutorRule.drainTasks(500, TimeUnit.MILLISECONDS)
+ assertThat(countingTaskExecutorRule.isIdle).isTrue()
+ }
+
+ @Test
+ fun collectBooks_takeOne() = runBlocking {
+ booksDao.addAuthors(TestUtil.AUTHOR_1)
+ booksDao.addPublishers(TestUtil.PUBLISHER)
+ booksDao.addBooks(TestUtil.BOOK_1, TestUtil.BOOK_2)
+
+ booksDao.getBooksFlow().take(1).collect {
+ assertThat(it)
+ .isEqualTo(listOf(TestUtil.BOOK_1, TestUtil.BOOK_2))
+ }
+ }
+
+ @Test
+ fun collectBooks_async() = runBlocking {
+ booksDao.addAuthors(TestUtil.AUTHOR_1)
+ booksDao.addPublishers(TestUtil.PUBLISHER)
+ booksDao.addBooks(TestUtil.BOOK_1, TestUtil.BOOK_2)
+
+ val latch = CountDownLatch(1)
+ val job = async(Dispatchers.IO) {
+ booksDao.getBooksFlow().collect {
+ assertThat(it)
+ .isEqualTo(listOf(TestUtil.BOOK_1, TestUtil.BOOK_2))
+ latch.countDown()
+ }
+ }
+
+ latch.await()
+ job.cancelAndJoin()
+ }
+
+ @Test
+ fun receiveBooks_async_update() = runBlocking {
+ booksDao.addAuthors(TestUtil.AUTHOR_1)
+ booksDao.addPublishers(TestUtil.PUBLISHER)
+ booksDao.addBooks(TestUtil.BOOK_1, TestUtil.BOOK_2)
+
+ val barrier = Phaser(2)
+ val results = mutableListOf<List<Book>>()
+ val job = async(Dispatchers.IO) {
+ booksDao.getBooksFlow().collect {
+ when (results.size) {
+ 0 -> {
+ results.add(it)
+ barrier.arrive()
+ }
+ 1 -> {
+ results.add(it)
+ barrier.arrive()
+ }
+ else -> fail("Should have only collected 2 results.")
+ }
+ }
+ }
+
+ barrier.arriveAndAwaitAdvance()
+ booksDao.insertBookSuspend(TestUtil.BOOK_3)
+
+ barrier.arriveAndAwaitAdvance()
+ assertThat(results.size).isEqualTo(2)
+ assertThat(results[0])
+ .isEqualTo(listOf(TestUtil.BOOK_1, TestUtil.BOOK_2))
+ assertThat(results[1])
+ .isEqualTo(listOf(TestUtil.BOOK_1, TestUtil.BOOK_2, TestUtil.BOOK_3))
+
+ job.cancelAndJoin()
+ }
+
+ @Test
+ fun receiveBooks() = runBlocking {
+ booksDao.addAuthors(TestUtil.AUTHOR_1)
+ booksDao.addPublishers(TestUtil.PUBLISHER)
+ booksDao.addBooks(TestUtil.BOOK_1, TestUtil.BOOK_2)
+
+ val channel = booksDao.getBooksFlow().produceIn(this)
+ assertThat(channel.receive())
+ .isEqualTo(listOf(TestUtil.BOOK_1, TestUtil.BOOK_2))
+ assertThat(channel.isEmpty).isTrue()
+
+ channel.cancel()
+ }
+
+ @Test
+ fun receiveBooks_update() = runBlocking {
+ booksDao.addAuthors(TestUtil.AUTHOR_1)
+ booksDao.addPublishers(TestUtil.PUBLISHER)
+ booksDao.addBooks(TestUtil.BOOK_1, TestUtil.BOOK_2)
+
+ val channel = booksDao.getBooksFlow().produceIn(this)
+
+ assertThat(channel.receive())
+ .isEqualTo(listOf(TestUtil.BOOK_1, TestUtil.BOOK_2))
+
+ booksDao.insertBookSuspend(TestUtil.BOOK_3)
+ drain() // drain async invalidate
+ yield()
+
+ assertThat(channel.receive())
+ .isEqualTo(listOf(TestUtil.BOOK_1, TestUtil.BOOK_2, TestUtil.BOOK_3))
+ assertThat(channel.isEmpty).isTrue()
+
+ channel.cancel()
+ }
+
+ @Test
+ fun receiveBooks_update_multipleChannels() = runBlocking {
+ booksDao.addAuthors(TestUtil.AUTHOR_1)
+ booksDao.addPublishers(TestUtil.PUBLISHER)
+ booksDao.addBooks(TestUtil.BOOK_1, TestUtil.BOOK_2)
+
+ val channels = Array(4) {
+ booksDao.getBooksFlow().produceIn(this)
+ }
+
+ channels.forEach {
+ assertThat(it.receive())
+ .isEqualTo(listOf(TestUtil.BOOK_1, TestUtil.BOOK_2))
+ }
+
+ booksDao.insertBookSuspend(TestUtil.BOOK_3)
+ drain() // drain async invalidate
+ yield()
+
+ channels.forEach {
+ assertThat(it.receive())
+ .isEqualTo(listOf(TestUtil.BOOK_1, TestUtil.BOOK_2, TestUtil.BOOK_3))
+ assertThat(it.isEmpty).isTrue()
+ it.cancel()
+ }
+ }
+
+ @Test
+ fun receiveBooks_update_multipleChannels_inTransaction() = runBlocking {
+ booksDao.addAuthors(TestUtil.AUTHOR_1)
+ booksDao.addPublishers(TestUtil.PUBLISHER)
+ booksDao.addBooks(TestUtil.BOOK_1, TestUtil.BOOK_2)
+
+ val channels = Array(4) {
+ booksDao.getBooksFlowInTransaction().produceIn(this)
+ }
+
+ channels.forEach {
+ assertThat(it.receive())
+ .isEqualTo(listOf(TestUtil.BOOK_1, TestUtil.BOOK_2))
+ }
+
+ booksDao.insertBookSuspend(TestUtil.BOOK_3)
+ drain() // drain async invalidate
+ yield()
+
+ channels.forEach {
+ assertThat(it.receive())
+ .isEqualTo(listOf(TestUtil.BOOK_1, TestUtil.BOOK_2, TestUtil.BOOK_3))
+ assertThat(it.isEmpty).isTrue()
+ it.cancel()
+ }
+ }
+
+ @Test
+ fun receiveBooks_latestUpdateOnly() = runBlocking {
+ booksDao.addAuthors(TestUtil.AUTHOR_1)
+ booksDao.addPublishers(TestUtil.PUBLISHER)
+ booksDao.addBooks(TestUtil.BOOK_1, TestUtil.BOOK_2)
+
+ val channel = booksDao.getBooksFlow().buffer(Channel.CONFLATED).produceIn(this)
+
+ assertThat(channel.receive())
+ .isEqualTo(listOf(TestUtil.BOOK_1, TestUtil.BOOK_2))
+
+ booksDao.insertBookSuspend(TestUtil.BOOK_3)
+ drain() // drain async invalidate
+ yield()
+ booksDao.deleteBookSuspend(TestUtil.BOOK_1)
+ drain() // drain async invalidate
+ yield()
+
+ assertThat(channel.receive())
+ .isEqualTo(listOf(TestUtil.BOOK_2, TestUtil.BOOK_3))
+ assertThat(channel.isEmpty).isTrue()
+
+ channel.cancel()
+ }
+
+ @Test
+ fun receiveBooks_async() = runBlocking {
+ booksDao.addAuthors(TestUtil.AUTHOR_1)
+ booksDao.addPublishers(TestUtil.PUBLISHER)
+ booksDao.addBooks(TestUtil.BOOK_1, TestUtil.BOOK_2)
+
+ val latch = CountDownLatch(1)
+ val job = async(Dispatchers.IO) {
+ for (result in booksDao.getBooksFlow().produceIn(this)) {
+ assertThat(result)
+ .isEqualTo(listOf(TestUtil.BOOK_1, TestUtil.BOOK_2))
+ latch.countDown()
+ }
+ }
+
+ latch.await()
+ job.cancelAndJoin()
+ }
+}
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/Book.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/Book.kt
index 6d7b7a7..21292d5 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/Book.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/Book.kt
@@ -16,6 +16,7 @@
package androidx.room.integration.kotlintestapp.vo
+import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
@@ -30,7 +31,9 @@
@PrimaryKey val bookId: String,
val title: String,
val bookPublisherId: String,
+ @ColumnInfo(defaultValue = "0")
@field:TypeConverters(Lang::class)
val languages: Set<Lang>,
+ @ColumnInfo(defaultValue = "0")
val salesCnt: Int
)
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/JoinedKey.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/MiniBook.kt
similarity index 75%
copy from compose/compose-runtime/src/main/java/androidx/compose/JoinedKey.kt
copy to room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/MiniBook.kt
index 601fbf1..42d013a 100644
--- a/compose/compose-runtime/src/main/java/androidx/compose/JoinedKey.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/MiniBook.kt
@@ -14,9 +14,12 @@
* limitations under the License.
*/
-package androidx.compose
+package androidx.room.integration.kotlintestapp.vo
-internal data class JoinedKey(
- @JvmField val left: Any?,
- @JvmField val right: Any?
+import androidx.room.PrimaryKey
+
+data class MiniBook(
+ @PrimaryKey val bookId: String,
+ val title: String,
+ val bookPublisherId: String
)
\ No newline at end of file
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/TestDatabase.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/TestDatabase.java
index f32c5cc..8e36cd8 100644
--- a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/TestDatabase.java
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/TestDatabase.java
@@ -22,6 +22,7 @@
import androidx.room.TypeConverters;
import androidx.room.integration.testapp.dao.BlobEntityDao;
import androidx.room.integration.testapp.dao.FunnyNamedDao;
+import androidx.room.integration.testapp.dao.LibraryItemDao;
import androidx.room.integration.testapp.dao.PetCoupleDao;
import androidx.room.integration.testapp.dao.PetDao;
import androidx.room.integration.testapp.dao.ProductDao;
@@ -45,6 +46,7 @@
import androidx.room.integration.testapp.vo.PetWithUser;
import androidx.room.integration.testapp.vo.Product;
import androidx.room.integration.testapp.vo.Robot;
+import androidx.room.integration.testapp.vo.RoomLibraryPojo;
import androidx.room.integration.testapp.vo.School;
import androidx.room.integration.testapp.vo.Toy;
import androidx.room.integration.testapp.vo.User;
@@ -57,7 +59,7 @@
@Database(entities = {User.class, Pet.class, School.class, PetCouple.class, Toy.class,
BlobEntity.class, Product.class, FunnyNamedEntity.class, House.class,
- FriendsJunction.class, Hivemind.class, Robot.class},
+ FriendsJunction.class, Hivemind.class, Robot.class, RoomLibraryPojo.class},
views = {PetWithUser.class},
version = 1, exportSchema = false)
@TypeConverters(TestDatabase.Converters.class)
@@ -76,6 +78,7 @@
public abstract RawDao getRawDao();
public abstract UserHouseDao getUserHouseDao();
public abstract RobotsDao getRobotsDao();
+ public abstract LibraryItemDao getLibraryItemDao();
@SuppressWarnings("unused")
public static class Converters {
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/LibraryItemDao.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/LibraryItemDao.java
new file mode 100644
index 0000000..5f9bcb0
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/LibraryItemDao.java
@@ -0,0 +1,44 @@
+/*
+ * 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.room.integration.testapp.dao;
+
+import androidx.room.Dao;
+import androidx.room.Delete;
+import androidx.room.Insert;
+import androidx.room.Query;
+import androidx.room.integration.testapp.vo.RoomLibraryPojo;
+
+import java.util.List;
+
+@Dao
+public interface LibraryItemDao {
+
+ @Query("SELECT mId, mName, mPrice FROM library_items")
+ List<RoomLibraryPojo> getAll();
+
+ @Query("SELECT mId, mName, mPrice FROM library_items WHERE mId IN (:ids)")
+ List<RoomLibraryPojo> loadAllByIds(int[] ids);
+
+ @Query("SELECT mId, mName, mPrice FROM library_items WHERE mName LIKE :name LIMIT 1")
+ RoomLibraryPojo findByName(String name);
+
+ @Insert
+ void insertAll(RoomLibraryPojo... item);
+
+ @Delete
+ void delete(RoomLibraryPojo item);
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/LibraryPojo.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/LibraryPojo.java
new file mode 100644
index 0000000..c9852f7
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/LibraryPojo.java
@@ -0,0 +1,64 @@
+/*
+ * 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.room.integration.testapp.vo;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * A unmodifiable POJO that comes from a library.
+ */
+public class LibraryPojo {
+
+ private final JSONObject mJsonObj = new JSONObject();
+
+ public Long getPrice() {
+ return mJsonObj.optLong("price");
+ }
+
+ public Long getId() {
+ return mJsonObj.optLong("id");
+ }
+
+ public String getName() {
+ return mJsonObj.optString("name");
+ }
+
+ public void setPrice(Long price) {
+ try {
+ mJsonObj.put("price", price);
+ } catch (JSONException e) {
+ // ignored
+ }
+ }
+
+ public void setId(long id) {
+ try {
+ mJsonObj.put("id", id);
+ } catch (JSONException e) {
+ // ignored
+ }
+ }
+
+ public void setName(String name) {
+ try {
+ mJsonObj.put("name", name);
+ } catch (JSONException e) {
+ // ignored
+ }
+ }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/RoomLibraryPojo.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/RoomLibraryPojo.java
new file mode 100644
index 0000000..fc02ba4
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/RoomLibraryPojo.java
@@ -0,0 +1,39 @@
+/*
+ * 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.room.integration.testapp.vo;
+
+import androidx.room.Entity;
+import androidx.room.PrimaryKey;
+
+/**
+ * A Room POJO is just wraps a POJO provided by some library.
+ */
+@Entity(tableName = "library_items", ignoredColumns = "mJsonObj")
+public class RoomLibraryPojo extends LibraryPojo {
+
+ @PrimaryKey
+ private long mId;
+ private String mName;
+ private Long mPrice;
+
+ public RoomLibraryPojo(long id, String name, Long price) {
+ setId(id);
+ setName(name);
+ setPrice(price);
+ }
+
+}
diff --git a/room/ktx/api/restricted_2.2.0-alpha01.txt b/room/ktx/api/restricted_2.2.0-alpha01.txt
index dc4a42bf..20837edc 100644
--- a/room/ktx/api/restricted_2.2.0-alpha01.txt
+++ b/room/ktx/api/restricted_2.2.0-alpha01.txt
@@ -2,11 +2,13 @@
package androidx.room {
@RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class CoroutinesRoom {
+ method public static <R> kotlinx.coroutines.flow.Flow<R> createFlow(androidx.room.RoomDatabase db, boolean inTransaction, String![] tableNames, java.util.concurrent.Callable<R> callable);
method public static suspend <R> Object! execute(androidx.room.RoomDatabase p, boolean db, java.util.concurrent.Callable<R> inTransaction, kotlin.coroutines.Continuation<? super R> callable);
field public static final androidx.room.CoroutinesRoom.Companion! Companion;
}
public static final class CoroutinesRoom.Companion {
+ method public <R> kotlinx.coroutines.flow.Flow<R> createFlow(androidx.room.RoomDatabase db, boolean inTransaction, String![] tableNames, java.util.concurrent.Callable<R> callable);
method public suspend <R> Object! execute(androidx.room.RoomDatabase db, boolean inTransaction, java.util.concurrent.Callable<R> callable, kotlin.coroutines.Continuation<? super R> p);
}
diff --git a/room/ktx/api/restricted_2.2.0-alpha02.txt b/room/ktx/api/restricted_2.2.0-alpha02.txt
index dc4a42bf..20837edc 100644
--- a/room/ktx/api/restricted_2.2.0-alpha02.txt
+++ b/room/ktx/api/restricted_2.2.0-alpha02.txt
@@ -2,11 +2,13 @@
package androidx.room {
@RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class CoroutinesRoom {
+ method public static <R> kotlinx.coroutines.flow.Flow<R> createFlow(androidx.room.RoomDatabase db, boolean inTransaction, String![] tableNames, java.util.concurrent.Callable<R> callable);
method public static suspend <R> Object! execute(androidx.room.RoomDatabase p, boolean db, java.util.concurrent.Callable<R> inTransaction, kotlin.coroutines.Continuation<? super R> callable);
field public static final androidx.room.CoroutinesRoom.Companion! Companion;
}
public static final class CoroutinesRoom.Companion {
+ method public <R> kotlinx.coroutines.flow.Flow<R> createFlow(androidx.room.RoomDatabase db, boolean inTransaction, String![] tableNames, java.util.concurrent.Callable<R> callable);
method public suspend <R> Object! execute(androidx.room.RoomDatabase db, boolean inTransaction, java.util.concurrent.Callable<R> callable, kotlin.coroutines.Continuation<? super R> p);
}
diff --git a/room/ktx/api/restricted_current.txt b/room/ktx/api/restricted_current.txt
index dc4a42bf..20837edc 100644
--- a/room/ktx/api/restricted_current.txt
+++ b/room/ktx/api/restricted_current.txt
@@ -2,11 +2,13 @@
package androidx.room {
@RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class CoroutinesRoom {
+ method public static <R> kotlinx.coroutines.flow.Flow<R> createFlow(androidx.room.RoomDatabase db, boolean inTransaction, String![] tableNames, java.util.concurrent.Callable<R> callable);
method public static suspend <R> Object! execute(androidx.room.RoomDatabase p, boolean db, java.util.concurrent.Callable<R> inTransaction, kotlin.coroutines.Continuation<? super R> callable);
field public static final androidx.room.CoroutinesRoom.Companion! Companion;
}
public static final class CoroutinesRoom.Companion {
+ method public <R> kotlinx.coroutines.flow.Flow<R> createFlow(androidx.room.RoomDatabase db, boolean inTransaction, String![] tableNames, java.util.concurrent.Callable<R> callable);
method public suspend <R> Object! execute(androidx.room.RoomDatabase db, boolean inTransaction, java.util.concurrent.Callable<R> callable, kotlin.coroutines.Continuation<? super R> p);
}
diff --git a/room/ktx/build.gradle b/room/ktx/build.gradle
index 3a13204..6615260 100644
--- a/room/ktx/build.gradle
+++ b/room/ktx/build.gradle
@@ -30,9 +30,11 @@
api(project(":room:room-common"))
api(project(":room:room-runtime"))
api(KOTLIN_STDLIB)
- api(KOTLIN_COROUTINES)
+ api(KOTLIN_COROUTINES_PREVIEW)
testImplementation(JUNIT)
testImplementation(MOCKITO_CORE)
+ testImplementation(TRUTH)
+ testImplementation(ARCH_LIFECYCLE_LIVEDATA_CORE)
}
androidx {
diff --git a/room/ktx/src/main/java/androidx/room/CoroutinesRoom.kt b/room/ktx/src/main/java/androidx/room/CoroutinesRoom.kt
index d38ed58..c7befed 100644
--- a/room/ktx/src/main/java/androidx/room/CoroutinesRoom.kt
+++ b/room/ktx/src/main/java/androidx/room/CoroutinesRoom.kt
@@ -19,6 +19,9 @@
import androidx.annotation.RestrictTo
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.asCoroutineDispatcher
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.withContext
import java.util.concurrent.Callable
import kotlin.coroutines.coroutineContext
@@ -51,6 +54,38 @@
callable.call()
}
}
+
+ @JvmStatic
+ fun <R> createFlow(
+ db: RoomDatabase,
+ inTransaction: Boolean,
+ tableNames: Array<String>,
+ callable: Callable<R>
+ ): Flow<@JvmSuppressWildcards R> = flow {
+ // Observer channel receives signals from the invalidation tracker to emit queries.
+ val observerChannel = Channel<Unit>(Channel.CONFLATED)
+ val observer = object : InvalidationTracker.Observer(tableNames) {
+ override fun onInvalidated(tables: MutableSet<String>) {
+ observerChannel.offer(Unit)
+ }
+ }
+ observerChannel.offer(Unit) // Initial signal to perform first query.
+ val flowContext = coroutineContext
+ val queryContext = if (inTransaction) db.transactionDispatcher else db.queryDispatcher
+ withContext(queryContext) {
+ db.invalidationTracker.addObserver(observer)
+ try {
+ // Iterate until cancelled, transforming observer signals to query results to
+ // be emitted to the flow.
+ for (signal in observerChannel) {
+ val result = callable.call()
+ withContext(flowContext) { emit(result) }
+ }
+ } finally {
+ db.invalidationTracker.removeObserver(observer)
+ }
+ }
+ }
}
}
@@ -71,5 +106,5 @@
*/
internal val RoomDatabase.transactionDispatcher: CoroutineDispatcher
get() = backingFieldMap.getOrPut("TransactionDispatcher") {
- queryExecutor.asCoroutineDispatcher()
+ transactionExecutor.asCoroutineDispatcher()
} as CoroutineDispatcher
diff --git a/room/ktx/src/test/java/androidx/room/CoroutinesRoomTest.kt b/room/ktx/src/test/java/androidx/room/CoroutinesRoomTest.kt
new file mode 100644
index 0000000..ad1841f
--- /dev/null
+++ b/room/ktx/src/test/java/androidx/room/CoroutinesRoomTest.kt
@@ -0,0 +1,97 @@
+/*
+ * 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.room
+
+import androidx.sqlite.db.SupportSQLiteOpenHelper
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.FlowPreview
+import kotlinx.coroutines.async
+import kotlinx.coroutines.cancelAndJoin
+import kotlinx.coroutines.flow.single
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.yield
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import java.util.concurrent.Callable
+import kotlin.coroutines.ContinuationInterceptor
+
+@FlowPreview
+@RunWith(JUnit4::class)
+class CoroutinesRoomTest {
+
+ private val database = TestDatabase()
+ private val invalidationTracker = database.invalidationTracker as TestInvalidationTracker
+
+ @Test
+ fun testCreateFlow() = testRun {
+ var callableExecuted = false
+ val flow = CoroutinesRoom.createFlow(
+ db = database,
+ inTransaction = false,
+ tableNames = arrayOf("Pet"),
+ callable = Callable { callableExecuted = true }
+ )
+
+ assertThat(invalidationTracker.observers.isEmpty()).isTrue()
+ assertThat(callableExecuted).isFalse()
+
+ val job = async {
+ flow.single()
+ }
+ yield(); yield() // yield for async and flow
+
+ assertThat(invalidationTracker.observers.size).isEqualTo(1)
+ assertThat(callableExecuted).isTrue()
+
+ job.cancelAndJoin()
+ assertThat(invalidationTracker.observers.isEmpty()).isTrue()
+ }
+
+ // Use runBlocking dispatcher as query dispatchers, keeps the tests consistent.
+ private fun testRun(block: suspend CoroutineScope.() -> Unit) = runBlocking {
+ database.backingFieldMap["QueryDispatcher"] = coroutineContext[ContinuationInterceptor]
+ block.invoke(this)
+ }
+
+ private class TestDatabase : RoomDatabase() {
+ override fun createOpenHelper(config: DatabaseConfiguration?): SupportSQLiteOpenHelper {
+ throw UnsupportedOperationException("Shouldn't be called!")
+ }
+
+ override fun createInvalidationTracker(): InvalidationTracker {
+ return TestInvalidationTracker(this)
+ }
+
+ override fun clearAllTables() {
+ throw UnsupportedOperationException("Shouldn't be called!")
+ }
+ }
+
+ private class TestInvalidationTracker(db: RoomDatabase) : InvalidationTracker(db) {
+ val observers = mutableListOf<Observer>()
+
+ override fun addObserver(observer: Observer) {
+ observers.add(observer)
+ }
+
+ override fun removeObserver(observer: Observer) {
+ observers.remove(observer)
+ }
+ }
+}
diff --git a/room/runtime/api/2.2.0-alpha02.txt b/room/runtime/api/2.2.0-alpha02.txt
index 7c34026..1d210e5 100644
--- a/room/runtime/api/2.2.0-alpha02.txt
+++ b/room/runtime/api/2.2.0-alpha02.txt
@@ -56,8 +56,9 @@
method @CallSuper public void init(androidx.room.DatabaseConfiguration);
method protected void internalInitInvalidationTracker(androidx.sqlite.db.SupportSQLiteDatabase);
method public boolean isOpen();
- method public android.database.Cursor! query(String!, Object![]?);
- method public android.database.Cursor! query(androidx.sqlite.db.SupportSQLiteQuery!);
+ method public android.database.Cursor query(String, Object![]?);
+ method public android.database.Cursor query(androidx.sqlite.db.SupportSQLiteQuery);
+ method public android.database.Cursor query(androidx.sqlite.db.SupportSQLiteQuery, android.os.CancellationSignal?);
method public void runInTransaction(Runnable);
method public <V> V! runInTransaction(java.util.concurrent.Callable<V!>);
method @Deprecated public void setTransactionSuccessful();
diff --git a/room/runtime/api/current.txt b/room/runtime/api/current.txt
index 7c34026..1d210e5 100644
--- a/room/runtime/api/current.txt
+++ b/room/runtime/api/current.txt
@@ -56,8 +56,9 @@
method @CallSuper public void init(androidx.room.DatabaseConfiguration);
method protected void internalInitInvalidationTracker(androidx.sqlite.db.SupportSQLiteDatabase);
method public boolean isOpen();
- method public android.database.Cursor! query(String!, Object![]?);
- method public android.database.Cursor! query(androidx.sqlite.db.SupportSQLiteQuery!);
+ method public android.database.Cursor query(String, Object![]?);
+ method public android.database.Cursor query(androidx.sqlite.db.SupportSQLiteQuery);
+ method public android.database.Cursor query(androidx.sqlite.db.SupportSQLiteQuery, android.os.CancellationSignal?);
method public void runInTransaction(Runnable);
method public <V> V! runInTransaction(java.util.concurrent.Callable<V!>);
method @Deprecated public void setTransactionSuccessful();
diff --git a/room/runtime/api/restricted_2.2.0-alpha02.txt b/room/runtime/api/restricted_2.2.0-alpha02.txt
index 170506d..8e744d7 100644
--- a/room/runtime/api/restricted_2.2.0-alpha02.txt
+++ b/room/runtime/api/restricted_2.2.0-alpha02.txt
@@ -95,8 +95,9 @@
method @CallSuper public void init(androidx.room.DatabaseConfiguration);
method protected void internalInitInvalidationTracker(androidx.sqlite.db.SupportSQLiteDatabase);
method public boolean isOpen();
- method public android.database.Cursor! query(String!, Object![]?);
- method public android.database.Cursor! query(androidx.sqlite.db.SupportSQLiteQuery!);
+ method public android.database.Cursor query(String, Object![]?);
+ method public android.database.Cursor query(androidx.sqlite.db.SupportSQLiteQuery);
+ method public android.database.Cursor query(androidx.sqlite.db.SupportSQLiteQuery, android.os.CancellationSignal?);
method public void runInTransaction(Runnable);
method public <V> V! runInTransaction(java.util.concurrent.Callable<V!>);
method @Deprecated public void setTransactionSuccessful();
@@ -233,7 +234,8 @@
@RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class DBUtil {
method public static void dropFtsSyncTriggers(androidx.sqlite.db.SupportSQLiteDatabase!);
- method public static android.database.Cursor query(androidx.room.RoomDatabase!, androidx.sqlite.db.SupportSQLiteQuery!, boolean);
+ method @Deprecated public static android.database.Cursor query(androidx.room.RoomDatabase!, androidx.sqlite.db.SupportSQLiteQuery!, boolean);
+ method public static android.database.Cursor query(androidx.room.RoomDatabase, androidx.sqlite.db.SupportSQLiteQuery, boolean, android.os.CancellationSignal?);
method public static int readVersion(java.io.File) throws java.io.IOException;
}
diff --git a/room/runtime/api/restricted_current.txt b/room/runtime/api/restricted_current.txt
index 170506d..8e744d7 100644
--- a/room/runtime/api/restricted_current.txt
+++ b/room/runtime/api/restricted_current.txt
@@ -95,8 +95,9 @@
method @CallSuper public void init(androidx.room.DatabaseConfiguration);
method protected void internalInitInvalidationTracker(androidx.sqlite.db.SupportSQLiteDatabase);
method public boolean isOpen();
- method public android.database.Cursor! query(String!, Object![]?);
- method public android.database.Cursor! query(androidx.sqlite.db.SupportSQLiteQuery!);
+ method public android.database.Cursor query(String, Object![]?);
+ method public android.database.Cursor query(androidx.sqlite.db.SupportSQLiteQuery);
+ method public android.database.Cursor query(androidx.sqlite.db.SupportSQLiteQuery, android.os.CancellationSignal?);
method public void runInTransaction(Runnable);
method public <V> V! runInTransaction(java.util.concurrent.Callable<V!>);
method @Deprecated public void setTransactionSuccessful();
@@ -233,7 +234,8 @@
@RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class DBUtil {
method public static void dropFtsSyncTriggers(androidx.sqlite.db.SupportSQLiteDatabase!);
- method public static android.database.Cursor query(androidx.room.RoomDatabase!, androidx.sqlite.db.SupportSQLiteQuery!, boolean);
+ method @Deprecated public static android.database.Cursor query(androidx.room.RoomDatabase!, androidx.sqlite.db.SupportSQLiteQuery!, boolean);
+ method public static android.database.Cursor query(androidx.room.RoomDatabase, androidx.sqlite.db.SupportSQLiteQuery, boolean, android.os.CancellationSignal?);
method public static int readVersion(java.io.File) throws java.io.IOException;
}
diff --git a/room/runtime/src/main/java/androidx/room/RoomDatabase.java b/room/runtime/src/main/java/androidx/room/RoomDatabase.java
index 2e88a28..b038c89 100644
--- a/room/runtime/src/main/java/androidx/room/RoomDatabase.java
+++ b/room/runtime/src/main/java/androidx/room/RoomDatabase.java
@@ -21,6 +21,7 @@
import android.content.Context;
import android.database.Cursor;
import android.os.Build;
+import android.os.CancellationSignal;
import android.os.Looper;
import android.util.Log;
@@ -294,7 +295,8 @@
* @param args The bind arguments for the placeholders in the query
* @return A Cursor obtained by running the given query in the Room database.
*/
- public Cursor query(String query, @Nullable Object[] args) {
+ @NonNull
+ public Cursor query(@NonNull String query, @Nullable Object[] args) {
return mOpenHelper.getWritableDatabase().query(new SimpleSQLiteQuery(query, args));
}
@@ -304,10 +306,27 @@
* @param query The Query which includes the SQL and a bind callback for bind arguments.
* @return Result of the query.
*/
- public Cursor query(SupportSQLiteQuery query) {
+ @NonNull
+ public Cursor query(@NonNull SupportSQLiteQuery query) {
+ return query(query, null);
+ }
+
+ /**
+ * Wrapper for {@link SupportSQLiteDatabase#query(SupportSQLiteQuery)}.
+ *
+ * @param query The Query which includes the SQL and a bind callback for bind arguments.
+ * @param signal The cancellation signal to be attached to the query.
+ * @return Result of the query.
+ */
+ @NonNull
+ public Cursor query(@NonNull SupportSQLiteQuery query, @Nullable CancellationSignal signal) {
assertNotMainThread();
assertNotSuspendingTransaction();
- return mOpenHelper.getWritableDatabase().query(query);
+ if (signal != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ return mOpenHelper.getWritableDatabase().query(query, signal);
+ } else {
+ return mOpenHelper.getWritableDatabase().query(query);
+ }
}
/**
diff --git a/room/runtime/src/main/java/androidx/room/util/DBUtil.java b/room/runtime/src/main/java/androidx/room/util/DBUtil.java
index f7afd26..f802e6d 100644
--- a/room/runtime/src/main/java/androidx/room/util/DBUtil.java
+++ b/room/runtime/src/main/java/androidx/room/util/DBUtil.java
@@ -19,8 +19,10 @@
import android.database.AbstractWindowedCursor;
import android.database.Cursor;
import android.os.Build;
+import android.os.CancellationSignal;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.room.RoomDatabase;
import androidx.sqlite.db.SupportSQLiteDatabase;
@@ -53,10 +55,32 @@
* @param sqLiteQuery The query to perform.
* @param maybeCopy True if the result cursor should maybe be copied, false otherwise.
* @return Result of the query.
+ *
+ * @deprecated This is only used in the generated code and shouldn't be called directly.
*/
+ @Deprecated
@NonNull
public static Cursor query(RoomDatabase db, SupportSQLiteQuery sqLiteQuery, boolean maybeCopy) {
- final Cursor cursor = db.query(sqLiteQuery);
+ return query(db, sqLiteQuery, maybeCopy, null);
+ }
+
+ /**
+ * Performs the SQLiteQuery on the given database.
+ * <p>
+ * This util method encapsulates copying the cursor if the {@code maybeCopy} parameter is
+ * {@code true} and either the api level is below a certain threshold or the full result of the
+ * query does not fit in a single window.
+ *
+ * @param db The database to perform the query on.
+ * @param sqLiteQuery The query to perform.
+ * @param maybeCopy True if the result cursor should maybe be copied, false otherwise.
+ * @param signal The cancellation signal to be attached to the query.
+ * @return Result of the query.
+ */
+ @NonNull
+ public static Cursor query(@NonNull RoomDatabase db, @NonNull SupportSQLiteQuery sqLiteQuery,
+ boolean maybeCopy, @Nullable CancellationSignal signal) {
+ final Cursor cursor = db.query(sqLiteQuery, signal);
if (maybeCopy && cursor instanceof AbstractWindowedCursor) {
AbstractWindowedCursor windowedCursor = (AbstractWindowedCursor) cursor;
int rowsInCursor = windowedCursor.getCount(); // Should fill the window.
diff --git a/room/runtime/src/test/java/androidx/room/InvalidationTrackerTest.java b/room/runtime/src/test/java/androidx/room/InvalidationTrackerTest.java
index f3caf34..f5026e1 100644
--- a/room/runtime/src/test/java/androidx/room/InvalidationTrackerTest.java
+++ b/room/runtime/src/test/java/androidx/room/InvalidationTrackerTest.java
@@ -187,7 +187,8 @@
@Test
public void refreshCheckTasks() throws Exception {
- when(mRoomDatabase.query(any(SimpleSQLiteQuery.class))).thenReturn(mock(Cursor.class));
+ when(mRoomDatabase.query(any(SimpleSQLiteQuery.class)))
+ .thenReturn(mock(Cursor.class));
mTracker.refreshVersionsAsync();
mTracker.refreshVersionsAsync();
verify(mTaskExecutorRule.getTaskExecutor()).executeOnDiskIO(mTracker.mRefreshRunnable);
diff --git a/samples/Support4Demos/src/main/java/com/example/android/supportv4/widget/BaseSwipeRefreshLayoutActivity.java b/samples/Support4Demos/src/main/java/com/example/android/supportv4/widget/BaseSwipeRefreshLayoutActivity.java
index 0c30d5c..4e926c7 100644
--- a/samples/Support4Demos/src/main/java/com/example/android/supportv4/widget/BaseSwipeRefreshLayoutActivity.java
+++ b/samples/Support4Demos/src/main/java/com/example/android/supportv4/widget/BaseSwipeRefreshLayoutActivity.java
@@ -132,8 +132,7 @@
setContentView(getLayoutId());
// TODO use by viewModels() once this class switches to Kotlin
- mViewModel = new ViewModelProvider(this,
- new ViewModelProvider.NewInstanceFactory()).get(MyViewModel.class);
+ mViewModel = new ViewModelProvider(this).get(MyViewModel.class);
mViewModel.refreshDone.observe(this, event -> {
if (event.getContentIfNotHandled() != null) {
mSwipeRefreshWidget.setRefreshing(false);
diff --git a/security/crypto/api/api_lint.ignore b/security/crypto/api/api_lint.ignore
index 2635915..0b681f2 100644
--- a/security/crypto/api/api_lint.ignore
+++ b/security/crypto/api/api_lint.ignore
@@ -5,10 +5,6 @@
Context is distinct, so it must be the first argument (method `create`)
-KotlinOperator: androidx.security.crypto.EncryptedSharedPreferences#contains(String):
- Method can be invoked as a "in" operator from Kotlin: `contains` (this is usually desirable; just make sure it makes sense for this type of object)
-
-
RegistrationName: androidx.security.crypto.EncryptedSharedPreferences#registerOnSharedPreferenceChangeListener(android.content.SharedPreferences.OnSharedPreferenceChangeListener):
Listener methods should be named add/remove; was registerOnSharedPreferenceChangeListener
RegistrationName: androidx.security.crypto.EncryptedSharedPreferences#unregisterOnSharedPreferenceChangeListener(android.content.SharedPreferences.OnSharedPreferenceChangeListener):
diff --git a/settings.gradle b/settings.gradle
index f94aa3f9..f13d7b3 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -39,6 +39,10 @@
includeProject(":activity:activity-ktx", "activity/activity-ktx")
includeProject(":activity:integration-tests:testapp", "activity/integration-tests/testapp")
includeProject(":ads-identifier", "ads/ads-identifier")
+includeProject(":ads-identifier:integration-tests:testapp", "ads/ads-identifier/integration-tests/testapp")
+includeProject(":ads-identifier-common", "ads/ads-identifier-common")
+includeProject(":ads-identifier-provider", "ads/ads-identifier-provider")
+includeProject(":ads-identifier-provider:integration-tests:testapp", "ads/ads-identifier-provider/integration-tests/testapp")
includeProject(":annotation:annotation", "annotation/annotation")
includeProject(":annotation:annotation-sampled", "annotation/annotation-sampled")
includeProject(":annotation:annotation-experimental", "annotation/annotation-experimental")
@@ -55,12 +59,16 @@
includeProject(":arch:core-runtime", "arch/core-runtime")
includeProject(":asynclayoutinflater", "asynclayoutinflater")
includeProject(":autofill", "autofill")
-includeProject(":benchmark", "benchmark")
+includeProject(":benchmark:benchmark-common", "benchmark/common")
+includeProject(":benchmark:benchmark-junit4", "benchmark/junit4")
includeProject(":benchmark:benchmark-benchmark", "benchmark/benchmark")
includeProject(":benchmark:benchmark-gradle-plugin", "benchmark/gradle-plugin")
+includeProject(":benchmark:integration-tests:dry-run-benchmark", "benchmark/integration-tests/dry-run-benchmark")
includeProject(":benchmark:integration-tests:startup-benchmark", "benchmark/integration-tests/startup-benchmark")
includeProject(":biometric", "biometric")
includeProject(":browser", "browser")
+includeProject(":buildSrc-tests", "buildSrc-tests")
+includeProject(":buildSrc-tests:lint-checks", "buildSrc-tests/lint-checks")
includeProject(":camera:camera-camera2", "camera/camera-camera2")
includeProject(":camera:camera-core", "camera/camera-core")
includeProject(":camera:camera-extensions", "camera/camera-extensions")
@@ -226,6 +234,7 @@
includeProject(":work:work-runtime-ktx", "work/workmanager-ktx")
includeProject(":work:work-rxjava2", "work/workmanager-rxjava2")
includeProject(":work:work-testing", "work/workmanager-testing")
+includeProject(":work:work-benchmark", "work/workmanager-benchmark")
includeProject(":work:integration-tests:testapp", "work/integration-tests/testapp")
/////////////////////////////
diff --git a/slices/benchmark/build.gradle b/slices/benchmark/build.gradle
index b063a7b..f7cb0db 100644
--- a/slices/benchmark/build.gradle
+++ b/slices/benchmark/build.gradle
@@ -21,6 +21,7 @@
plugins {
id("AndroidXPlugin")
id("com.android.library")
+ id("androidx.benchmark")
}
dependencies {
@@ -28,7 +29,7 @@
androidTestImplementation(project(":slice-view"))
androidTestImplementation(project(":slice-core"))
androidTestImplementation(project(":slice-builders"))
- androidTestImplementation(project(":benchmark"))
+ androidTestImplementation(project(":benchmark:benchmark-junit4"))
androidTestImplementation(JUNIT)
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
androidTestImplementation(ANDROIDX_TEST_CORE)
diff --git a/slices/benchmark/src/androidTest/java/androidx/slice/SliceSerializeMetrics.java b/slices/benchmark/src/androidTest/java/androidx/slice/SliceSerializeMetrics.java
index 0b26fbd..de9e87a 100644
--- a/slices/benchmark/src/androidTest/java/androidx/slice/SliceSerializeMetrics.java
+++ b/slices/benchmark/src/androidTest/java/androidx/slice/SliceSerializeMetrics.java
@@ -32,8 +32,8 @@
import android.graphics.Canvas;
import android.net.Uri;
-import androidx.benchmark.BenchmarkRule;
import androidx.benchmark.BenchmarkState;
+import androidx.benchmark.junit4.BenchmarkRule;
import androidx.core.graphics.drawable.IconCompat;
import androidx.slice.benchmark.test.R;
import androidx.slice.core.SliceHints;
diff --git a/slices/benchmark/src/androidTest/java/androidx/slice/SliceViewMetrics.java b/slices/benchmark/src/androidTest/java/androidx/slice/SliceViewMetrics.java
index 43143e1..05482e8 100644
--- a/slices/benchmark/src/androidTest/java/androidx/slice/SliceViewMetrics.java
+++ b/slices/benchmark/src/androidTest/java/androidx/slice/SliceViewMetrics.java
@@ -20,8 +20,8 @@
import android.content.Context;
import android.net.Uri;
-import androidx.benchmark.BenchmarkRule;
import androidx.benchmark.BenchmarkState;
+import androidx.benchmark.junit4.BenchmarkRule;
import androidx.slice.widget.SliceView;
import androidx.test.annotation.UiThreadTest;
import androidx.test.core.app.ApplicationProvider;
diff --git a/textclassifier/api/1.0.0-alpha03.txt b/textclassifier/api/1.0.0-alpha03.txt
index 974299f..21567f3 100644
--- a/textclassifier/api/1.0.0-alpha03.txt
+++ b/textclassifier/api/1.0.0-alpha03.txt
@@ -2,7 +2,7 @@
package androidx.textclassifier {
public final class ConversationAction {
- method public static androidx.textclassifier.ConversationAction createFromBundle(android.os.Bundle!);
+ method public static androidx.textclassifier.ConversationAction createFromBundle(android.os.Bundle);
method public androidx.core.app.RemoteActionCompat? getAction();
method @FloatRange(from=0, to=1) public float getConfidenceScore();
method public android.os.Bundle getExtras();
diff --git a/textclassifier/api/current.txt b/textclassifier/api/current.txt
index 974299f..21567f3 100644
--- a/textclassifier/api/current.txt
+++ b/textclassifier/api/current.txt
@@ -2,7 +2,7 @@
package androidx.textclassifier {
public final class ConversationAction {
- method public static androidx.textclassifier.ConversationAction createFromBundle(android.os.Bundle!);
+ method public static androidx.textclassifier.ConversationAction createFromBundle(android.os.Bundle);
method public androidx.core.app.RemoteActionCompat? getAction();
method @FloatRange(from=0, to=1) public float getConfidenceScore();
method public android.os.Bundle getExtras();
diff --git a/textclassifier/api/restricted_1.0.0-alpha03.txt b/textclassifier/api/restricted_1.0.0-alpha03.txt
index e335921..2bfef3d 100644
--- a/textclassifier/api/restricted_1.0.0-alpha03.txt
+++ b/textclassifier/api/restricted_1.0.0-alpha03.txt
@@ -2,7 +2,7 @@
package androidx.textclassifier {
public final class ConversationAction {
- method public static androidx.textclassifier.ConversationAction createFromBundle(android.os.Bundle!);
+ method public static androidx.textclassifier.ConversationAction createFromBundle(android.os.Bundle);
method public androidx.core.app.RemoteActionCompat? getAction();
method @FloatRange(from=0, to=1) public float getConfidenceScore();
method public android.os.Bundle getExtras();
diff --git a/textclassifier/api/restricted_current.txt b/textclassifier/api/restricted_current.txt
index e335921..2bfef3d 100644
--- a/textclassifier/api/restricted_current.txt
+++ b/textclassifier/api/restricted_current.txt
@@ -2,7 +2,7 @@
package androidx.textclassifier {
public final class ConversationAction {
- method public static androidx.textclassifier.ConversationAction createFromBundle(android.os.Bundle!);
+ method public static androidx.textclassifier.ConversationAction createFromBundle(android.os.Bundle);
method public androidx.core.app.RemoteActionCompat? getAction();
method @FloatRange(from=0, to=1) public float getConfidenceScore();
method public android.os.Bundle getExtras();
diff --git a/textclassifier/src/main/java/androidx/textclassifier/ConversationAction.java b/textclassifier/src/main/java/androidx/textclassifier/ConversationAction.java
index 9aa9288..866e49c 100644
--- a/textclassifier/src/main/java/androidx/textclassifier/ConversationAction.java
+++ b/textclassifier/src/main/java/androidx/textclassifier/ConversationAction.java
@@ -32,7 +32,12 @@
import java.lang.annotation.Retention;
-/** Represents the action suggested by a {@link TextClassifier} on a given conversation. */
+/**
+ * Represents an action suggested by a {@link TextClassifier} on a given conversation.
+ *
+ * @see TextClassifier#suggestConversationActions(ConversationActions.Request)
+ * @see ConversationActions
+ */
public final class ConversationAction {
private static final String EXTRA_TYPE = "type";
@@ -214,7 +219,7 @@
* Converts a bundle that was created using {@link #toBundle()} to a {@link ConversationAction}.
*/
@NonNull
- public static ConversationAction createFromBundle(Bundle bundle) {
+ public static ConversationAction createFromBundle(@NonNull Bundle bundle) {
return new ConversationAction(
bundle.getString(EXTRA_TYPE),
(RemoteActionCompat) ParcelUtils.getVersionedParcelable(bundle, EXTRA_ACTION),
diff --git a/textclassifier/src/main/java/androidx/textclassifier/ConversationActions.java b/textclassifier/src/main/java/androidx/textclassifier/ConversationActions.java
index d7129dc..3ba6f20 100644
--- a/textclassifier/src/main/java/androidx/textclassifier/ConversationActions.java
+++ b/textclassifier/src/main/java/androidx/textclassifier/ConversationActions.java
@@ -36,6 +36,8 @@
/**
* Represents a list of actions suggested by a {@link TextClassifier} on a given conversation.
+ * <p>
+ * This is an object to store the result of {@link TextClassifier#suggestConversationActions(Request)}.
*
* @see TextClassifier#suggestConversationActions(Request)
*/
diff --git a/textclassifier/src/main/java/androidx/textclassifier/widget/ToolbarController.java b/textclassifier/src/main/java/androidx/textclassifier/widget/ToolbarController.java
index 26f0c7f..45c481f 100644
--- a/textclassifier/src/main/java/androidx/textclassifier/widget/ToolbarController.java
+++ b/textclassifier/src/main/java/androidx/textclassifier/widget/ToolbarController.java
@@ -226,18 +226,18 @@
}
@SuppressWarnings("WeakerAccess") /* synthetic access */
- static void updateRectCoordinates(Rect rect, TextView textView, int start, int end) {
- final int[] startXY = getCoordinates(textView, start);
- final int[] endXY = getCoordinates(textView, end);
+ static void updateRectCoordinates(Rect rect, TextView textView, int startIndex, int endIndex) {
+ final int[] startXY = getCoordinates(textView, startIndex, /* startCoordinate= */ true);
+ final int[] endXY = getCoordinates(textView, endIndex, /* startCoordinate= */false);
rect.set(startXY[0], startXY[1], endXY[0], endXY[1]);
rect.sort();
}
- private static int[] getCoordinates(TextView textView, int index) {
+ private static int[] getCoordinates(TextView textView, int index, boolean startCoordinate) {
final Layout layout = textView.getLayout();
final int line = layout.getLineForOffset(index);
final int x = (int) layout.getPrimaryHorizontal(index);
- final int y = layout.getLineTop(line);
+ final int y = (startCoordinate) ? layout.getLineTop(line) : layout.getLineBottom(line);
final int[] xy = new int[2];
textView.getLocationOnScreen(xy);
return new int[]{
diff --git a/ui/integration-tests/benchmark/build.gradle b/ui/integration-tests/benchmark/build.gradle
index 4d8845b..6fde7f7 100644
--- a/ui/integration-tests/benchmark/build.gradle
+++ b/ui/integration-tests/benchmark/build.gradle
@@ -24,12 +24,14 @@
id("com.android.library")
id("AndroidXUiPlugin")
id("org.jetbrains.kotlin.android")
+ id("androidx.benchmark")
}
dependencies {
kotlinPlugin project(path: ":compose:compose-compiler", configuration: "embeddablePlugin")
implementation(project(":ui:integration-tests:test"))
- implementation(project(":benchmark"))
+ implementation(project(":benchmark:benchmark-junit4"))
+ implementation(project(":compose:compose-runtime"))
implementation(KOTLIN_COMPOSE_STDLIB)
implementation(JUNIT)
androidTestImplementation(project(":ui:ui-core"))
@@ -41,6 +43,7 @@
androidTestImplementation 'androidx.test.ext:junit:1.1.0'
androidTestImplementation(TRUTH)
androidTestImplementation(ANDROIDX_TEST_RULES)
+ androidTestCompile project(path: ':ui:ui-layout')
}
androidx {
@@ -59,4 +62,3 @@
useIR = true
}
}
-
diff --git a/ui/integration-tests/benchmark/src/androidTest/AndroidManifest.xml b/ui/integration-tests/benchmark/src/androidTest/AndroidManifest.xml
index 1496a9d..3369493 100644
--- a/ui/integration-tests/benchmark/src/androidTest/AndroidManifest.xml
+++ b/ui/integration-tests/benchmark/src/androidTest/AndroidManifest.xml
@@ -14,13 +14,16 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<manifest
- xmlns:android="http://schemas.android.com/apk/res/android"
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="androidx.ui.benchmark.test">
- <!-- Important: disable debuggable for accurate performance results -->
+ <!--
+ ~ Important: disable debuggable for accurate performance results
+ ~ requestLegacyExternalStorage to enable legacy JSON reporting when targeting Q
+ -->
<application
+ android:requestLegacyExternalStorage="true"
android:debuggable="false"
tools:replace="android:debuggable">
<activity android:name="android.app.Activity"/>
diff --git a/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/benchmark/test/CheckboxesInRowsBenchmark.kt b/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/benchmark/test/CheckboxesInRowsBenchmark.kt
index 994e303..58e7938 100644
--- a/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/benchmark/test/CheckboxesInRowsBenchmark.kt
+++ b/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/benchmark/test/CheckboxesInRowsBenchmark.kt
@@ -17,10 +17,14 @@
package androidx.ui.benchmark.test
import android.app.Activity
-import androidx.benchmark.BenchmarkRule
+import androidx.benchmark.junit4.BenchmarkRule
import androidx.test.filters.LargeTest
import androidx.test.rule.ActivityTestRule
import androidx.ui.benchmark.measureDrawPerf
+import androidx.ui.benchmark.measureFirstCompose
+import androidx.ui.benchmark.measureFirstDraw
+import androidx.ui.benchmark.measureFirstLayout
+import androidx.ui.benchmark.measureFirstMeasure
import androidx.ui.benchmark.measureLayoutPerf
import androidx.ui.benchmark.toggleStateMeasureDraw
import androidx.ui.benchmark.toggleStateMeasureLayout
@@ -58,6 +62,30 @@
private val activity: Activity get() = activityRule.activity
@Test
+ fun first_compose() {
+ benchmarkRule.measureFirstCompose(activity,
+ CheckboxesInRowsTestCase(activity, numberOfCheckboxes))
+ }
+
+ @Test
+ fun first_measure() {
+ benchmarkRule.measureFirstMeasure(activity,
+ CheckboxesInRowsTestCase(activity, numberOfCheckboxes))
+ }
+
+ @Test
+ fun first_layout() {
+ benchmarkRule.measureFirstLayout(activity,
+ CheckboxesInRowsTestCase(activity, numberOfCheckboxes))
+ }
+
+ @Test
+ fun first_draw() {
+ benchmarkRule.measureFirstDraw(activity,
+ CheckboxesInRowsTestCase(activity, numberOfCheckboxes))
+ }
+
+ @Test
fun toggleCheckbox_recompose() {
benchmarkRule.toggleStateMeasureRecompose(activity,
CheckboxesInRowsTestCase(activity, numberOfCheckboxes))
diff --git a/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/benchmark/test/RectsInColumnBenchmark.kt b/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/benchmark/test/RectsInColumnBenchmark.kt
index 1383456..85a5559 100644
--- a/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/benchmark/test/RectsInColumnBenchmark.kt
+++ b/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/benchmark/test/RectsInColumnBenchmark.kt
@@ -17,10 +17,14 @@
package androidx.ui.benchmark.test
import android.app.Activity
-import androidx.benchmark.BenchmarkRule
+import androidx.benchmark.junit4.BenchmarkRule
import androidx.test.filters.LargeTest
import androidx.test.rule.ActivityTestRule
import androidx.ui.benchmark.measureDrawPerf
+import androidx.ui.benchmark.measureFirstCompose
+import androidx.ui.benchmark.measureFirstDraw
+import androidx.ui.benchmark.measureFirstLayout
+import androidx.ui.benchmark.measureFirstMeasure
import androidx.ui.benchmark.measureLayoutPerf
import androidx.ui.benchmark.toggleStateMeasureDraw
import androidx.ui.benchmark.toggleStateMeasureLayout
@@ -58,6 +62,30 @@
private val activity: Activity get() = activityRule.activity
@Test
+ fun first_compose() {
+ benchmarkRule.measureFirstCompose(activity,
+ RectsInColumnTestCase(activity, numberOfRectangles))
+ }
+
+ @Test
+ fun first_measure() {
+ benchmarkRule.measureFirstMeasure(activity,
+ RectsInColumnTestCase(activity, numberOfRectangles))
+ }
+
+ @Test
+ fun first_layout() {
+ benchmarkRule.measureFirstLayout(activity,
+ RectsInColumnTestCase(activity, numberOfRectangles))
+ }
+
+ @Test
+ fun first_draw() {
+ benchmarkRule.measureFirstDraw(activity,
+ RectsInColumnTestCase(activity, numberOfRectangles))
+ }
+
+ @Test
fun toggleRectangleColor_recompose() {
benchmarkRule.toggleStateMeasureRecompose(activity,
RectsInColumnTestCase(activity, numberOfRectangles))
diff --git a/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/benchmark/test/RectsInColumnSharedModelBenchmark.kt b/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/benchmark/test/RectsInColumnSharedModelBenchmark.kt
index ba0502e..17a739a 100644
--- a/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/benchmark/test/RectsInColumnSharedModelBenchmark.kt
+++ b/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/benchmark/test/RectsInColumnSharedModelBenchmark.kt
@@ -17,10 +17,14 @@
package androidx.ui.benchmark.test
import android.app.Activity
-import androidx.benchmark.BenchmarkRule
+import androidx.benchmark.junit4.BenchmarkRule
import androidx.test.filters.LargeTest
import androidx.test.rule.ActivityTestRule
import androidx.ui.benchmark.measureDrawPerf
+import androidx.ui.benchmark.measureFirstCompose
+import androidx.ui.benchmark.measureFirstDraw
+import androidx.ui.benchmark.measureFirstLayout
+import androidx.ui.benchmark.measureFirstMeasure
import androidx.ui.benchmark.measureLayoutPerf
import androidx.ui.benchmark.toggleStateMeasureDraw
import androidx.ui.benchmark.toggleStateMeasureLayout
@@ -58,6 +62,30 @@
private val activity: Activity get() = activityRule.activity
@Test
+ fun first_compose() {
+ benchmarkRule.measureFirstCompose(activity,
+ RectsInColumnSharedModelTestCase(activity, numberOfRectangles))
+ }
+
+ @Test
+ fun first_measure() {
+ benchmarkRule.measureFirstMeasure(activity,
+ RectsInColumnSharedModelTestCase(activity, numberOfRectangles))
+ }
+
+ @Test
+ fun first_layout() {
+ benchmarkRule.measureFirstLayout(activity,
+ RectsInColumnSharedModelTestCase(activity, numberOfRectangles))
+ }
+
+ @Test
+ fun first_draw() {
+ benchmarkRule.measureFirstDraw(activity,
+ RectsInColumnSharedModelTestCase(activity, numberOfRectangles))
+ }
+
+ @Test
fun toggleRectangleColor_recompose() {
benchmarkRule.toggleStateMeasureRecompose(activity,
RectsInColumnSharedModelTestCase(activity, numberOfRectangles))
diff --git a/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/benchmark/test/ScrollerBenchmark.kt b/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/benchmark/test/ScrollerBenchmark.kt
new file mode 100644
index 0000000..95f6760
--- /dev/null
+++ b/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/benchmark/test/ScrollerBenchmark.kt
@@ -0,0 +1,100 @@
+/*
+ * 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.ui.benchmark.test
+
+import android.app.Activity
+import androidx.benchmark.junit4.BenchmarkRule
+import androidx.test.filters.LargeTest
+import androidx.test.rule.ActivityTestRule
+import androidx.ui.benchmark.measureDrawPerf
+import androidx.ui.benchmark.measureFirstCompose
+import androidx.ui.benchmark.measureFirstDraw
+import androidx.ui.benchmark.measureFirstLayout
+import androidx.ui.benchmark.measureFirstMeasure
+import androidx.ui.benchmark.measureLayoutPerf
+import androidx.ui.benchmark.toggleStateMeasureDraw
+import androidx.ui.benchmark.toggleStateMeasureLayout
+import androidx.ui.benchmark.toggleStateMeasureMeasure
+import androidx.ui.test.DisableTransitions
+import androidx.ui.test.cases.ScrollerTestCase
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@LargeTest
+@RunWith(JUnit4::class)
+class ScrollerBenchmark {
+ @get:Rule
+ val benchmarkRule = BenchmarkRule()
+
+ @get:Rule
+ val activityRule = ActivityTestRule(Activity::class.java)
+
+ @get:Rule
+ val disableAnimationRule = DisableTransitions()
+
+ private val activity: Activity get() = activityRule.activity
+
+ @Test
+ fun first_compose() {
+ benchmarkRule.measureFirstCompose(activity, ScrollerTestCase(activity))
+ }
+
+ @Test
+ fun first_measure() {
+ benchmarkRule.measureFirstMeasure(activity, ScrollerTestCase(activity))
+ }
+
+ @Test
+ fun first_layout() {
+ benchmarkRule.measureFirstLayout(activity, ScrollerTestCase(activity))
+ }
+
+ @Test
+ fun first_draw() {
+ benchmarkRule.measureFirstDraw(activity, ScrollerTestCase(activity))
+ }
+
+ @Test
+ fun changeScroll_measure() {
+ benchmarkRule.toggleStateMeasureMeasure(activity, ScrollerTestCase(activity),
+ toggleCausesRecompose = false, firstDrawCausesRecompose = true)
+ }
+
+ @Test
+ fun changeScroll_layout() {
+ benchmarkRule.toggleStateMeasureLayout(activity, ScrollerTestCase(activity),
+ toggleCausesRecompose = false, firstDrawCausesRecompose = true)
+ }
+
+ @Test
+ fun changeScroll_draw() {
+ benchmarkRule.toggleStateMeasureDraw(activity, ScrollerTestCase(activity),
+ toggleCausesRecompose = false, firstDrawCausesRecompose = true)
+ }
+
+ @Test
+ fun layout() {
+ benchmarkRule.measureLayoutPerf(activity, ScrollerTestCase(activity))
+ }
+
+ @Test
+ fun draw() {
+ benchmarkRule.measureDrawPerf(activity, ScrollerTestCase(activity))
+ }
+}
diff --git a/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/benchmark/test/view/AndroidCheckboxesInLinearLayoutBenchmark.kt b/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/benchmark/test/view/AndroidCheckboxesInLinearLayoutBenchmark.kt
index ce982a7..d431b29 100644
--- a/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/benchmark/test/view/AndroidCheckboxesInLinearLayoutBenchmark.kt
+++ b/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/benchmark/test/view/AndroidCheckboxesInLinearLayoutBenchmark.kt
@@ -17,10 +17,14 @@
package androidx.ui.benchmark.test.view
import android.app.Activity
-import androidx.benchmark.BenchmarkRule
+import androidx.benchmark.junit4.BenchmarkRule
import androidx.test.filters.LargeTest
import androidx.test.rule.ActivityTestRule
import androidx.ui.benchmark.measureDrawPerf
+import androidx.ui.benchmark.measureFirstDraw
+import androidx.ui.benchmark.measureFirstLayout
+import androidx.ui.benchmark.measureFirstMeasure
+import androidx.ui.benchmark.measureFirstSetContent
import androidx.ui.benchmark.measureLayoutPerf
import androidx.ui.test.DisableTransitions
import androidx.ui.test.cases.view.AndroidCheckboxesInLinearLayoutTestCase
@@ -51,15 +55,41 @@
@get:Rule
val disableAnimationRule = DisableTransitions()
+ private val activity: Activity get() = activityRule.activity
+
+ @Test
+ fun first_setContent() {
+ benchmarkRule.measureFirstSetContent(activity,
+ AndroidCheckboxesInLinearLayoutTestCase(activity, numberOfCheckboxes))
+ }
+
+ @Test
+ fun first_measure() {
+ benchmarkRule.measureFirstMeasure(activity,
+ AndroidCheckboxesInLinearLayoutTestCase(activity, numberOfCheckboxes))
+ }
+
+ @Test
+ fun first_layout() {
+ benchmarkRule.measureFirstLayout(activity,
+ AndroidCheckboxesInLinearLayoutTestCase(activity, numberOfCheckboxes))
+ }
+
+ @Test
+ fun first_draw() {
+ benchmarkRule.measureFirstDraw(activity,
+ AndroidCheckboxesInLinearLayoutTestCase(activity, numberOfCheckboxes))
+ }
+
@Test
fun layout() {
- benchmarkRule.measureLayoutPerf(activityRule.activity,
- AndroidCheckboxesInLinearLayoutTestCase(activityRule.activity, numberOfCheckboxes))
+ benchmarkRule.measureLayoutPerf(activity,
+ AndroidCheckboxesInLinearLayoutTestCase(activity, numberOfCheckboxes))
}
@Test
fun draw() {
- benchmarkRule.measureDrawPerf(activityRule.activity,
- AndroidCheckboxesInLinearLayoutTestCase(activityRule.activity, numberOfCheckboxes))
+ benchmarkRule.measureDrawPerf(activity,
+ AndroidCheckboxesInLinearLayoutTestCase(activity, numberOfCheckboxes))
}
}
\ No newline at end of file
diff --git a/ui/integration-tests/benchmark/src/main/java/androidx/ui/benchmark/BenchmarksExtensions.kt b/ui/integration-tests/benchmark/src/main/java/androidx/ui/benchmark/BenchmarksExtensions.kt
index 7d17561..c5fe352 100644
--- a/ui/integration-tests/benchmark/src/main/java/androidx/ui/benchmark/BenchmarksExtensions.kt
+++ b/ui/integration-tests/benchmark/src/main/java/androidx/ui/benchmark/BenchmarksExtensions.kt
@@ -18,23 +18,27 @@
import android.app.Activity
import android.view.View
-import androidx.benchmark.BenchmarkRule
-import androidx.benchmark.measureRepeated
+import androidx.benchmark.junit4.BenchmarkRule
+import androidx.benchmark.junit4.measureRepeated
+import androidx.compose.FrameManager
+import androidx.compose.disposeComposition
+import androidx.ui.test.AndroidTestCase
import androidx.ui.test.ComposeTestCase
import androidx.ui.test.TestCase
import androidx.ui.test.ToggleableTestCase
import androidx.ui.test.invalidateViews
+import androidx.ui.test.recomposeSyncAssert
import androidx.ui.test.recomposeSyncAssertHadChanges
import androidx.ui.test.recomposeSyncAssertNoChanges
import androidx.ui.test.requestLayout
import androidx.ui.test.runOnUiThreadSync
/**
- * Measures measure and layout performance of the given testCase by toggling measure constraints.
+ * Measures measure and layout performance of the given test case by toggling measure constraints.
*/
fun BenchmarkRule.measureLayoutPerf(activity: Activity, testCase: TestCase) {
activity.runOnUiThreadSync {
- testCase.runSetup()
+ testCase.runToFirstDraw()
val width = testCase.view.measuredWidth
val height = testCase.view.measuredHeight
@@ -66,15 +70,19 @@
testCase.measureWithSpec(widthSpec, heightSpec)
testCase.layout()
}
+
+ if (testCase is ComposeTestCase) {
+ activity.disposeComposition()
+ }
}
}
/**
- * Measures draw performance of the given testCase by invalidating the view hierarchy.
+ * Measures draw performance of the given test case by invalidating the view hierarchy.
*/
fun BenchmarkRule.measureDrawPerf(activity: Activity, testCase: TestCase) {
activity.runOnUiThreadSync {
- testCase.runSetup()
+ testCase.runToFirstDraw()
measureRepeated {
runWithTimingDisabled {
@@ -86,6 +94,125 @@
testCase.finishDraw()
}
}
+
+ if (testCase is ComposeTestCase) {
+ activity.disposeComposition()
+ }
+ }
+}
+
+/**
+ * Measures the time of the first composition of the given compose test case.
+ */
+fun BenchmarkRule.measureFirstCompose(
+ activity: Activity,
+ testCase: ComposeTestCase
+) {
+ activity.runOnUiThreadSync {
+ measureRepeated {
+ testCase.setupContent(activity)
+ runWithTimingDisabled {
+ testCase.recomposeSyncAssertNoChanges()
+ activity.disposeComposition()
+ }
+ }
+ }
+}
+
+/**
+ * Measures the time of the first set content of the given Android test case.
+ */
+fun BenchmarkRule.measureFirstSetContent(
+ activity: Activity,
+ testCase: AndroidTestCase
+) {
+ activity.runOnUiThreadSync {
+ measureRepeated {
+ testCase.setupContent(activity)
+ }
+ }
+}
+
+/**
+ * Measures the time of the first measure of the given test case.
+ */
+fun BenchmarkRule.measureFirstMeasure(
+ activity: Activity,
+ testCase: TestCase
+) {
+ activity.runOnUiThreadSync {
+ measureRepeated {
+ runWithTimingDisabled {
+ testCase.setupContent(activity)
+ testCase.requestLayout()
+ }
+
+ testCase.measure()
+
+ runWithTimingDisabled {
+ if (testCase is ComposeTestCase) {
+ testCase.recomposeSyncAssertNoChanges()
+ activity.disposeComposition()
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Measures the time of the first layout of the given test case.
+ */
+fun BenchmarkRule.measureFirstLayout(
+ activity: Activity,
+ testCase: TestCase
+) {
+ activity.runOnUiThreadSync {
+ measureRepeated {
+ runWithTimingDisabled {
+ testCase.setupContent(activity)
+ testCase.requestLayout()
+ testCase.measure()
+ }
+
+ testCase.layout()
+
+ runWithTimingDisabled {
+ if (testCase is ComposeTestCase) {
+ testCase.recomposeSyncAssertNoChanges()
+ activity.disposeComposition()
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Measures the time of the first draw of the given test case.
+ */
+fun BenchmarkRule.measureFirstDraw(
+ activity: Activity,
+ testCase: TestCase
+) {
+ activity.runOnUiThreadSync {
+ measureRepeated {
+ runWithTimingDisabled {
+ testCase.setupContent(activity)
+ testCase.requestLayout()
+ testCase.measure()
+ testCase.layout()
+ testCase.prepareDraw()
+ }
+
+ testCase.draw()
+
+ runWithTimingDisabled {
+ testCase.finishDraw()
+ if (testCase is ComposeTestCase) {
+ testCase.recomposeSyncAssertNoChanges()
+ activity.disposeComposition()
+ }
+ }
+ }
}
}
@@ -96,29 +223,17 @@
activity: Activity,
testCase: T
) where T : ComposeTestCase, T : ToggleableTestCase {
- toggleStateMeasureMeasure(activity, testCase) {
- testCase.toggleState()
- }
-}
-
-/**
- * Measures recomposition time of the hierarchy after changing a state.
- */
-fun BenchmarkRule.toggleStateMeasureRecompose(
- activity: Activity,
- testCase: ComposeTestCase,
- toggleState: () -> Unit
-) {
activity.runOnUiThreadSync {
- testCase.runSetup()
+ testCase.runToFirstDraw()
testCase.recomposeSyncAssertNoChanges()
measureRepeated {
runWithTimingDisabled {
- toggleState()
+ testCase.toggleState()
}
testCase.recomposeSyncAssertHadChanges()
}
+ activity.disposeComposition()
}
}
@@ -127,33 +242,22 @@
*/
fun <T> BenchmarkRule.toggleStateMeasureMeasure(
activity: Activity,
- testCase: T
+ testCase: T,
+ toggleCausesRecompose: Boolean = true,
+ firstDrawCausesRecompose: Boolean = false
) where T : ComposeTestCase, T : ToggleableTestCase {
- toggleStateMeasureMeasure(activity, testCase) {
- testCase.toggleState()
- }
-}
-
-/**
- * Measures measure time of the hierarchy after changing a state.
- */
-fun BenchmarkRule.toggleStateMeasureMeasure(
- activity: Activity,
- testCase: ComposeTestCase,
- toggleState: () -> Unit
-) {
activity.runOnUiThreadSync {
- testCase.runSetup()
- testCase.recomposeSyncAssertNoChanges()
+ runToFirstDraw(testCase, firstDrawCausesRecompose)
measureRepeated {
runWithTimingDisabled {
- toggleState()
- testCase.recomposeSyncAssertHadChanges()
+ testCase.toggleState()
+ testCase.recomposeSyncAssert(toggleCausesRecompose)
testCase.requestLayout()
}
testCase.measure()
}
+ activity.disposeComposition()
}
}
@@ -162,34 +266,23 @@
*/
fun <T> BenchmarkRule.toggleStateMeasureLayout(
activity: Activity,
- testCase: T
+ testCase: T,
+ toggleCausesRecompose: Boolean = true,
+ firstDrawCausesRecompose: Boolean = false
) where T : ComposeTestCase, T : ToggleableTestCase {
- toggleStateMeasureLayout(activity, testCase) {
- testCase.toggleState()
- }
-}
-
-/**
- * Measures layout time of the hierarchy after changing a state.
- */
-fun BenchmarkRule.toggleStateMeasureLayout(
- activity: Activity,
- testCase: ComposeTestCase,
- toggleState: () -> Unit
-) {
activity.runOnUiThreadSync {
- testCase.runSetup()
- testCase.recomposeSyncAssertNoChanges()
+ runToFirstDraw(testCase, firstDrawCausesRecompose)
measureRepeated {
runWithTimingDisabled {
- toggleState()
- testCase.recomposeSyncAssertHadChanges()
+ testCase.toggleState()
+ testCase.recomposeSyncAssert(toggleCausesRecompose)
testCase.requestLayout()
testCase.measure()
}
testCase.layout()
}
+ activity.disposeComposition()
}
}
@@ -198,29 +291,17 @@
*/
fun <T> BenchmarkRule.toggleStateMeasureDraw(
activity: Activity,
- testCase: T
+ testCase: T,
+ toggleCausesRecompose: Boolean = true,
+ firstDrawCausesRecompose: Boolean = false
) where T : ComposeTestCase, T : ToggleableTestCase {
- toggleStateMeasureDraw(activity, testCase) {
- testCase.toggleState()
- }
-}
-
-/**
- * Measures draw time of the hierarchy after changing a state.
- */
-fun BenchmarkRule.toggleStateMeasureDraw(
- activity: Activity,
- testCase: ComposeTestCase,
- toggleState: () -> Unit
-) {
activity.runOnUiThreadSync {
- testCase.runSetup()
- testCase.recomposeSyncAssertNoChanges()
+ runToFirstDraw(testCase, firstDrawCausesRecompose)
measureRepeated {
runWithTimingDisabled {
- toggleState()
- testCase.recomposeSyncAssertHadChanges()
+ testCase.toggleState()
+ testCase.recomposeSyncAssert(toggleCausesRecompose)
testCase.requestLayout()
testCase.measure()
testCase.layout()
@@ -231,5 +312,20 @@
testCase.finishDraw()
}
}
+ activity.disposeComposition()
}
+}
+
+/**
+ * Runs first draw on the test case and runs recomposition. Some layout/draw cycles
+ * cause recomposition changes ([firstDrawCausesRecompose]). Changes are expected only
+ * when [firstDrawCausesRecompose] is `true`.
+ */
+private fun <T> runToFirstDraw(
+ testCase: T,
+ firstDrawCausesRecompose: Boolean
+) where T : ComposeTestCase {
+ testCase.runToFirstDraw()
+ FrameManager.nextFrame()
+ testCase.recomposeSyncAssert(firstDrawCausesRecompose)
}
\ No newline at end of file
diff --git a/ui/integration-tests/test/src/main/java/androidx/ui/test/TestCase.kt b/ui/integration-tests/test/src/main/java/androidx/ui/test/TestCase.kt
index 2103bb1..ccb6862 100644
--- a/ui/integration-tests/test/src/main/java/androidx/ui/test/TestCase.kt
+++ b/ui/integration-tests/test/src/main/java/androidx/ui/test/TestCase.kt
@@ -26,6 +26,7 @@
import android.view.ViewGroup
import android.widget.ImageView
import androidx.compose.CompositionContext
+import androidx.compose.FrameManager
import androidx.ui.core.AndroidCraneView
import androidx.ui.core.ComponentNode
import androidx.ui.core.DrawNode
@@ -41,6 +42,9 @@
private val renderNode = RenderNode("test")
private var canvas: Canvas? = null
+ lateinit var view: ViewGroup
+ private set
+
init {
val displayMetrics = DisplayMetrics()
activity.windowManager.defaultDisplay.getMetrics(displayMetrics)
@@ -51,9 +55,22 @@
screenHeightSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.AT_MOST)
}
- lateinit var view: ViewGroup
+ fun setupContent(activity: Activity) {
+ view = setupContentInternal(activity)
+ }
- abstract fun runSetup()
+ protected abstract fun setupContentInternal(activity: Activity): ViewGroup
+
+ /**
+ * Runs all the steps leading into drawing first pixels. Useful to get into the initial state
+ * before you benchmark a change of the state.
+ */
+ fun runToFirstDraw() {
+ setupContent(activity)
+ measure()
+ layout()
+ drawSlow()
+ }
/**
* To be run in benchmark.
@@ -108,11 +125,30 @@
}
}
+abstract class AndroidTestCase(
+ activity: Activity
+) : TestCase(activity) {
+
+ override fun setupContentInternal(activity: Activity) = createViewContent(activity)
+ .also { activity.setContentView(it) }
+
+ abstract fun createViewContent(activity: Activity): ViewGroup
+}
+
abstract class ComposeTestCase(
activity: Activity
) : TestCase(activity) {
lateinit var compositionContext: CompositionContext
+ private set
+
+ override fun setupContentInternal(activity: Activity): ViewGroup {
+ compositionContext = setComposeContent(activity)
+ FrameManager.nextFrame()
+ return findComposeView()!!
+ }
+
+ abstract fun setComposeContent(activity: Activity): CompositionContext
}
/**
@@ -164,22 +200,29 @@
* Performs recomposition and asserts that there were some pending changes.
*/
fun ComposeTestCase.recomposeSyncAssertHadChanges() {
- val hadChanges = compositionContext.recomposeSync()
- Assert.assertTrue(
- "Expected pending changes on recomposition but there were none. Did " +
- "you forget to call FrameManager.next()?", hadChanges
- )
+ recomposeSyncAssert(expectingChanges = true)
}
/**
* Performs recomposition and asserts that there were no pending changes.
*/
fun ComposeTestCase.recomposeSyncAssertNoChanges() {
+ recomposeSyncAssert(expectingChanges = false)
+}
+
+/**
+ * Performs recomposition and asserts that there were or weren't pending changes based on
+ * [expectingChanges].
+ */
+fun ComposeTestCase.recomposeSyncAssert(expectingChanges: Boolean) {
+ val message = if (expectingChanges) {
+ "Expected pending changes on recomposition but there were none. Did " +
+ "you forget to call FrameManager.next()?"
+ } else {
+ "Expected no pending changes on recomposition but there were some."
+ }
val hadChanges = compositionContext.recomposeSync()
- Assert.assertFalse(
- "Expected no pending changes on recomposition but there were some.",
- hadChanges
- )
+ Assert.assertEquals(message, expectingChanges, hadChanges)
}
/**
@@ -197,4 +240,33 @@
val imageView = ImageView(activity)
imageView.setImageBitmap(Bitmap.createBitmap(picture))
activity.setContentView(imageView)
-}
\ No newline at end of file
+}
+
+/**
+ * Returns the first found [AndroidCraneView] in the content view hierarchy:
+ *
+ * override fun setupContent(activity: Activity) {
+ * activity.setContent { ... }
+ * view = findComposeView()!!
+ * FrameManager.nextFrame()
+ * }
+ */
+fun ComposeTestCase.findComposeView(): AndroidCraneView? {
+ return findComposeView(activity.findViewById(android.R.id.content))
+}
+
+private fun findComposeView(view: View): AndroidCraneView? {
+ if (view is AndroidCraneView) {
+ return view
+ }
+
+ if (view is ViewGroup) {
+ for (i in 0 until view.childCount) {
+ val composeView = findComposeView(view.getChildAt(i))
+ if (composeView != null) {
+ return composeView
+ }
+ }
+ }
+ return null
+}
diff --git a/ui/integration-tests/test/src/main/java/androidx/ui/test/TestExecutors.kt b/ui/integration-tests/test/src/main/java/androidx/ui/test/TestExecutors.kt
index 55cb4d1..909a8e6 100644
--- a/ui/integration-tests/test/src/main/java/androidx/ui/test/TestExecutors.kt
+++ b/ui/integration-tests/test/src/main/java/androidx/ui/test/TestExecutors.kt
@@ -24,7 +24,7 @@
testCase: ComposeTestCase,
toggleState: () -> Unit
) {
- testCase.runSetup()
+ testCase.runToFirstDraw()
testCase.assertMeasureSizeIsPositive()
diff --git a/ui/integration-tests/test/src/main/java/androidx/ui/test/cases/CheckboxesInRowsTestCase.kt b/ui/integration-tests/test/src/main/java/androidx/ui/test/cases/CheckboxesInRowsTestCase.kt
index bb8f07f..dd5abdd 100644
--- a/ui/integration-tests/test/src/main/java/androidx/ui/test/cases/CheckboxesInRowsTestCase.kt
+++ b/ui/integration-tests/test/src/main/java/androidx/ui/test/cases/CheckboxesInRowsTestCase.kt
@@ -17,8 +17,10 @@
package androidx.ui.test.cases
import android.app.Activity
+import android.view.View
import androidx.compose.composer
import androidx.compose.Composable
+import androidx.compose.CompositionContext
import androidx.compose.FrameManager
import androidx.compose.State
import androidx.compose.state
@@ -31,8 +33,10 @@
import androidx.ui.layout.FlexRow
import androidx.ui.material.Checkbox
import androidx.ui.material.MaterialTheme
+import androidx.ui.material.surface.Surface
import androidx.ui.test.ComposeTestCase
import androidx.ui.test.ToggleableTestCase
+import androidx.ui.test.findComposeView
/**
* Test case that puts the given amount of checkboxes into a column of rows and makes changes by
@@ -45,9 +49,9 @@
private val states = mutableListOf<State<Boolean>>()
- override fun runSetup() {
- compositionContext = activity.setContent {
- MaterialTheme {
+ override fun setComposeContent(activity: Activity) = activity.setContent {
+ MaterialTheme {
+ Surface {
Column {
repeat(amountOfCheckboxes) {
FlexRow {
@@ -63,15 +67,8 @@
}
}
}
- }!!
- FrameManager.nextFrame()
-
- view = activity.findViewById(android.R.id.content)
-
- measure()
- layout()
- drawSlow()
- }
+ }
+ }!!
override fun toggleState() {
val state = states.first()
diff --git a/ui/integration-tests/test/src/main/java/androidx/ui/test/cases/RectsInColumnSharedModelTestCase.kt b/ui/integration-tests/test/src/main/java/androidx/ui/test/cases/RectsInColumnSharedModelTestCase.kt
index d4bb20e..6660cf6 100644
--- a/ui/integration-tests/test/src/main/java/androidx/ui/test/cases/RectsInColumnSharedModelTestCase.kt
+++ b/ui/integration-tests/test/src/main/java/androidx/ui/test/cases/RectsInColumnSharedModelTestCase.kt
@@ -17,6 +17,8 @@
package androidx.ui.test.cases
import android.app.Activity
+import androidx.compose.Composable
+import androidx.compose.CompositionContext
import androidx.compose.composer
import androidx.compose.FrameManager
import androidx.compose.Model
@@ -46,28 +48,19 @@
private val model = RectanglesInColumnTestCaseColorModel(Color.Black)
- override fun runSetup() {
- compositionContext = activity.setContent {
- MaterialTheme {
- Column {
- repeat(amountOfRectangles) { i ->
- if (i == 0) {
- ColoredRect(color = model.color, width = 100.dp, height = 50.dp)
- } else {
- ColoredRect(color = Color.Green, width = 100.dp, height = 50.dp)
- }
+ override fun setComposeContent(activity: Activity) = activity.setContent {
+ MaterialTheme {
+ Column {
+ repeat(amountOfRectangles) { i ->
+ if (i == 0) {
+ ColoredRect(color = model.color, width = 100.dp, height = 50.dp)
+ } else {
+ ColoredRect(color = Color.Green, width = 100.dp, height = 50.dp)
}
}
}
- }!!
- FrameManager.nextFrame()
-
- view = activity.findViewById(android.R.id.content)
-
- measure()
- layout()
- drawSlow()
- }
+ }
+ }!!
override fun toggleState() {
if (model.color == Color.Purple) {
diff --git a/ui/integration-tests/test/src/main/java/androidx/ui/test/cases/RectsInColumnTestCase.kt b/ui/integration-tests/test/src/main/java/androidx/ui/test/cases/RectsInColumnTestCase.kt
index 66c9793..0f25cc2 100644
--- a/ui/integration-tests/test/src/main/java/androidx/ui/test/cases/RectsInColumnTestCase.kt
+++ b/ui/integration-tests/test/src/main/java/androidx/ui/test/cases/RectsInColumnTestCase.kt
@@ -19,6 +19,7 @@
import android.app.Activity
import androidx.compose.composer
import androidx.compose.Composable
+import androidx.compose.CompositionContext
import androidx.compose.FrameManager
import androidx.compose.State
import androidx.compose.state
@@ -29,6 +30,7 @@
import androidx.ui.graphics.Color
import androidx.ui.layout.Column
import androidx.ui.material.MaterialTheme
+import androidx.ui.material.surface.Surface
import androidx.ui.test.ComposeTestCase
import androidx.ui.test.ToggleableTestCase
@@ -45,24 +47,17 @@
private val states = mutableListOf<State<Color>>()
- override fun runSetup() {
- compositionContext = activity.setContent {
- MaterialTheme {
+ override fun setComposeContent(activity: Activity) = activity.setContent {
+ MaterialTheme {
+ Surface {
Column {
repeat(amountOfRectangles) {
ColoredRectWithModel()
}
}
}
- }!!
- FrameManager.nextFrame()
-
- view = activity.findViewById(android.R.id.content)
-
- measure()
- layout()
- drawSlow()
- }
+ }
+ }!!
override fun toggleState() {
val state = states.first()
diff --git a/ui/integration-tests/test/src/main/java/androidx/ui/test/cases/ScrollerTestCase.kt b/ui/integration-tests/test/src/main/java/androidx/ui/test/cases/ScrollerTestCase.kt
new file mode 100644
index 0000000..40d97a8
--- /dev/null
+++ b/ui/integration-tests/test/src/main/java/androidx/ui/test/cases/ScrollerTestCase.kt
@@ -0,0 +1,93 @@
+/*
+ * 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.ui.test.cases
+
+import android.app.Activity
+import androidx.compose.composer
+import androidx.compose.Composable
+import androidx.compose.CompositionContext
+import androidx.compose.FrameManager
+import androidx.compose.memo
+import androidx.compose.unaryPlus
+import androidx.ui.core.Draw
+import androidx.ui.core.dp
+import androidx.ui.core.px
+import androidx.ui.core.setContent
+import androidx.ui.core.toRect
+import androidx.ui.graphics.Color
+import androidx.ui.layout.Column
+import androidx.ui.layout.Container
+import androidx.ui.layout.CrossAxisAlignment
+import androidx.ui.layout.ScrollerPosition
+import androidx.ui.layout.VerticalScroller
+import androidx.ui.painting.Paint
+import androidx.ui.painting.PaintingStyle
+import androidx.ui.test.ComposeTestCase
+import androidx.ui.test.ToggleableTestCase
+
+/**
+ * Test case that puts a large number of boxes in a column in a vertical scroller to force scrolling.
+ */
+class ScrollerTestCase(
+ activity: Activity
+) : ComposeTestCase(activity), ToggleableTestCase {
+ private val scrollerPosition = ScrollerPosition()
+
+ override fun setComposeContent(activity: Activity): CompositionContext = activity.setContent {
+ VerticalScroller(
+ scrollerPosition = scrollerPosition
+ ) {
+ Column(crossAxisAlignment = CrossAxisAlignment.Start) {
+ for (green in 0..0xFF) {
+ ColorStripe(0xFF, green, 0)
+ }
+ for (red in 0xFF downTo 0) {
+ ColorStripe(red, 0xFF, 0)
+ }
+ for (blue in 0..0xFF) {
+ ColorStripe(0, 0xFF, blue)
+ }
+ for (green in 0xFF downTo 0) {
+ ColorStripe(0, green, 0xFF)
+ }
+ for (red in 0..0xFF) {
+ ColorStripe(red, 0, 0xFF)
+ }
+ for (blue in 0xFF downTo 0) {
+ ColorStripe(0xFF, 0, blue)
+ }
+ }
+ }
+ }!!
+
+ override fun toggleState() {
+ scrollerPosition.position = if (scrollerPosition.position == 0.px) 10.px else 0.px
+ FrameManager.nextFrame()
+ }
+
+ @Composable
+ fun ColorStripe(red: Int, green: Int, blue: Int) {
+ val paint = +memo { Paint() }
+ Container(height = 5.dp, width = 45.dp) {
+ Draw { canvas, parentSize ->
+ paint.color = Color(red = red, green = green, blue = blue)
+ paint.style = PaintingStyle.fill
+ canvas.drawRect(parentSize.toRect(), paint)
+ }
+ }
+ }
+}
diff --git a/ui/integration-tests/test/src/main/java/androidx/ui/test/cases/view/AndroidCheckboxesInLinearLayoutTestCase.kt b/ui/integration-tests/test/src/main/java/androidx/ui/test/cases/view/AndroidCheckboxesInLinearLayoutTestCase.kt
index e12d6fc..03bd230 100644
--- a/ui/integration-tests/test/src/main/java/androidx/ui/test/cases/view/AndroidCheckboxesInLinearLayoutTestCase.kt
+++ b/ui/integration-tests/test/src/main/java/androidx/ui/test/cases/view/AndroidCheckboxesInLinearLayoutTestCase.kt
@@ -22,8 +22,9 @@
import android.widget.CheckBox
import android.widget.LinearLayout
import android.widget.TextView
+import androidx.ui.test.AndroidTestCase
import androidx.ui.test.R
-import androidx.ui.test.TestCase
+import androidx.ui.test.cases.CheckboxesInRowsTestCase
/**
* Version of [CheckboxesInRowsTestCase] using Android views.
@@ -31,11 +32,11 @@
class AndroidCheckboxesInLinearLayoutTestCase(
activity: Activity,
private val amountOfCheckboxes: Int
-) : TestCase(activity) {
+) : AndroidTestCase(activity) {
private val checkboxes = mutableListOf<CheckBox>()
- override fun runSetup() {
+ override fun createViewContent(activity: Activity): ViewGroup {
val column = LinearLayout(activity)
column.orientation = LinearLayout.VERTICAL
column.layoutParams = ViewGroup.LayoutParams(
@@ -72,13 +73,7 @@
row.addView(checkbox)
column.addView(row)
}
-
- view = column
- activity.setContentView(column)
-
- measure()
- layout()
- drawSlow()
+ return column
}
fun toggleState() {
diff --git a/ui/settings.gradle b/ui/settings.gradle
index 3055cfb..2063fef 100644
--- a/ui/settings.gradle
+++ b/ui/settings.gradle
@@ -20,7 +20,8 @@
}
includeProject(":annotation:annotation-sampled", "../annotation/annotation-sampled")
-includeProject(":benchmark", "../benchmark")
+includeProject(":benchmark:benchmark-common", "../benchmark/common")
+includeProject(":benchmark:benchmark-junit4", "../benchmark/junit4")
includeProject(":compose:compose-compiler", "../compose/compose-compiler")
includeProject(":compose:compose-compiler-hosted", "../compose/compose-compiler-hosted")
includeProject(":compose:compose-compiler-hosted:integration-tests", "../compose/compose-compiler-hosted/integration-tests")
@@ -54,6 +55,10 @@
includeProject(":ui:ui-test", "ui-test")
includeProject(":ui:ui-text", "ui-text")
includeProject(":ui:ui-text:integration-tests:ui-text-demos", "ui-text/integration-tests/text-demos")
+includeProject(":ui:ui-vector", "ui-vector")
+includeProject(":compose:compose-runtime:integration-tests", "../compose/compose-runtime/integration-tests")
+includeProject(":compose:compose-runtime:integration-tests:android-tests", "../compose/compose-runtime/integration-tests/android-tests")
+
/////////////////////////////
//
diff --git a/ui/ui-android-text/src/main/java/androidx/text/TextLayout.kt b/ui/ui-android-text/src/main/java/androidx/text/TextLayout.kt
index f9e5c59..73709d0 100644
--- a/ui/ui-android-text/src/main/java/androidx/text/TextLayout.kt
+++ b/ui/ui-android-text/src/main/java/androidx/text/TextLayout.kt
@@ -86,9 +86,11 @@
val frameworkTextDir = getTextDirectionHeuristic(textDirectionHeuristic)
val boringMetrics = BoringLayoutCompat.isBoring(charSequence, textPaint, frameworkTextDir)
+ // TODO(haoyuchang): we didn't pass the TextDirection to Layout.getDesiredWidth(), check if
+ // there is any behavior difference from
+ // Layout.getWidthWithLimits(charSequence, start, end, paint, dir)
maxIntrinsicWidth = boringMetrics?.width?.toFloat()
- // we may need to getWidthWithLimits(maxWidth: Int, maxLines: Int)
- ?: Layout.getDesiredWidth(charSequence, start, end, textPaint)
+ ?: Layout.getDesiredWidth(charSequence, start, end, textPaint)
val finalWidth = width.toInt()
val ellipsizeWidth = finalWidth
@@ -164,6 +166,12 @@
fun getLineRight(lineIndex: Int): Float = layout.getLineRight(lineIndex)
+ fun getLineTop(line: Int): Float = layout.getLineTop(line).toFloat()
+
+ fun getLineBottom(line: Int): Float = layout.getLineBottom(line).toFloat()
+
+ fun getLineBaseline(line: Int): Float = layout.getLineBaseline(line).toFloat()
+
fun getLineHeight(lineIndex: Int): Float =
(layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex)).toFloat()
@@ -178,10 +186,6 @@
fun getLineForOffset(offset: Int): Int = layout.getLineForOffset(offset)
- fun getLineTop(line: Int): Float = layout.getLineTop(line).toFloat()
-
- fun getLineBottom(line: Int): Float = layout.getLineBottom(line).toFloat()
-
fun getSelectionPath(start: Int, end: Int, dest: Path) =
layout.getSelectionPath(start, end, dest)
diff --git a/ui/ui-animation/src/main/java/androidx/ui/animation/Transition.kt b/ui/ui-animation/src/main/java/androidx/ui/animation/Transition.kt
index c1a49d5..f3f6dd7 100644
--- a/ui/ui-animation/src/main/java/androidx/ui/animation/Transition.kt
+++ b/ui/ui-animation/src/main/java/androidx/ui/animation/Transition.kt
@@ -46,7 +46,7 @@
fun <T> Transition(
definition: TransitionDefinition<T>,
toState: T,
- @Children children: @Composable() (state: TransitionState) -> Unit
+ children: @Composable() (state: TransitionState) -> Unit
) {
if (transitionsEnabled) {
// TODO: This null is workaround for b/132148894
diff --git a/ui/ui-core/api/1.0.0-alpha01.txt b/ui/ui-core/api/1.0.0-alpha01.txt
index 88194bc..1648b3a 100644
--- a/ui/ui-core/api/1.0.0-alpha01.txt
+++ b/ui/ui-core/api/1.0.0-alpha01.txt
@@ -479,7 +479,9 @@
public static final class Px.Companion {
method public androidx.ui.core.Px getInfinity();
+ method public androidx.ui.core.Px getZero();
property public final androidx.ui.core.Px Infinity;
+ property public final androidx.ui.core.Px Zero;
}
public final class PxBounds {
@@ -708,74 +710,6 @@
}
-package androidx.ui.core.semantics {
-
- public final class SemanticsAction<T> {
- ctor public SemanticsAction(androidx.ui.core.semantics.SemanticsActionType<T> type, T handler);
- method public androidx.ui.core.semantics.SemanticsActionType<T> component1();
- method public T component2();
- method public androidx.ui.core.semantics.SemanticsAction<T> copy(androidx.ui.core.semantics.SemanticsActionType<T> type, T handler);
- method public T getHandler();
- method public androidx.ui.core.semantics.SemanticsActionType<T> getType();
- method public void invokeHandler(Object? args);
- }
-
- public final class SemanticsActionType<T> {
- method public int getBitmask();
- field public static final androidx.ui.core.semantics.SemanticsActionType.Companion! Companion;
- }
-
- public static final class SemanticsActionType.Companion {
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getCopy();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getCustomAction();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getCut();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getDecrease();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getDidGainAccessibilityFocus();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getDidLoseAccessibilityFocus();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getDismiss();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getIncrease();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getLongPress();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function1<java.lang.Boolean,kotlin.Unit>> getMoveCursorBackwardByCharacter();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function1<java.lang.Boolean,kotlin.Unit>> getMoveCursorBackwardByWord();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function1<java.lang.Boolean,kotlin.Unit>> getMoveCursorForwardByCharacter();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function1<java.lang.Boolean,kotlin.Unit>> getMoveCursorForwardByWord();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getPaste();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getScrollDown();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getScrollLeft();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getScrollRight();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getScrollUp();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function1<androidx.ui.text.TextSelection,kotlin.Unit>> getSetSelection();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getShowOnScreen();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getTap();
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> Copy;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> CustomAction;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> Cut;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> Decrease;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> DidGainAccessibilityFocus;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> DidLoseAccessibilityFocus;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> Dismiss;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> Increase;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> LongPress;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function1<java.lang.Boolean,kotlin.Unit>> MoveCursorBackwardByCharacter;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function1<java.lang.Boolean,kotlin.Unit>> MoveCursorBackwardByWord;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function1<java.lang.Boolean,kotlin.Unit>> MoveCursorForwardByCharacter;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function1<java.lang.Boolean,kotlin.Unit>> MoveCursorForwardByWord;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> Paste;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> ScrollDown;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> ScrollLeft;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> ScrollRight;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> ScrollUp;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function1<androidx.ui.text.TextSelection,kotlin.Unit>> SetSelection;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> ShowOnScreen;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> Tap;
- }
-
- public final class SemanticsActionsKt {
- ctor public SemanticsActionsKt();
- }
-
-}
-
package androidx.ui.engine.geometry {
public final class Offset implements androidx.ui.engine.geometry.OffsetBase {
@@ -1320,6 +1254,255 @@
}
+package androidx.ui.graphics.vectorgraphics {
+
+ public interface Brush {
+ method public void applyBrush(androidx.ui.painting.Paint p);
+ }
+
+ public final class BrushKt {
+ ctor public BrushKt();
+ method public static androidx.ui.graphics.vectorgraphics.LinearGradient HorizontalGradient(androidx.ui.graphics.Color![] colors, androidx.ui.core.Px startX, androidx.ui.core.Px endX, androidx.ui.painting.TileMode tileMode = TileMode.clamp);
+ method public static androidx.ui.graphics.vectorgraphics.Brush HorizontalGradient(kotlin.Pair<java.lang.Float,androidx.ui.graphics.Color>![] colorStops, androidx.ui.core.Px startX, androidx.ui.core.Px endX, androidx.ui.painting.TileMode tileMode = TileMode.clamp);
+ method public static androidx.ui.graphics.vectorgraphics.LinearGradient LinearGradient(androidx.ui.graphics.Color![] colors, androidx.ui.core.Px startX, androidx.ui.core.Px startY, androidx.ui.core.Px endX, androidx.ui.core.Px endY, androidx.ui.painting.TileMode tileMode = TileMode.clamp);
+ method public static androidx.ui.graphics.vectorgraphics.LinearGradient LinearGradient(kotlin.Pair<java.lang.Float,androidx.ui.graphics.Color>![] colorStops, androidx.ui.core.Px startX, androidx.ui.core.Px startY, androidx.ui.core.Px endX, androidx.ui.core.Px endY, androidx.ui.painting.TileMode tileMode = TileMode.clamp);
+ method public static androidx.ui.graphics.vectorgraphics.RadialGradient RadialGradient(kotlin.Pair<java.lang.Float,androidx.ui.graphics.Color>![] colorStops, float centerX, float centerY, float radius, androidx.ui.painting.TileMode tileMode = TileMode.clamp);
+ method public static androidx.ui.graphics.vectorgraphics.RadialGradient RadialGradient(androidx.ui.graphics.Color![] colors, float centerX, float centerY, float radius, androidx.ui.painting.TileMode tileMode = TileMode.clamp);
+ method public static androidx.ui.graphics.vectorgraphics.LinearGradient VerticalGradient(androidx.ui.graphics.Color![] colors, androidx.ui.core.Px startY, androidx.ui.core.Px endY, androidx.ui.painting.TileMode tileMode = TileMode.clamp);
+ method public static androidx.ui.graphics.vectorgraphics.LinearGradient VerticalGradient(kotlin.Pair<java.lang.Float,androidx.ui.graphics.Color>![] colorStops, androidx.ui.core.Px startY, androidx.ui.core.Px endY, androidx.ui.painting.TileMode tileMode = TileMode.clamp);
+ method public static androidx.ui.graphics.vectorgraphics.Brush getEmptyBrush();
+ method public static androidx.ui.graphics.vectorgraphics.Brush obtainBrush(Object? brush);
+ }
+
+ public final class GroupComponent extends androidx.ui.graphics.vectorgraphics.VNode {
+ ctor public GroupComponent(String name);
+ ctor public GroupComponent();
+ method public void draw(androidx.ui.painting.Canvas canvas);
+ method public androidx.ui.graphics.vectorgraphics.PathNode![] getClipPathNodes();
+ method public String getName();
+ method public float getPivotX();
+ method public float getPivotY();
+ method public float getRotation();
+ method public float getScaleX();
+ method public float getScaleY();
+ method public int getSize();
+ method public float getTranslationX();
+ method public float getTranslationY();
+ method public void insertAt(int index, androidx.ui.graphics.vectorgraphics.VNode instance);
+ method public void move(int from, int to, int count);
+ method public void remove(int index, int count);
+ method public void setClipPathNodes(androidx.ui.graphics.vectorgraphics.PathNode![] value);
+ method public void setPivotX(float value);
+ method public void setPivotY(float value);
+ method public void setRotation(float value);
+ method public void setScaleX(float value);
+ method public void setScaleY(float value);
+ method public void setTranslationX(float value);
+ method public void setTranslationY(float value);
+ property public final androidx.ui.graphics.vectorgraphics.PathNode![] clipPathNodes;
+ property public final float pivotX;
+ property public final float pivotY;
+ property public final float rotation;
+ property public final float scaleX;
+ property public final float scaleY;
+ property public final int size;
+ property public final float translationX;
+ property public final float translationY;
+ }
+
+ public final class LinearGradient implements androidx.ui.graphics.vectorgraphics.Brush {
+ method public void applyBrush(androidx.ui.painting.Paint p);
+ method public java.util.List<androidx.ui.graphics.Color> component1();
+ method public java.util.List<java.lang.Float>? component2();
+ method public androidx.ui.core.Px component3();
+ method public androidx.ui.core.Px component4();
+ method public androidx.ui.core.Px component5();
+ method public androidx.ui.core.Px component6();
+ method public androidx.ui.painting.TileMode component7();
+ method public androidx.ui.graphics.vectorgraphics.LinearGradient copy(java.util.List<androidx.ui.graphics.Color> colors, java.util.List<java.lang.Float>? stops, androidx.ui.core.Px startX, androidx.ui.core.Px startY, androidx.ui.core.Px endX, androidx.ui.core.Px endY, androidx.ui.painting.TileMode tileMode);
+ method public java.util.List<androidx.ui.graphics.Color> getColors();
+ method public androidx.ui.core.Px getEndX();
+ method public androidx.ui.core.Px getEndY();
+ method public androidx.ui.core.Px getStartX();
+ method public androidx.ui.core.Px getStartY();
+ method public java.util.List<java.lang.Float>? getStops();
+ method public androidx.ui.painting.TileMode getTileMode();
+ }
+
+ public final class PathBuilder {
+ ctor public PathBuilder();
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder arcTo(float horizontalEllipseRadius, float verticalEllipseRadius, float theta, float largeArcFlag, float sweepFlag, float x1, float y1);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder arcToRelative(float a, float b, float theta, float largeArcFlag, float sweepFlag, float x1, float y1);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder close();
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder curveTo(float x1, float y1, float x2, float y2, float x3, float y3);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder curveToRelative(float dx1, float dy1, float dx2, float dy2, float dx3, float dy3);
+ method public androidx.ui.graphics.vectorgraphics.PathNode![] getNodes();
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder horizontalLineTo(float x);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder horizontalLineToRelative(float x);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder lineTo(float x, float y);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder lineToRelative(float x, float y);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder moveTo(float x, float y);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder moveToRelative(float x, float y);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder quadTo(float x1, float y1, float x2, float y2);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder quadToRelative(float x1, float y1, float x2, float y2);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder reflectiveCurveTo(float x1, float y1, float x2, float y2);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder reflectiveCurveToRelative(float x1, float y1, float x2, float y2);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder reflectiveQuadTo(float x1, float y1);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder reflectiveQuadToRelative(float x1, float y1);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder verticalLineTo(float y);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder verticalLineToRelative(float y);
+ }
+
+ public enum PathCommand {
+ method public final char toKey();
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand ArcTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand Close;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand CurveTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand HorizontalLineTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand LineTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand MoveTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand QuadTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand ReflectiveCurveTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand ReflectiveQuadTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand RelativeArcTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand RelativeClose;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand RelativeCurveTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand RelativeHorizontalTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand RelativeLineTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand RelativeMoveTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand RelativeQuadTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand RelativeReflectiveCurveTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand RelativeReflectiveQuadTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand RelativeVerticalTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand VerticalLineTo;
+ }
+
+ public final class PathCommandKt {
+ ctor public PathCommandKt();
+ method public static androidx.ui.graphics.vectorgraphics.PathCommand toPathCommand(char);
+ }
+
+ public final class PathComponent extends androidx.ui.graphics.vectorgraphics.VNode {
+ ctor public PathComponent(String name);
+ method public void draw(androidx.ui.painting.Canvas canvas);
+ method public androidx.ui.graphics.vectorgraphics.Brush getFill();
+ method public float getFillAlpha();
+ method public String getName();
+ method public androidx.ui.graphics.vectorgraphics.PathNode![] getPathNodes();
+ method public androidx.ui.graphics.vectorgraphics.Brush getStroke();
+ method public float getStrokeAlpha();
+ method public androidx.ui.painting.StrokeCap getStrokeLineCap();
+ method public androidx.ui.painting.StrokeJoin getStrokeLineJoin();
+ method public float getStrokeLineMiter();
+ method public float getStrokeLineWidth();
+ method public void setFill(androidx.ui.graphics.vectorgraphics.Brush value);
+ method public void setFillAlpha(float value);
+ method public void setPathNodes(androidx.ui.graphics.vectorgraphics.PathNode![] value);
+ method public void setStroke(androidx.ui.graphics.vectorgraphics.Brush value);
+ method public void setStrokeAlpha(float value);
+ method public void setStrokeLineCap(androidx.ui.painting.StrokeCap value);
+ method public void setStrokeLineJoin(androidx.ui.painting.StrokeJoin value);
+ method public void setStrokeLineMiter(float value);
+ method public void setStrokeLineWidth(float value);
+ property public final androidx.ui.graphics.vectorgraphics.Brush fill;
+ property public final float fillAlpha;
+ property public final androidx.ui.graphics.vectorgraphics.PathNode![] pathNodes;
+ property public final androidx.ui.graphics.vectorgraphics.Brush stroke;
+ property public final float strokeAlpha;
+ property public final androidx.ui.painting.StrokeCap strokeLineCap;
+ property public final androidx.ui.painting.StrokeJoin strokeLineJoin;
+ property public final float strokeLineMiter;
+ property public final float strokeLineWidth;
+ }
+
+ public final class PathDelegate {
+ ctor public PathDelegate(kotlin.jvm.functions.Function1<? super androidx.ui.graphics.vectorgraphics.PathBuilder,kotlin.Unit> delegate);
+ method public kotlin.jvm.functions.Function1<androidx.ui.graphics.vectorgraphics.PathBuilder,kotlin.Unit> getDelegate();
+ }
+
+ public final class PathNode {
+ ctor public PathNode(androidx.ui.graphics.vectorgraphics.PathCommand command, float[] args);
+ method public androidx.ui.graphics.vectorgraphics.PathCommand component1();
+ method public float[] component2();
+ method public androidx.ui.graphics.vectorgraphics.PathNode copy(androidx.ui.graphics.vectorgraphics.PathCommand command, float[] args);
+ method public float[] getArgs();
+ method public androidx.ui.graphics.vectorgraphics.PathCommand getCommand();
+ }
+
+ public final class PathNodeKt {
+ ctor public PathNodeKt();
+ method public static operator StringBuilder plus(StringBuilder, androidx.ui.graphics.vectorgraphics.PathNode node);
+ }
+
+ public final class PathParser {
+ ctor public PathParser();
+ method public androidx.ui.graphics.vectorgraphics.PathParser addPathNodes(androidx.ui.graphics.vectorgraphics.PathNode![] nodes);
+ method public void clear();
+ method public androidx.ui.graphics.vectorgraphics.PathParser parsePathString(String pathData);
+ method public androidx.ui.graphics.vectorgraphics.PathNode![] toNodes();
+ method public androidx.ui.painting.Path toPath(androidx.ui.painting.Path target = androidx.ui.painting.Path());
+ }
+
+ public final class PathParserKt {
+ ctor public PathParserKt();
+ }
+
+ public final class RadialGradient implements androidx.ui.graphics.vectorgraphics.Brush {
+ method public void applyBrush(androidx.ui.painting.Paint p);
+ method public androidx.ui.graphics.vectorgraphics.RadialGradient copy(java.util.List<androidx.ui.graphics.Color> colors, java.util.List<java.lang.Float>? stops, float centerX, float centerY, float radius, androidx.ui.painting.TileMode tileMode);
+ }
+
+ public final class SolidColor implements androidx.ui.graphics.vectorgraphics.Brush {
+ ctor public SolidColor(androidx.ui.graphics.Color value);
+ method public void applyBrush(androidx.ui.painting.Paint p);
+ method public androidx.ui.graphics.vectorgraphics.SolidColor copy(androidx.ui.graphics.Color value);
+ }
+
+ public abstract sealed class VNode {
+ method public abstract void draw(androidx.ui.painting.Canvas canvas);
+ }
+
+ public final class VectorComponent extends androidx.ui.graphics.vectorgraphics.VNode {
+ ctor public VectorComponent(String name, float viewportWidth, float viewportHeight, androidx.ui.core.Px defaultWidth, androidx.ui.core.Px defaultHeight);
+ method public void draw(androidx.ui.painting.Canvas canvas);
+ method public androidx.ui.core.Px getDefaultHeight();
+ method public androidx.ui.core.Px getDefaultWidth();
+ method public String getName();
+ method public androidx.ui.graphics.vectorgraphics.GroupComponent getRoot();
+ method public int getSize();
+ method public float getViewportHeight();
+ method public float getViewportWidth();
+ method public void setDefaultHeight(androidx.ui.core.Px p);
+ method public void setDefaultWidth(androidx.ui.core.Px p);
+ method public void setViewportHeight(float p);
+ method public void setViewportWidth(float p);
+ property public final androidx.ui.graphics.vectorgraphics.GroupComponent root;
+ property public final int size;
+ }
+
+ public final class VectorKt {
+ ctor public VectorKt();
+ method public static androidx.ui.graphics.vectorgraphics.PathNode![] addPathNodes(String? pathStr);
+ method public static androidx.ui.graphics.vectorgraphics.PathNode![] createPath(Object? pathData);
+ method public static androidx.ui.painting.StrokeCap getDefaultStrokeLineCap();
+ method public static androidx.ui.painting.StrokeJoin getDefaultStrokeLineJoin();
+ method public static androidx.ui.graphics.vectorgraphics.PathNode![] getEmptyPath();
+ field public static final float DefaultAlpha = 1.0f;
+ field public static final String DefaultGroupName = "";
+ field public static final String DefaultPathName = "";
+ field public static final float DefaultPivotX = 0.0f;
+ field public static final float DefaultPivotY = 0.0f;
+ field public static final float DefaultRotation = 0.0f;
+ field public static final float DefaultScaleX = 1.0f;
+ field public static final float DefaultScaleY = 1.0f;
+ field public static final float DefaultStrokeLineMiter = 4.0f;
+ field public static final float DefaultStrokeLineWidth = 0.0f;
+ field public static final float DefaultTranslationX = 0.0f;
+ field public static final float DefaultTranslationY = 0.0f;
+ }
+
+}
+
package androidx.ui.painting {
public final class AndroidCanvasKt {
@@ -1441,7 +1624,7 @@
public static final class Gradient.Companion {
method public androidx.ui.painting.Gradient linear(androidx.ui.engine.geometry.Offset from, androidx.ui.engine.geometry.Offset to, java.util.List<androidx.ui.graphics.Color> colors, java.util.List<java.lang.Float>? colorStops = null, androidx.ui.painting.TileMode tileMode = TileMode.clamp);
- method public androidx.ui.painting.Gradient radial(androidx.ui.engine.geometry.Offset center, float radius, java.util.List<androidx.ui.graphics.Color> color, java.util.List<java.lang.Float>? colorStops, androidx.ui.painting.TileMode tileMode = TileMode.clamp, androidx.ui.vectormath64.Matrix4 matrix4, androidx.ui.engine.geometry.Offset? focal, float focalRadius);
+ method public androidx.ui.painting.Gradient radial(androidx.ui.engine.geometry.Offset center, float radius, java.util.List<androidx.ui.graphics.Color> color, java.util.List<java.lang.Float>? colorStops, androidx.ui.painting.TileMode tileMode = TileMode.clamp, androidx.ui.vectormath64.Matrix4 matrix4);
method public androidx.ui.painting.Gradient sweep(androidx.ui.engine.geometry.Offset center, java.util.List<androidx.ui.graphics.Color> colors, java.util.List<java.lang.Float> colorStops, androidx.ui.painting.TileMode tileMode = TileMode.clamp, float startAngle, float endAngle = 6.2831855f, androidx.ui.vectormath64.Matrix4 matrix4);
}
@@ -1669,6 +1852,40 @@
}
+package androidx.ui.semantics {
+
+ public final class AccessibilityAction<T extends kotlin.Function<? extends kotlin.Unit>> {
+ ctor public AccessibilityAction(String? label, T action);
+ method public String? component1();
+ method public T component2();
+ method public androidx.ui.semantics.AccessibilityAction<T> copy(String? label, T action);
+ method public T getAction();
+ method public String? getLabel();
+ }
+
+ public class SemanticsPropertyKey<T> implements kotlin.properties.ReadWriteProperty<androidx.ui.semantics.SemanticsPropertyReceiver,T> {
+ ctor public SemanticsPropertyKey(String name);
+ method public final String getName();
+ method public final T! getValue(androidx.ui.semantics.SemanticsPropertyReceiver thisRef, kotlin.reflect.KProperty<?> property);
+ method public T! merge(T? existingValue, T? newValue);
+ method public final void setValue(androidx.ui.semantics.SemanticsPropertyReceiver thisRef, kotlin.reflect.KProperty<?> property, T? value);
+ }
+
+ public interface SemanticsPropertyReceiver {
+ method public operator <T> void set(androidx.ui.semantics.SemanticsPropertyKey<T> key, T? value);
+ }
+
+}
+
+package androidx.ui.temputils {
+
+ public final class CoroutineUtilsKt {
+ ctor public CoroutineUtilsKt();
+ method public static kotlinx.coroutines.Job delay(androidx.ui.core.Duration duration, kotlin.coroutines.CoroutineContext context, kotlin.jvm.functions.Function0<kotlin.Unit> block);
+ }
+
+}
+
package androidx.ui.testutils {
public final class PointerInputTestUtilKt {
@@ -1676,6 +1893,7 @@
method public static androidx.ui.core.PointerInputChange consume(androidx.ui.core.PointerInputChange, float dx = 0f, float dy = 0f, boolean downChange = false);
method public static androidx.ui.core.PointerInputChange down(int id = 0, androidx.ui.core.Timestamp timestamp = 0.millisecondsToTimestamp(), float x = 0f, float y = 0f);
method public static java.util.List<androidx.ui.core.PointerInputChange> invokeOverAllPasses(kotlin.jvm.functions.Function2<? super java.util.List<androidx.ui.core.PointerInputChange>,? super androidx.ui.core.PointerEventPass,? extends java.util.List<androidx.ui.core.PointerInputChange>>, java.util.List<androidx.ui.core.PointerInputChange> pointerInputChanges);
+ method public static java.util.List<androidx.ui.core.PointerInputChange> invokeOverAllPasses(kotlin.jvm.functions.Function2<? super java.util.List<androidx.ui.core.PointerInputChange>,? super androidx.ui.core.PointerEventPass,? extends java.util.List<androidx.ui.core.PointerInputChange>>, androidx.ui.core.PointerInputChange... pointerInputChanges);
method public static java.util.List<androidx.ui.core.PointerInputChange> invokeOverPasses(kotlin.jvm.functions.Function2<? super java.util.List<androidx.ui.core.PointerInputChange>,? super androidx.ui.core.PointerEventPass,? extends java.util.List<androidx.ui.core.PointerInputChange>>, java.util.List<androidx.ui.core.PointerInputChange> pointerInputChanges, androidx.ui.core.PointerEventPass... pointerEventPasses);
method public static androidx.ui.core.PointerInputChange moveBy(androidx.ui.core.PointerInputChange, androidx.ui.core.Duration duration, float dx = 0f, float dy = 0f);
method public static androidx.ui.core.PointerInputChange moveTo(androidx.ui.core.PointerInputChange, androidx.ui.core.Timestamp timestamp, float x = 0f, float y = 0f);
diff --git a/ui/ui-core/api/api_lint.ignore b/ui/ui-core/api/api_lint.ignore
index a0c8d75..c321320 100644
--- a/ui/ui-core/api/api_lint.ignore
+++ b/ui/ui-core/api/api_lint.ignore
@@ -106,17 +106,17 @@
KotlinOperator: androidx.ui.engine.geometry.RRect#contains(androidx.ui.engine.geometry.Offset):
- Method can be invoked as a "in" operator from Kotlin: `contains` (this is usually desirable; just make sure it makes sense for this type of object)
+ Note that adding the `operator` keyword would allow calling this method using operator syntax
KotlinOperator: androidx.ui.engine.geometry.Rect#contains(androidx.ui.engine.geometry.Offset):
- Method can be invoked as a "in" operator from Kotlin: `contains` (this is usually desirable; just make sure it makes sense for this type of object)
+ Note that adding the `operator` keyword would allow calling this method using operator syntax
KotlinOperator: androidx.ui.engine.geometry.Size#contains(androidx.ui.engine.geometry.Offset):
- Method can be invoked as a "in" operator from Kotlin: `contains` (this is usually desirable; just make sure it makes sense for this type of object)
+ Note that adding the `operator` keyword would allow calling this method using operator syntax
KotlinOperator: androidx.ui.painting.Path#contains(androidx.ui.engine.geometry.Offset):
- Method can be invoked as a "in" operator from Kotlin: `contains` (this is usually desirable; just make sure it makes sense for this type of object)
+ Note that adding the `operator` keyword would allow calling this method using operator syntax
KotlinOperator: androidx.ui.text.TextRange#contains(androidx.ui.text.TextRange):
- Method can be invoked as a "in" operator from Kotlin: `contains` (this is usually desirable; just make sure it makes sense for this type of object)
+ Note that adding the `operator` keyword would allow calling this method using operator syntax
KotlinOperator: androidx.ui.text.TextRange#contains(int):
- Method can be invoked as a "in" operator from Kotlin: `contains` (this is usually desirable; just make sure it makes sense for this type of object)
+ Note that adding the `operator` keyword would allow calling this method using operator syntax
MethodNameUnits: androidx.ui.core.Durations#inSeconds(androidx.ui.core.Duration):
diff --git a/ui/ui-core/api/current.txt b/ui/ui-core/api/current.txt
index 88194bc..1648b3a 100644
--- a/ui/ui-core/api/current.txt
+++ b/ui/ui-core/api/current.txt
@@ -479,7 +479,9 @@
public static final class Px.Companion {
method public androidx.ui.core.Px getInfinity();
+ method public androidx.ui.core.Px getZero();
property public final androidx.ui.core.Px Infinity;
+ property public final androidx.ui.core.Px Zero;
}
public final class PxBounds {
@@ -708,74 +710,6 @@
}
-package androidx.ui.core.semantics {
-
- public final class SemanticsAction<T> {
- ctor public SemanticsAction(androidx.ui.core.semantics.SemanticsActionType<T> type, T handler);
- method public androidx.ui.core.semantics.SemanticsActionType<T> component1();
- method public T component2();
- method public androidx.ui.core.semantics.SemanticsAction<T> copy(androidx.ui.core.semantics.SemanticsActionType<T> type, T handler);
- method public T getHandler();
- method public androidx.ui.core.semantics.SemanticsActionType<T> getType();
- method public void invokeHandler(Object? args);
- }
-
- public final class SemanticsActionType<T> {
- method public int getBitmask();
- field public static final androidx.ui.core.semantics.SemanticsActionType.Companion! Companion;
- }
-
- public static final class SemanticsActionType.Companion {
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getCopy();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getCustomAction();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getCut();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getDecrease();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getDidGainAccessibilityFocus();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getDidLoseAccessibilityFocus();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getDismiss();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getIncrease();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getLongPress();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function1<java.lang.Boolean,kotlin.Unit>> getMoveCursorBackwardByCharacter();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function1<java.lang.Boolean,kotlin.Unit>> getMoveCursorBackwardByWord();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function1<java.lang.Boolean,kotlin.Unit>> getMoveCursorForwardByCharacter();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function1<java.lang.Boolean,kotlin.Unit>> getMoveCursorForwardByWord();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getPaste();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getScrollDown();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getScrollLeft();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getScrollRight();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getScrollUp();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function1<androidx.ui.text.TextSelection,kotlin.Unit>> getSetSelection();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getShowOnScreen();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getTap();
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> Copy;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> CustomAction;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> Cut;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> Decrease;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> DidGainAccessibilityFocus;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> DidLoseAccessibilityFocus;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> Dismiss;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> Increase;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> LongPress;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function1<java.lang.Boolean,kotlin.Unit>> MoveCursorBackwardByCharacter;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function1<java.lang.Boolean,kotlin.Unit>> MoveCursorBackwardByWord;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function1<java.lang.Boolean,kotlin.Unit>> MoveCursorForwardByCharacter;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function1<java.lang.Boolean,kotlin.Unit>> MoveCursorForwardByWord;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> Paste;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> ScrollDown;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> ScrollLeft;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> ScrollRight;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> ScrollUp;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function1<androidx.ui.text.TextSelection,kotlin.Unit>> SetSelection;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> ShowOnScreen;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> Tap;
- }
-
- public final class SemanticsActionsKt {
- ctor public SemanticsActionsKt();
- }
-
-}
-
package androidx.ui.engine.geometry {
public final class Offset implements androidx.ui.engine.geometry.OffsetBase {
@@ -1320,6 +1254,255 @@
}
+package androidx.ui.graphics.vectorgraphics {
+
+ public interface Brush {
+ method public void applyBrush(androidx.ui.painting.Paint p);
+ }
+
+ public final class BrushKt {
+ ctor public BrushKt();
+ method public static androidx.ui.graphics.vectorgraphics.LinearGradient HorizontalGradient(androidx.ui.graphics.Color![] colors, androidx.ui.core.Px startX, androidx.ui.core.Px endX, androidx.ui.painting.TileMode tileMode = TileMode.clamp);
+ method public static androidx.ui.graphics.vectorgraphics.Brush HorizontalGradient(kotlin.Pair<java.lang.Float,androidx.ui.graphics.Color>![] colorStops, androidx.ui.core.Px startX, androidx.ui.core.Px endX, androidx.ui.painting.TileMode tileMode = TileMode.clamp);
+ method public static androidx.ui.graphics.vectorgraphics.LinearGradient LinearGradient(androidx.ui.graphics.Color![] colors, androidx.ui.core.Px startX, androidx.ui.core.Px startY, androidx.ui.core.Px endX, androidx.ui.core.Px endY, androidx.ui.painting.TileMode tileMode = TileMode.clamp);
+ method public static androidx.ui.graphics.vectorgraphics.LinearGradient LinearGradient(kotlin.Pair<java.lang.Float,androidx.ui.graphics.Color>![] colorStops, androidx.ui.core.Px startX, androidx.ui.core.Px startY, androidx.ui.core.Px endX, androidx.ui.core.Px endY, androidx.ui.painting.TileMode tileMode = TileMode.clamp);
+ method public static androidx.ui.graphics.vectorgraphics.RadialGradient RadialGradient(kotlin.Pair<java.lang.Float,androidx.ui.graphics.Color>![] colorStops, float centerX, float centerY, float radius, androidx.ui.painting.TileMode tileMode = TileMode.clamp);
+ method public static androidx.ui.graphics.vectorgraphics.RadialGradient RadialGradient(androidx.ui.graphics.Color![] colors, float centerX, float centerY, float radius, androidx.ui.painting.TileMode tileMode = TileMode.clamp);
+ method public static androidx.ui.graphics.vectorgraphics.LinearGradient VerticalGradient(androidx.ui.graphics.Color![] colors, androidx.ui.core.Px startY, androidx.ui.core.Px endY, androidx.ui.painting.TileMode tileMode = TileMode.clamp);
+ method public static androidx.ui.graphics.vectorgraphics.LinearGradient VerticalGradient(kotlin.Pair<java.lang.Float,androidx.ui.graphics.Color>![] colorStops, androidx.ui.core.Px startY, androidx.ui.core.Px endY, androidx.ui.painting.TileMode tileMode = TileMode.clamp);
+ method public static androidx.ui.graphics.vectorgraphics.Brush getEmptyBrush();
+ method public static androidx.ui.graphics.vectorgraphics.Brush obtainBrush(Object? brush);
+ }
+
+ public final class GroupComponent extends androidx.ui.graphics.vectorgraphics.VNode {
+ ctor public GroupComponent(String name);
+ ctor public GroupComponent();
+ method public void draw(androidx.ui.painting.Canvas canvas);
+ method public androidx.ui.graphics.vectorgraphics.PathNode![] getClipPathNodes();
+ method public String getName();
+ method public float getPivotX();
+ method public float getPivotY();
+ method public float getRotation();
+ method public float getScaleX();
+ method public float getScaleY();
+ method public int getSize();
+ method public float getTranslationX();
+ method public float getTranslationY();
+ method public void insertAt(int index, androidx.ui.graphics.vectorgraphics.VNode instance);
+ method public void move(int from, int to, int count);
+ method public void remove(int index, int count);
+ method public void setClipPathNodes(androidx.ui.graphics.vectorgraphics.PathNode![] value);
+ method public void setPivotX(float value);
+ method public void setPivotY(float value);
+ method public void setRotation(float value);
+ method public void setScaleX(float value);
+ method public void setScaleY(float value);
+ method public void setTranslationX(float value);
+ method public void setTranslationY(float value);
+ property public final androidx.ui.graphics.vectorgraphics.PathNode![] clipPathNodes;
+ property public final float pivotX;
+ property public final float pivotY;
+ property public final float rotation;
+ property public final float scaleX;
+ property public final float scaleY;
+ property public final int size;
+ property public final float translationX;
+ property public final float translationY;
+ }
+
+ public final class LinearGradient implements androidx.ui.graphics.vectorgraphics.Brush {
+ method public void applyBrush(androidx.ui.painting.Paint p);
+ method public java.util.List<androidx.ui.graphics.Color> component1();
+ method public java.util.List<java.lang.Float>? component2();
+ method public androidx.ui.core.Px component3();
+ method public androidx.ui.core.Px component4();
+ method public androidx.ui.core.Px component5();
+ method public androidx.ui.core.Px component6();
+ method public androidx.ui.painting.TileMode component7();
+ method public androidx.ui.graphics.vectorgraphics.LinearGradient copy(java.util.List<androidx.ui.graphics.Color> colors, java.util.List<java.lang.Float>? stops, androidx.ui.core.Px startX, androidx.ui.core.Px startY, androidx.ui.core.Px endX, androidx.ui.core.Px endY, androidx.ui.painting.TileMode tileMode);
+ method public java.util.List<androidx.ui.graphics.Color> getColors();
+ method public androidx.ui.core.Px getEndX();
+ method public androidx.ui.core.Px getEndY();
+ method public androidx.ui.core.Px getStartX();
+ method public androidx.ui.core.Px getStartY();
+ method public java.util.List<java.lang.Float>? getStops();
+ method public androidx.ui.painting.TileMode getTileMode();
+ }
+
+ public final class PathBuilder {
+ ctor public PathBuilder();
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder arcTo(float horizontalEllipseRadius, float verticalEllipseRadius, float theta, float largeArcFlag, float sweepFlag, float x1, float y1);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder arcToRelative(float a, float b, float theta, float largeArcFlag, float sweepFlag, float x1, float y1);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder close();
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder curveTo(float x1, float y1, float x2, float y2, float x3, float y3);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder curveToRelative(float dx1, float dy1, float dx2, float dy2, float dx3, float dy3);
+ method public androidx.ui.graphics.vectorgraphics.PathNode![] getNodes();
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder horizontalLineTo(float x);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder horizontalLineToRelative(float x);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder lineTo(float x, float y);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder lineToRelative(float x, float y);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder moveTo(float x, float y);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder moveToRelative(float x, float y);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder quadTo(float x1, float y1, float x2, float y2);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder quadToRelative(float x1, float y1, float x2, float y2);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder reflectiveCurveTo(float x1, float y1, float x2, float y2);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder reflectiveCurveToRelative(float x1, float y1, float x2, float y2);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder reflectiveQuadTo(float x1, float y1);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder reflectiveQuadToRelative(float x1, float y1);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder verticalLineTo(float y);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder verticalLineToRelative(float y);
+ }
+
+ public enum PathCommand {
+ method public final char toKey();
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand ArcTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand Close;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand CurveTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand HorizontalLineTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand LineTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand MoveTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand QuadTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand ReflectiveCurveTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand ReflectiveQuadTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand RelativeArcTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand RelativeClose;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand RelativeCurveTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand RelativeHorizontalTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand RelativeLineTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand RelativeMoveTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand RelativeQuadTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand RelativeReflectiveCurveTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand RelativeReflectiveQuadTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand RelativeVerticalTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand VerticalLineTo;
+ }
+
+ public final class PathCommandKt {
+ ctor public PathCommandKt();
+ method public static androidx.ui.graphics.vectorgraphics.PathCommand toPathCommand(char);
+ }
+
+ public final class PathComponent extends androidx.ui.graphics.vectorgraphics.VNode {
+ ctor public PathComponent(String name);
+ method public void draw(androidx.ui.painting.Canvas canvas);
+ method public androidx.ui.graphics.vectorgraphics.Brush getFill();
+ method public float getFillAlpha();
+ method public String getName();
+ method public androidx.ui.graphics.vectorgraphics.PathNode![] getPathNodes();
+ method public androidx.ui.graphics.vectorgraphics.Brush getStroke();
+ method public float getStrokeAlpha();
+ method public androidx.ui.painting.StrokeCap getStrokeLineCap();
+ method public androidx.ui.painting.StrokeJoin getStrokeLineJoin();
+ method public float getStrokeLineMiter();
+ method public float getStrokeLineWidth();
+ method public void setFill(androidx.ui.graphics.vectorgraphics.Brush value);
+ method public void setFillAlpha(float value);
+ method public void setPathNodes(androidx.ui.graphics.vectorgraphics.PathNode![] value);
+ method public void setStroke(androidx.ui.graphics.vectorgraphics.Brush value);
+ method public void setStrokeAlpha(float value);
+ method public void setStrokeLineCap(androidx.ui.painting.StrokeCap value);
+ method public void setStrokeLineJoin(androidx.ui.painting.StrokeJoin value);
+ method public void setStrokeLineMiter(float value);
+ method public void setStrokeLineWidth(float value);
+ property public final androidx.ui.graphics.vectorgraphics.Brush fill;
+ property public final float fillAlpha;
+ property public final androidx.ui.graphics.vectorgraphics.PathNode![] pathNodes;
+ property public final androidx.ui.graphics.vectorgraphics.Brush stroke;
+ property public final float strokeAlpha;
+ property public final androidx.ui.painting.StrokeCap strokeLineCap;
+ property public final androidx.ui.painting.StrokeJoin strokeLineJoin;
+ property public final float strokeLineMiter;
+ property public final float strokeLineWidth;
+ }
+
+ public final class PathDelegate {
+ ctor public PathDelegate(kotlin.jvm.functions.Function1<? super androidx.ui.graphics.vectorgraphics.PathBuilder,kotlin.Unit> delegate);
+ method public kotlin.jvm.functions.Function1<androidx.ui.graphics.vectorgraphics.PathBuilder,kotlin.Unit> getDelegate();
+ }
+
+ public final class PathNode {
+ ctor public PathNode(androidx.ui.graphics.vectorgraphics.PathCommand command, float[] args);
+ method public androidx.ui.graphics.vectorgraphics.PathCommand component1();
+ method public float[] component2();
+ method public androidx.ui.graphics.vectorgraphics.PathNode copy(androidx.ui.graphics.vectorgraphics.PathCommand command, float[] args);
+ method public float[] getArgs();
+ method public androidx.ui.graphics.vectorgraphics.PathCommand getCommand();
+ }
+
+ public final class PathNodeKt {
+ ctor public PathNodeKt();
+ method public static operator StringBuilder plus(StringBuilder, androidx.ui.graphics.vectorgraphics.PathNode node);
+ }
+
+ public final class PathParser {
+ ctor public PathParser();
+ method public androidx.ui.graphics.vectorgraphics.PathParser addPathNodes(androidx.ui.graphics.vectorgraphics.PathNode![] nodes);
+ method public void clear();
+ method public androidx.ui.graphics.vectorgraphics.PathParser parsePathString(String pathData);
+ method public androidx.ui.graphics.vectorgraphics.PathNode![] toNodes();
+ method public androidx.ui.painting.Path toPath(androidx.ui.painting.Path target = androidx.ui.painting.Path());
+ }
+
+ public final class PathParserKt {
+ ctor public PathParserKt();
+ }
+
+ public final class RadialGradient implements androidx.ui.graphics.vectorgraphics.Brush {
+ method public void applyBrush(androidx.ui.painting.Paint p);
+ method public androidx.ui.graphics.vectorgraphics.RadialGradient copy(java.util.List<androidx.ui.graphics.Color> colors, java.util.List<java.lang.Float>? stops, float centerX, float centerY, float radius, androidx.ui.painting.TileMode tileMode);
+ }
+
+ public final class SolidColor implements androidx.ui.graphics.vectorgraphics.Brush {
+ ctor public SolidColor(androidx.ui.graphics.Color value);
+ method public void applyBrush(androidx.ui.painting.Paint p);
+ method public androidx.ui.graphics.vectorgraphics.SolidColor copy(androidx.ui.graphics.Color value);
+ }
+
+ public abstract sealed class VNode {
+ method public abstract void draw(androidx.ui.painting.Canvas canvas);
+ }
+
+ public final class VectorComponent extends androidx.ui.graphics.vectorgraphics.VNode {
+ ctor public VectorComponent(String name, float viewportWidth, float viewportHeight, androidx.ui.core.Px defaultWidth, androidx.ui.core.Px defaultHeight);
+ method public void draw(androidx.ui.painting.Canvas canvas);
+ method public androidx.ui.core.Px getDefaultHeight();
+ method public androidx.ui.core.Px getDefaultWidth();
+ method public String getName();
+ method public androidx.ui.graphics.vectorgraphics.GroupComponent getRoot();
+ method public int getSize();
+ method public float getViewportHeight();
+ method public float getViewportWidth();
+ method public void setDefaultHeight(androidx.ui.core.Px p);
+ method public void setDefaultWidth(androidx.ui.core.Px p);
+ method public void setViewportHeight(float p);
+ method public void setViewportWidth(float p);
+ property public final androidx.ui.graphics.vectorgraphics.GroupComponent root;
+ property public final int size;
+ }
+
+ public final class VectorKt {
+ ctor public VectorKt();
+ method public static androidx.ui.graphics.vectorgraphics.PathNode![] addPathNodes(String? pathStr);
+ method public static androidx.ui.graphics.vectorgraphics.PathNode![] createPath(Object? pathData);
+ method public static androidx.ui.painting.StrokeCap getDefaultStrokeLineCap();
+ method public static androidx.ui.painting.StrokeJoin getDefaultStrokeLineJoin();
+ method public static androidx.ui.graphics.vectorgraphics.PathNode![] getEmptyPath();
+ field public static final float DefaultAlpha = 1.0f;
+ field public static final String DefaultGroupName = "";
+ field public static final String DefaultPathName = "";
+ field public static final float DefaultPivotX = 0.0f;
+ field public static final float DefaultPivotY = 0.0f;
+ field public static final float DefaultRotation = 0.0f;
+ field public static final float DefaultScaleX = 1.0f;
+ field public static final float DefaultScaleY = 1.0f;
+ field public static final float DefaultStrokeLineMiter = 4.0f;
+ field public static final float DefaultStrokeLineWidth = 0.0f;
+ field public static final float DefaultTranslationX = 0.0f;
+ field public static final float DefaultTranslationY = 0.0f;
+ }
+
+}
+
package androidx.ui.painting {
public final class AndroidCanvasKt {
@@ -1441,7 +1624,7 @@
public static final class Gradient.Companion {
method public androidx.ui.painting.Gradient linear(androidx.ui.engine.geometry.Offset from, androidx.ui.engine.geometry.Offset to, java.util.List<androidx.ui.graphics.Color> colors, java.util.List<java.lang.Float>? colorStops = null, androidx.ui.painting.TileMode tileMode = TileMode.clamp);
- method public androidx.ui.painting.Gradient radial(androidx.ui.engine.geometry.Offset center, float radius, java.util.List<androidx.ui.graphics.Color> color, java.util.List<java.lang.Float>? colorStops, androidx.ui.painting.TileMode tileMode = TileMode.clamp, androidx.ui.vectormath64.Matrix4 matrix4, androidx.ui.engine.geometry.Offset? focal, float focalRadius);
+ method public androidx.ui.painting.Gradient radial(androidx.ui.engine.geometry.Offset center, float radius, java.util.List<androidx.ui.graphics.Color> color, java.util.List<java.lang.Float>? colorStops, androidx.ui.painting.TileMode tileMode = TileMode.clamp, androidx.ui.vectormath64.Matrix4 matrix4);
method public androidx.ui.painting.Gradient sweep(androidx.ui.engine.geometry.Offset center, java.util.List<androidx.ui.graphics.Color> colors, java.util.List<java.lang.Float> colorStops, androidx.ui.painting.TileMode tileMode = TileMode.clamp, float startAngle, float endAngle = 6.2831855f, androidx.ui.vectormath64.Matrix4 matrix4);
}
@@ -1669,6 +1852,40 @@
}
+package androidx.ui.semantics {
+
+ public final class AccessibilityAction<T extends kotlin.Function<? extends kotlin.Unit>> {
+ ctor public AccessibilityAction(String? label, T action);
+ method public String? component1();
+ method public T component2();
+ method public androidx.ui.semantics.AccessibilityAction<T> copy(String? label, T action);
+ method public T getAction();
+ method public String? getLabel();
+ }
+
+ public class SemanticsPropertyKey<T> implements kotlin.properties.ReadWriteProperty<androidx.ui.semantics.SemanticsPropertyReceiver,T> {
+ ctor public SemanticsPropertyKey(String name);
+ method public final String getName();
+ method public final T! getValue(androidx.ui.semantics.SemanticsPropertyReceiver thisRef, kotlin.reflect.KProperty<?> property);
+ method public T! merge(T? existingValue, T? newValue);
+ method public final void setValue(androidx.ui.semantics.SemanticsPropertyReceiver thisRef, kotlin.reflect.KProperty<?> property, T? value);
+ }
+
+ public interface SemanticsPropertyReceiver {
+ method public operator <T> void set(androidx.ui.semantics.SemanticsPropertyKey<T> key, T? value);
+ }
+
+}
+
+package androidx.ui.temputils {
+
+ public final class CoroutineUtilsKt {
+ ctor public CoroutineUtilsKt();
+ method public static kotlinx.coroutines.Job delay(androidx.ui.core.Duration duration, kotlin.coroutines.CoroutineContext context, kotlin.jvm.functions.Function0<kotlin.Unit> block);
+ }
+
+}
+
package androidx.ui.testutils {
public final class PointerInputTestUtilKt {
@@ -1676,6 +1893,7 @@
method public static androidx.ui.core.PointerInputChange consume(androidx.ui.core.PointerInputChange, float dx = 0f, float dy = 0f, boolean downChange = false);
method public static androidx.ui.core.PointerInputChange down(int id = 0, androidx.ui.core.Timestamp timestamp = 0.millisecondsToTimestamp(), float x = 0f, float y = 0f);
method public static java.util.List<androidx.ui.core.PointerInputChange> invokeOverAllPasses(kotlin.jvm.functions.Function2<? super java.util.List<androidx.ui.core.PointerInputChange>,? super androidx.ui.core.PointerEventPass,? extends java.util.List<androidx.ui.core.PointerInputChange>>, java.util.List<androidx.ui.core.PointerInputChange> pointerInputChanges);
+ method public static java.util.List<androidx.ui.core.PointerInputChange> invokeOverAllPasses(kotlin.jvm.functions.Function2<? super java.util.List<androidx.ui.core.PointerInputChange>,? super androidx.ui.core.PointerEventPass,? extends java.util.List<androidx.ui.core.PointerInputChange>>, androidx.ui.core.PointerInputChange... pointerInputChanges);
method public static java.util.List<androidx.ui.core.PointerInputChange> invokeOverPasses(kotlin.jvm.functions.Function2<? super java.util.List<androidx.ui.core.PointerInputChange>,? super androidx.ui.core.PointerEventPass,? extends java.util.List<androidx.ui.core.PointerInputChange>>, java.util.List<androidx.ui.core.PointerInputChange> pointerInputChanges, androidx.ui.core.PointerEventPass... pointerEventPasses);
method public static androidx.ui.core.PointerInputChange moveBy(androidx.ui.core.PointerInputChange, androidx.ui.core.Duration duration, float dx = 0f, float dy = 0f);
method public static androidx.ui.core.PointerInputChange moveTo(androidx.ui.core.PointerInputChange, androidx.ui.core.Timestamp timestamp, float x = 0f, float y = 0f);
diff --git a/ui/ui-core/api/restricted_1.0.0-alpha01.txt b/ui/ui-core/api/restricted_1.0.0-alpha01.txt
index 88194bc..1648b3a 100644
--- a/ui/ui-core/api/restricted_1.0.0-alpha01.txt
+++ b/ui/ui-core/api/restricted_1.0.0-alpha01.txt
@@ -479,7 +479,9 @@
public static final class Px.Companion {
method public androidx.ui.core.Px getInfinity();
+ method public androidx.ui.core.Px getZero();
property public final androidx.ui.core.Px Infinity;
+ property public final androidx.ui.core.Px Zero;
}
public final class PxBounds {
@@ -708,74 +710,6 @@
}
-package androidx.ui.core.semantics {
-
- public final class SemanticsAction<T> {
- ctor public SemanticsAction(androidx.ui.core.semantics.SemanticsActionType<T> type, T handler);
- method public androidx.ui.core.semantics.SemanticsActionType<T> component1();
- method public T component2();
- method public androidx.ui.core.semantics.SemanticsAction<T> copy(androidx.ui.core.semantics.SemanticsActionType<T> type, T handler);
- method public T getHandler();
- method public androidx.ui.core.semantics.SemanticsActionType<T> getType();
- method public void invokeHandler(Object? args);
- }
-
- public final class SemanticsActionType<T> {
- method public int getBitmask();
- field public static final androidx.ui.core.semantics.SemanticsActionType.Companion! Companion;
- }
-
- public static final class SemanticsActionType.Companion {
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getCopy();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getCustomAction();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getCut();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getDecrease();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getDidGainAccessibilityFocus();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getDidLoseAccessibilityFocus();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getDismiss();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getIncrease();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getLongPress();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function1<java.lang.Boolean,kotlin.Unit>> getMoveCursorBackwardByCharacter();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function1<java.lang.Boolean,kotlin.Unit>> getMoveCursorBackwardByWord();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function1<java.lang.Boolean,kotlin.Unit>> getMoveCursorForwardByCharacter();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function1<java.lang.Boolean,kotlin.Unit>> getMoveCursorForwardByWord();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getPaste();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getScrollDown();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getScrollLeft();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getScrollRight();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getScrollUp();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function1<androidx.ui.text.TextSelection,kotlin.Unit>> getSetSelection();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getShowOnScreen();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getTap();
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> Copy;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> CustomAction;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> Cut;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> Decrease;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> DidGainAccessibilityFocus;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> DidLoseAccessibilityFocus;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> Dismiss;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> Increase;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> LongPress;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function1<java.lang.Boolean,kotlin.Unit>> MoveCursorBackwardByCharacter;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function1<java.lang.Boolean,kotlin.Unit>> MoveCursorBackwardByWord;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function1<java.lang.Boolean,kotlin.Unit>> MoveCursorForwardByCharacter;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function1<java.lang.Boolean,kotlin.Unit>> MoveCursorForwardByWord;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> Paste;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> ScrollDown;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> ScrollLeft;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> ScrollRight;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> ScrollUp;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function1<androidx.ui.text.TextSelection,kotlin.Unit>> SetSelection;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> ShowOnScreen;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> Tap;
- }
-
- public final class SemanticsActionsKt {
- ctor public SemanticsActionsKt();
- }
-
-}
-
package androidx.ui.engine.geometry {
public final class Offset implements androidx.ui.engine.geometry.OffsetBase {
@@ -1320,6 +1254,255 @@
}
+package androidx.ui.graphics.vectorgraphics {
+
+ public interface Brush {
+ method public void applyBrush(androidx.ui.painting.Paint p);
+ }
+
+ public final class BrushKt {
+ ctor public BrushKt();
+ method public static androidx.ui.graphics.vectorgraphics.LinearGradient HorizontalGradient(androidx.ui.graphics.Color![] colors, androidx.ui.core.Px startX, androidx.ui.core.Px endX, androidx.ui.painting.TileMode tileMode = TileMode.clamp);
+ method public static androidx.ui.graphics.vectorgraphics.Brush HorizontalGradient(kotlin.Pair<java.lang.Float,androidx.ui.graphics.Color>![] colorStops, androidx.ui.core.Px startX, androidx.ui.core.Px endX, androidx.ui.painting.TileMode tileMode = TileMode.clamp);
+ method public static androidx.ui.graphics.vectorgraphics.LinearGradient LinearGradient(androidx.ui.graphics.Color![] colors, androidx.ui.core.Px startX, androidx.ui.core.Px startY, androidx.ui.core.Px endX, androidx.ui.core.Px endY, androidx.ui.painting.TileMode tileMode = TileMode.clamp);
+ method public static androidx.ui.graphics.vectorgraphics.LinearGradient LinearGradient(kotlin.Pair<java.lang.Float,androidx.ui.graphics.Color>![] colorStops, androidx.ui.core.Px startX, androidx.ui.core.Px startY, androidx.ui.core.Px endX, androidx.ui.core.Px endY, androidx.ui.painting.TileMode tileMode = TileMode.clamp);
+ method public static androidx.ui.graphics.vectorgraphics.RadialGradient RadialGradient(kotlin.Pair<java.lang.Float,androidx.ui.graphics.Color>![] colorStops, float centerX, float centerY, float radius, androidx.ui.painting.TileMode tileMode = TileMode.clamp);
+ method public static androidx.ui.graphics.vectorgraphics.RadialGradient RadialGradient(androidx.ui.graphics.Color![] colors, float centerX, float centerY, float radius, androidx.ui.painting.TileMode tileMode = TileMode.clamp);
+ method public static androidx.ui.graphics.vectorgraphics.LinearGradient VerticalGradient(androidx.ui.graphics.Color![] colors, androidx.ui.core.Px startY, androidx.ui.core.Px endY, androidx.ui.painting.TileMode tileMode = TileMode.clamp);
+ method public static androidx.ui.graphics.vectorgraphics.LinearGradient VerticalGradient(kotlin.Pair<java.lang.Float,androidx.ui.graphics.Color>![] colorStops, androidx.ui.core.Px startY, androidx.ui.core.Px endY, androidx.ui.painting.TileMode tileMode = TileMode.clamp);
+ method public static androidx.ui.graphics.vectorgraphics.Brush getEmptyBrush();
+ method public static androidx.ui.graphics.vectorgraphics.Brush obtainBrush(Object? brush);
+ }
+
+ public final class GroupComponent extends androidx.ui.graphics.vectorgraphics.VNode {
+ ctor public GroupComponent(String name);
+ ctor public GroupComponent();
+ method public void draw(androidx.ui.painting.Canvas canvas);
+ method public androidx.ui.graphics.vectorgraphics.PathNode![] getClipPathNodes();
+ method public String getName();
+ method public float getPivotX();
+ method public float getPivotY();
+ method public float getRotation();
+ method public float getScaleX();
+ method public float getScaleY();
+ method public int getSize();
+ method public float getTranslationX();
+ method public float getTranslationY();
+ method public void insertAt(int index, androidx.ui.graphics.vectorgraphics.VNode instance);
+ method public void move(int from, int to, int count);
+ method public void remove(int index, int count);
+ method public void setClipPathNodes(androidx.ui.graphics.vectorgraphics.PathNode![] value);
+ method public void setPivotX(float value);
+ method public void setPivotY(float value);
+ method public void setRotation(float value);
+ method public void setScaleX(float value);
+ method public void setScaleY(float value);
+ method public void setTranslationX(float value);
+ method public void setTranslationY(float value);
+ property public final androidx.ui.graphics.vectorgraphics.PathNode![] clipPathNodes;
+ property public final float pivotX;
+ property public final float pivotY;
+ property public final float rotation;
+ property public final float scaleX;
+ property public final float scaleY;
+ property public final int size;
+ property public final float translationX;
+ property public final float translationY;
+ }
+
+ public final class LinearGradient implements androidx.ui.graphics.vectorgraphics.Brush {
+ method public void applyBrush(androidx.ui.painting.Paint p);
+ method public java.util.List<androidx.ui.graphics.Color> component1();
+ method public java.util.List<java.lang.Float>? component2();
+ method public androidx.ui.core.Px component3();
+ method public androidx.ui.core.Px component4();
+ method public androidx.ui.core.Px component5();
+ method public androidx.ui.core.Px component6();
+ method public androidx.ui.painting.TileMode component7();
+ method public androidx.ui.graphics.vectorgraphics.LinearGradient copy(java.util.List<androidx.ui.graphics.Color> colors, java.util.List<java.lang.Float>? stops, androidx.ui.core.Px startX, androidx.ui.core.Px startY, androidx.ui.core.Px endX, androidx.ui.core.Px endY, androidx.ui.painting.TileMode tileMode);
+ method public java.util.List<androidx.ui.graphics.Color> getColors();
+ method public androidx.ui.core.Px getEndX();
+ method public androidx.ui.core.Px getEndY();
+ method public androidx.ui.core.Px getStartX();
+ method public androidx.ui.core.Px getStartY();
+ method public java.util.List<java.lang.Float>? getStops();
+ method public androidx.ui.painting.TileMode getTileMode();
+ }
+
+ public final class PathBuilder {
+ ctor public PathBuilder();
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder arcTo(float horizontalEllipseRadius, float verticalEllipseRadius, float theta, float largeArcFlag, float sweepFlag, float x1, float y1);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder arcToRelative(float a, float b, float theta, float largeArcFlag, float sweepFlag, float x1, float y1);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder close();
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder curveTo(float x1, float y1, float x2, float y2, float x3, float y3);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder curveToRelative(float dx1, float dy1, float dx2, float dy2, float dx3, float dy3);
+ method public androidx.ui.graphics.vectorgraphics.PathNode![] getNodes();
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder horizontalLineTo(float x);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder horizontalLineToRelative(float x);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder lineTo(float x, float y);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder lineToRelative(float x, float y);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder moveTo(float x, float y);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder moveToRelative(float x, float y);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder quadTo(float x1, float y1, float x2, float y2);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder quadToRelative(float x1, float y1, float x2, float y2);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder reflectiveCurveTo(float x1, float y1, float x2, float y2);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder reflectiveCurveToRelative(float x1, float y1, float x2, float y2);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder reflectiveQuadTo(float x1, float y1);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder reflectiveQuadToRelative(float x1, float y1);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder verticalLineTo(float y);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder verticalLineToRelative(float y);
+ }
+
+ public enum PathCommand {
+ method public final char toKey();
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand ArcTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand Close;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand CurveTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand HorizontalLineTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand LineTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand MoveTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand QuadTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand ReflectiveCurveTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand ReflectiveQuadTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand RelativeArcTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand RelativeClose;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand RelativeCurveTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand RelativeHorizontalTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand RelativeLineTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand RelativeMoveTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand RelativeQuadTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand RelativeReflectiveCurveTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand RelativeReflectiveQuadTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand RelativeVerticalTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand VerticalLineTo;
+ }
+
+ public final class PathCommandKt {
+ ctor public PathCommandKt();
+ method public static androidx.ui.graphics.vectorgraphics.PathCommand toPathCommand(char);
+ }
+
+ public final class PathComponent extends androidx.ui.graphics.vectorgraphics.VNode {
+ ctor public PathComponent(String name);
+ method public void draw(androidx.ui.painting.Canvas canvas);
+ method public androidx.ui.graphics.vectorgraphics.Brush getFill();
+ method public float getFillAlpha();
+ method public String getName();
+ method public androidx.ui.graphics.vectorgraphics.PathNode![] getPathNodes();
+ method public androidx.ui.graphics.vectorgraphics.Brush getStroke();
+ method public float getStrokeAlpha();
+ method public androidx.ui.painting.StrokeCap getStrokeLineCap();
+ method public androidx.ui.painting.StrokeJoin getStrokeLineJoin();
+ method public float getStrokeLineMiter();
+ method public float getStrokeLineWidth();
+ method public void setFill(androidx.ui.graphics.vectorgraphics.Brush value);
+ method public void setFillAlpha(float value);
+ method public void setPathNodes(androidx.ui.graphics.vectorgraphics.PathNode![] value);
+ method public void setStroke(androidx.ui.graphics.vectorgraphics.Brush value);
+ method public void setStrokeAlpha(float value);
+ method public void setStrokeLineCap(androidx.ui.painting.StrokeCap value);
+ method public void setStrokeLineJoin(androidx.ui.painting.StrokeJoin value);
+ method public void setStrokeLineMiter(float value);
+ method public void setStrokeLineWidth(float value);
+ property public final androidx.ui.graphics.vectorgraphics.Brush fill;
+ property public final float fillAlpha;
+ property public final androidx.ui.graphics.vectorgraphics.PathNode![] pathNodes;
+ property public final androidx.ui.graphics.vectorgraphics.Brush stroke;
+ property public final float strokeAlpha;
+ property public final androidx.ui.painting.StrokeCap strokeLineCap;
+ property public final androidx.ui.painting.StrokeJoin strokeLineJoin;
+ property public final float strokeLineMiter;
+ property public final float strokeLineWidth;
+ }
+
+ public final class PathDelegate {
+ ctor public PathDelegate(kotlin.jvm.functions.Function1<? super androidx.ui.graphics.vectorgraphics.PathBuilder,kotlin.Unit> delegate);
+ method public kotlin.jvm.functions.Function1<androidx.ui.graphics.vectorgraphics.PathBuilder,kotlin.Unit> getDelegate();
+ }
+
+ public final class PathNode {
+ ctor public PathNode(androidx.ui.graphics.vectorgraphics.PathCommand command, float[] args);
+ method public androidx.ui.graphics.vectorgraphics.PathCommand component1();
+ method public float[] component2();
+ method public androidx.ui.graphics.vectorgraphics.PathNode copy(androidx.ui.graphics.vectorgraphics.PathCommand command, float[] args);
+ method public float[] getArgs();
+ method public androidx.ui.graphics.vectorgraphics.PathCommand getCommand();
+ }
+
+ public final class PathNodeKt {
+ ctor public PathNodeKt();
+ method public static operator StringBuilder plus(StringBuilder, androidx.ui.graphics.vectorgraphics.PathNode node);
+ }
+
+ public final class PathParser {
+ ctor public PathParser();
+ method public androidx.ui.graphics.vectorgraphics.PathParser addPathNodes(androidx.ui.graphics.vectorgraphics.PathNode![] nodes);
+ method public void clear();
+ method public androidx.ui.graphics.vectorgraphics.PathParser parsePathString(String pathData);
+ method public androidx.ui.graphics.vectorgraphics.PathNode![] toNodes();
+ method public androidx.ui.painting.Path toPath(androidx.ui.painting.Path target = androidx.ui.painting.Path());
+ }
+
+ public final class PathParserKt {
+ ctor public PathParserKt();
+ }
+
+ public final class RadialGradient implements androidx.ui.graphics.vectorgraphics.Brush {
+ method public void applyBrush(androidx.ui.painting.Paint p);
+ method public androidx.ui.graphics.vectorgraphics.RadialGradient copy(java.util.List<androidx.ui.graphics.Color> colors, java.util.List<java.lang.Float>? stops, float centerX, float centerY, float radius, androidx.ui.painting.TileMode tileMode);
+ }
+
+ public final class SolidColor implements androidx.ui.graphics.vectorgraphics.Brush {
+ ctor public SolidColor(androidx.ui.graphics.Color value);
+ method public void applyBrush(androidx.ui.painting.Paint p);
+ method public androidx.ui.graphics.vectorgraphics.SolidColor copy(androidx.ui.graphics.Color value);
+ }
+
+ public abstract sealed class VNode {
+ method public abstract void draw(androidx.ui.painting.Canvas canvas);
+ }
+
+ public final class VectorComponent extends androidx.ui.graphics.vectorgraphics.VNode {
+ ctor public VectorComponent(String name, float viewportWidth, float viewportHeight, androidx.ui.core.Px defaultWidth, androidx.ui.core.Px defaultHeight);
+ method public void draw(androidx.ui.painting.Canvas canvas);
+ method public androidx.ui.core.Px getDefaultHeight();
+ method public androidx.ui.core.Px getDefaultWidth();
+ method public String getName();
+ method public androidx.ui.graphics.vectorgraphics.GroupComponent getRoot();
+ method public int getSize();
+ method public float getViewportHeight();
+ method public float getViewportWidth();
+ method public void setDefaultHeight(androidx.ui.core.Px p);
+ method public void setDefaultWidth(androidx.ui.core.Px p);
+ method public void setViewportHeight(float p);
+ method public void setViewportWidth(float p);
+ property public final androidx.ui.graphics.vectorgraphics.GroupComponent root;
+ property public final int size;
+ }
+
+ public final class VectorKt {
+ ctor public VectorKt();
+ method public static androidx.ui.graphics.vectorgraphics.PathNode![] addPathNodes(String? pathStr);
+ method public static androidx.ui.graphics.vectorgraphics.PathNode![] createPath(Object? pathData);
+ method public static androidx.ui.painting.StrokeCap getDefaultStrokeLineCap();
+ method public static androidx.ui.painting.StrokeJoin getDefaultStrokeLineJoin();
+ method public static androidx.ui.graphics.vectorgraphics.PathNode![] getEmptyPath();
+ field public static final float DefaultAlpha = 1.0f;
+ field public static final String DefaultGroupName = "";
+ field public static final String DefaultPathName = "";
+ field public static final float DefaultPivotX = 0.0f;
+ field public static final float DefaultPivotY = 0.0f;
+ field public static final float DefaultRotation = 0.0f;
+ field public static final float DefaultScaleX = 1.0f;
+ field public static final float DefaultScaleY = 1.0f;
+ field public static final float DefaultStrokeLineMiter = 4.0f;
+ field public static final float DefaultStrokeLineWidth = 0.0f;
+ field public static final float DefaultTranslationX = 0.0f;
+ field public static final float DefaultTranslationY = 0.0f;
+ }
+
+}
+
package androidx.ui.painting {
public final class AndroidCanvasKt {
@@ -1441,7 +1624,7 @@
public static final class Gradient.Companion {
method public androidx.ui.painting.Gradient linear(androidx.ui.engine.geometry.Offset from, androidx.ui.engine.geometry.Offset to, java.util.List<androidx.ui.graphics.Color> colors, java.util.List<java.lang.Float>? colorStops = null, androidx.ui.painting.TileMode tileMode = TileMode.clamp);
- method public androidx.ui.painting.Gradient radial(androidx.ui.engine.geometry.Offset center, float radius, java.util.List<androidx.ui.graphics.Color> color, java.util.List<java.lang.Float>? colorStops, androidx.ui.painting.TileMode tileMode = TileMode.clamp, androidx.ui.vectormath64.Matrix4 matrix4, androidx.ui.engine.geometry.Offset? focal, float focalRadius);
+ method public androidx.ui.painting.Gradient radial(androidx.ui.engine.geometry.Offset center, float radius, java.util.List<androidx.ui.graphics.Color> color, java.util.List<java.lang.Float>? colorStops, androidx.ui.painting.TileMode tileMode = TileMode.clamp, androidx.ui.vectormath64.Matrix4 matrix4);
method public androidx.ui.painting.Gradient sweep(androidx.ui.engine.geometry.Offset center, java.util.List<androidx.ui.graphics.Color> colors, java.util.List<java.lang.Float> colorStops, androidx.ui.painting.TileMode tileMode = TileMode.clamp, float startAngle, float endAngle = 6.2831855f, androidx.ui.vectormath64.Matrix4 matrix4);
}
@@ -1669,6 +1852,40 @@
}
+package androidx.ui.semantics {
+
+ public final class AccessibilityAction<T extends kotlin.Function<? extends kotlin.Unit>> {
+ ctor public AccessibilityAction(String? label, T action);
+ method public String? component1();
+ method public T component2();
+ method public androidx.ui.semantics.AccessibilityAction<T> copy(String? label, T action);
+ method public T getAction();
+ method public String? getLabel();
+ }
+
+ public class SemanticsPropertyKey<T> implements kotlin.properties.ReadWriteProperty<androidx.ui.semantics.SemanticsPropertyReceiver,T> {
+ ctor public SemanticsPropertyKey(String name);
+ method public final String getName();
+ method public final T! getValue(androidx.ui.semantics.SemanticsPropertyReceiver thisRef, kotlin.reflect.KProperty<?> property);
+ method public T! merge(T? existingValue, T? newValue);
+ method public final void setValue(androidx.ui.semantics.SemanticsPropertyReceiver thisRef, kotlin.reflect.KProperty<?> property, T? value);
+ }
+
+ public interface SemanticsPropertyReceiver {
+ method public operator <T> void set(androidx.ui.semantics.SemanticsPropertyKey<T> key, T? value);
+ }
+
+}
+
+package androidx.ui.temputils {
+
+ public final class CoroutineUtilsKt {
+ ctor public CoroutineUtilsKt();
+ method public static kotlinx.coroutines.Job delay(androidx.ui.core.Duration duration, kotlin.coroutines.CoroutineContext context, kotlin.jvm.functions.Function0<kotlin.Unit> block);
+ }
+
+}
+
package androidx.ui.testutils {
public final class PointerInputTestUtilKt {
@@ -1676,6 +1893,7 @@
method public static androidx.ui.core.PointerInputChange consume(androidx.ui.core.PointerInputChange, float dx = 0f, float dy = 0f, boolean downChange = false);
method public static androidx.ui.core.PointerInputChange down(int id = 0, androidx.ui.core.Timestamp timestamp = 0.millisecondsToTimestamp(), float x = 0f, float y = 0f);
method public static java.util.List<androidx.ui.core.PointerInputChange> invokeOverAllPasses(kotlin.jvm.functions.Function2<? super java.util.List<androidx.ui.core.PointerInputChange>,? super androidx.ui.core.PointerEventPass,? extends java.util.List<androidx.ui.core.PointerInputChange>>, java.util.List<androidx.ui.core.PointerInputChange> pointerInputChanges);
+ method public static java.util.List<androidx.ui.core.PointerInputChange> invokeOverAllPasses(kotlin.jvm.functions.Function2<? super java.util.List<androidx.ui.core.PointerInputChange>,? super androidx.ui.core.PointerEventPass,? extends java.util.List<androidx.ui.core.PointerInputChange>>, androidx.ui.core.PointerInputChange... pointerInputChanges);
method public static java.util.List<androidx.ui.core.PointerInputChange> invokeOverPasses(kotlin.jvm.functions.Function2<? super java.util.List<androidx.ui.core.PointerInputChange>,? super androidx.ui.core.PointerEventPass,? extends java.util.List<androidx.ui.core.PointerInputChange>>, java.util.List<androidx.ui.core.PointerInputChange> pointerInputChanges, androidx.ui.core.PointerEventPass... pointerEventPasses);
method public static androidx.ui.core.PointerInputChange moveBy(androidx.ui.core.PointerInputChange, androidx.ui.core.Duration duration, float dx = 0f, float dy = 0f);
method public static androidx.ui.core.PointerInputChange moveTo(androidx.ui.core.PointerInputChange, androidx.ui.core.Timestamp timestamp, float x = 0f, float y = 0f);
diff --git a/ui/ui-core/api/restricted_current.txt b/ui/ui-core/api/restricted_current.txt
index 88194bc..1648b3a 100644
--- a/ui/ui-core/api/restricted_current.txt
+++ b/ui/ui-core/api/restricted_current.txt
@@ -479,7 +479,9 @@
public static final class Px.Companion {
method public androidx.ui.core.Px getInfinity();
+ method public androidx.ui.core.Px getZero();
property public final androidx.ui.core.Px Infinity;
+ property public final androidx.ui.core.Px Zero;
}
public final class PxBounds {
@@ -708,74 +710,6 @@
}
-package androidx.ui.core.semantics {
-
- public final class SemanticsAction<T> {
- ctor public SemanticsAction(androidx.ui.core.semantics.SemanticsActionType<T> type, T handler);
- method public androidx.ui.core.semantics.SemanticsActionType<T> component1();
- method public T component2();
- method public androidx.ui.core.semantics.SemanticsAction<T> copy(androidx.ui.core.semantics.SemanticsActionType<T> type, T handler);
- method public T getHandler();
- method public androidx.ui.core.semantics.SemanticsActionType<T> getType();
- method public void invokeHandler(Object? args);
- }
-
- public final class SemanticsActionType<T> {
- method public int getBitmask();
- field public static final androidx.ui.core.semantics.SemanticsActionType.Companion! Companion;
- }
-
- public static final class SemanticsActionType.Companion {
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getCopy();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getCustomAction();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getCut();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getDecrease();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getDidGainAccessibilityFocus();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getDidLoseAccessibilityFocus();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getDismiss();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getIncrease();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getLongPress();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function1<java.lang.Boolean,kotlin.Unit>> getMoveCursorBackwardByCharacter();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function1<java.lang.Boolean,kotlin.Unit>> getMoveCursorBackwardByWord();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function1<java.lang.Boolean,kotlin.Unit>> getMoveCursorForwardByCharacter();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function1<java.lang.Boolean,kotlin.Unit>> getMoveCursorForwardByWord();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getPaste();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getScrollDown();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getScrollLeft();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getScrollRight();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getScrollUp();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function1<androidx.ui.text.TextSelection,kotlin.Unit>> getSetSelection();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getShowOnScreen();
- method public androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> getTap();
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> Copy;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> CustomAction;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> Cut;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> Decrease;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> DidGainAccessibilityFocus;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> DidLoseAccessibilityFocus;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> Dismiss;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> Increase;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> LongPress;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function1<java.lang.Boolean,kotlin.Unit>> MoveCursorBackwardByCharacter;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function1<java.lang.Boolean,kotlin.Unit>> MoveCursorBackwardByWord;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function1<java.lang.Boolean,kotlin.Unit>> MoveCursorForwardByCharacter;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function1<java.lang.Boolean,kotlin.Unit>> MoveCursorForwardByWord;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> Paste;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> ScrollDown;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> ScrollLeft;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> ScrollRight;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> ScrollUp;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function1<androidx.ui.text.TextSelection,kotlin.Unit>> SetSelection;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> ShowOnScreen;
- property public final androidx.ui.core.semantics.SemanticsActionType<kotlin.jvm.functions.Function0<kotlin.Unit>> Tap;
- }
-
- public final class SemanticsActionsKt {
- ctor public SemanticsActionsKt();
- }
-
-}
-
package androidx.ui.engine.geometry {
public final class Offset implements androidx.ui.engine.geometry.OffsetBase {
@@ -1320,6 +1254,255 @@
}
+package androidx.ui.graphics.vectorgraphics {
+
+ public interface Brush {
+ method public void applyBrush(androidx.ui.painting.Paint p);
+ }
+
+ public final class BrushKt {
+ ctor public BrushKt();
+ method public static androidx.ui.graphics.vectorgraphics.LinearGradient HorizontalGradient(androidx.ui.graphics.Color![] colors, androidx.ui.core.Px startX, androidx.ui.core.Px endX, androidx.ui.painting.TileMode tileMode = TileMode.clamp);
+ method public static androidx.ui.graphics.vectorgraphics.Brush HorizontalGradient(kotlin.Pair<java.lang.Float,androidx.ui.graphics.Color>![] colorStops, androidx.ui.core.Px startX, androidx.ui.core.Px endX, androidx.ui.painting.TileMode tileMode = TileMode.clamp);
+ method public static androidx.ui.graphics.vectorgraphics.LinearGradient LinearGradient(androidx.ui.graphics.Color![] colors, androidx.ui.core.Px startX, androidx.ui.core.Px startY, androidx.ui.core.Px endX, androidx.ui.core.Px endY, androidx.ui.painting.TileMode tileMode = TileMode.clamp);
+ method public static androidx.ui.graphics.vectorgraphics.LinearGradient LinearGradient(kotlin.Pair<java.lang.Float,androidx.ui.graphics.Color>![] colorStops, androidx.ui.core.Px startX, androidx.ui.core.Px startY, androidx.ui.core.Px endX, androidx.ui.core.Px endY, androidx.ui.painting.TileMode tileMode = TileMode.clamp);
+ method public static androidx.ui.graphics.vectorgraphics.RadialGradient RadialGradient(kotlin.Pair<java.lang.Float,androidx.ui.graphics.Color>![] colorStops, float centerX, float centerY, float radius, androidx.ui.painting.TileMode tileMode = TileMode.clamp);
+ method public static androidx.ui.graphics.vectorgraphics.RadialGradient RadialGradient(androidx.ui.graphics.Color![] colors, float centerX, float centerY, float radius, androidx.ui.painting.TileMode tileMode = TileMode.clamp);
+ method public static androidx.ui.graphics.vectorgraphics.LinearGradient VerticalGradient(androidx.ui.graphics.Color![] colors, androidx.ui.core.Px startY, androidx.ui.core.Px endY, androidx.ui.painting.TileMode tileMode = TileMode.clamp);
+ method public static androidx.ui.graphics.vectorgraphics.LinearGradient VerticalGradient(kotlin.Pair<java.lang.Float,androidx.ui.graphics.Color>![] colorStops, androidx.ui.core.Px startY, androidx.ui.core.Px endY, androidx.ui.painting.TileMode tileMode = TileMode.clamp);
+ method public static androidx.ui.graphics.vectorgraphics.Brush getEmptyBrush();
+ method public static androidx.ui.graphics.vectorgraphics.Brush obtainBrush(Object? brush);
+ }
+
+ public final class GroupComponent extends androidx.ui.graphics.vectorgraphics.VNode {
+ ctor public GroupComponent(String name);
+ ctor public GroupComponent();
+ method public void draw(androidx.ui.painting.Canvas canvas);
+ method public androidx.ui.graphics.vectorgraphics.PathNode![] getClipPathNodes();
+ method public String getName();
+ method public float getPivotX();
+ method public float getPivotY();
+ method public float getRotation();
+ method public float getScaleX();
+ method public float getScaleY();
+ method public int getSize();
+ method public float getTranslationX();
+ method public float getTranslationY();
+ method public void insertAt(int index, androidx.ui.graphics.vectorgraphics.VNode instance);
+ method public void move(int from, int to, int count);
+ method public void remove(int index, int count);
+ method public void setClipPathNodes(androidx.ui.graphics.vectorgraphics.PathNode![] value);
+ method public void setPivotX(float value);
+ method public void setPivotY(float value);
+ method public void setRotation(float value);
+ method public void setScaleX(float value);
+ method public void setScaleY(float value);
+ method public void setTranslationX(float value);
+ method public void setTranslationY(float value);
+ property public final androidx.ui.graphics.vectorgraphics.PathNode![] clipPathNodes;
+ property public final float pivotX;
+ property public final float pivotY;
+ property public final float rotation;
+ property public final float scaleX;
+ property public final float scaleY;
+ property public final int size;
+ property public final float translationX;
+ property public final float translationY;
+ }
+
+ public final class LinearGradient implements androidx.ui.graphics.vectorgraphics.Brush {
+ method public void applyBrush(androidx.ui.painting.Paint p);
+ method public java.util.List<androidx.ui.graphics.Color> component1();
+ method public java.util.List<java.lang.Float>? component2();
+ method public androidx.ui.core.Px component3();
+ method public androidx.ui.core.Px component4();
+ method public androidx.ui.core.Px component5();
+ method public androidx.ui.core.Px component6();
+ method public androidx.ui.painting.TileMode component7();
+ method public androidx.ui.graphics.vectorgraphics.LinearGradient copy(java.util.List<androidx.ui.graphics.Color> colors, java.util.List<java.lang.Float>? stops, androidx.ui.core.Px startX, androidx.ui.core.Px startY, androidx.ui.core.Px endX, androidx.ui.core.Px endY, androidx.ui.painting.TileMode tileMode);
+ method public java.util.List<androidx.ui.graphics.Color> getColors();
+ method public androidx.ui.core.Px getEndX();
+ method public androidx.ui.core.Px getEndY();
+ method public androidx.ui.core.Px getStartX();
+ method public androidx.ui.core.Px getStartY();
+ method public java.util.List<java.lang.Float>? getStops();
+ method public androidx.ui.painting.TileMode getTileMode();
+ }
+
+ public final class PathBuilder {
+ ctor public PathBuilder();
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder arcTo(float horizontalEllipseRadius, float verticalEllipseRadius, float theta, float largeArcFlag, float sweepFlag, float x1, float y1);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder arcToRelative(float a, float b, float theta, float largeArcFlag, float sweepFlag, float x1, float y1);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder close();
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder curveTo(float x1, float y1, float x2, float y2, float x3, float y3);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder curveToRelative(float dx1, float dy1, float dx2, float dy2, float dx3, float dy3);
+ method public androidx.ui.graphics.vectorgraphics.PathNode![] getNodes();
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder horizontalLineTo(float x);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder horizontalLineToRelative(float x);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder lineTo(float x, float y);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder lineToRelative(float x, float y);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder moveTo(float x, float y);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder moveToRelative(float x, float y);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder quadTo(float x1, float y1, float x2, float y2);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder quadToRelative(float x1, float y1, float x2, float y2);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder reflectiveCurveTo(float x1, float y1, float x2, float y2);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder reflectiveCurveToRelative(float x1, float y1, float x2, float y2);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder reflectiveQuadTo(float x1, float y1);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder reflectiveQuadToRelative(float x1, float y1);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder verticalLineTo(float y);
+ method public androidx.ui.graphics.vectorgraphics.PathBuilder verticalLineToRelative(float y);
+ }
+
+ public enum PathCommand {
+ method public final char toKey();
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand ArcTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand Close;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand CurveTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand HorizontalLineTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand LineTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand MoveTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand QuadTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand ReflectiveCurveTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand ReflectiveQuadTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand RelativeArcTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand RelativeClose;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand RelativeCurveTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand RelativeHorizontalTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand RelativeLineTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand RelativeMoveTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand RelativeQuadTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand RelativeReflectiveCurveTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand RelativeReflectiveQuadTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand RelativeVerticalTo;
+ enum_constant public static final androidx.ui.graphics.vectorgraphics.PathCommand VerticalLineTo;
+ }
+
+ public final class PathCommandKt {
+ ctor public PathCommandKt();
+ method public static androidx.ui.graphics.vectorgraphics.PathCommand toPathCommand(char);
+ }
+
+ public final class PathComponent extends androidx.ui.graphics.vectorgraphics.VNode {
+ ctor public PathComponent(String name);
+ method public void draw(androidx.ui.painting.Canvas canvas);
+ method public androidx.ui.graphics.vectorgraphics.Brush getFill();
+ method public float getFillAlpha();
+ method public String getName();
+ method public androidx.ui.graphics.vectorgraphics.PathNode![] getPathNodes();
+ method public androidx.ui.graphics.vectorgraphics.Brush getStroke();
+ method public float getStrokeAlpha();
+ method public androidx.ui.painting.StrokeCap getStrokeLineCap();
+ method public androidx.ui.painting.StrokeJoin getStrokeLineJoin();
+ method public float getStrokeLineMiter();
+ method public float getStrokeLineWidth();
+ method public void setFill(androidx.ui.graphics.vectorgraphics.Brush value);
+ method public void setFillAlpha(float value);
+ method public void setPathNodes(androidx.ui.graphics.vectorgraphics.PathNode![] value);
+ method public void setStroke(androidx.ui.graphics.vectorgraphics.Brush value);
+ method public void setStrokeAlpha(float value);
+ method public void setStrokeLineCap(androidx.ui.painting.StrokeCap value);
+ method public void setStrokeLineJoin(androidx.ui.painting.StrokeJoin value);
+ method public void setStrokeLineMiter(float value);
+ method public void setStrokeLineWidth(float value);
+ property public final androidx.ui.graphics.vectorgraphics.Brush fill;
+ property public final float fillAlpha;
+ property public final androidx.ui.graphics.vectorgraphics.PathNode![] pathNodes;
+ property public final androidx.ui.graphics.vectorgraphics.Brush stroke;
+ property public final float strokeAlpha;
+ property public final androidx.ui.painting.StrokeCap strokeLineCap;
+ property public final androidx.ui.painting.StrokeJoin strokeLineJoin;
+ property public final float strokeLineMiter;
+ property public final float strokeLineWidth;
+ }
+
+ public final class PathDelegate {
+ ctor public PathDelegate(kotlin.jvm.functions.Function1<? super androidx.ui.graphics.vectorgraphics.PathBuilder,kotlin.Unit> delegate);
+ method public kotlin.jvm.functions.Function1<androidx.ui.graphics.vectorgraphics.PathBuilder,kotlin.Unit> getDelegate();
+ }
+
+ public final class PathNode {
+ ctor public PathNode(androidx.ui.graphics.vectorgraphics.PathCommand command, float[] args);
+ method public androidx.ui.graphics.vectorgraphics.PathCommand component1();
+ method public float[] component2();
+ method public androidx.ui.graphics.vectorgraphics.PathNode copy(androidx.ui.graphics.vectorgraphics.PathCommand command, float[] args);
+ method public float[] getArgs();
+ method public androidx.ui.graphics.vectorgraphics.PathCommand getCommand();
+ }
+
+ public final class PathNodeKt {
+ ctor public PathNodeKt();
+ method public static operator StringBuilder plus(StringBuilder, androidx.ui.graphics.vectorgraphics.PathNode node);
+ }
+
+ public final class PathParser {
+ ctor public PathParser();
+ method public androidx.ui.graphics.vectorgraphics.PathParser addPathNodes(androidx.ui.graphics.vectorgraphics.PathNode![] nodes);
+ method public void clear();
+ method public androidx.ui.graphics.vectorgraphics.PathParser parsePathString(String pathData);
+ method public androidx.ui.graphics.vectorgraphics.PathNode![] toNodes();
+ method public androidx.ui.painting.Path toPath(androidx.ui.painting.Path target = androidx.ui.painting.Path());
+ }
+
+ public final class PathParserKt {
+ ctor public PathParserKt();
+ }
+
+ public final class RadialGradient implements androidx.ui.graphics.vectorgraphics.Brush {
+ method public void applyBrush(androidx.ui.painting.Paint p);
+ method public androidx.ui.graphics.vectorgraphics.RadialGradient copy(java.util.List<androidx.ui.graphics.Color> colors, java.util.List<java.lang.Float>? stops, float centerX, float centerY, float radius, androidx.ui.painting.TileMode tileMode);
+ }
+
+ public final class SolidColor implements androidx.ui.graphics.vectorgraphics.Brush {
+ ctor public SolidColor(androidx.ui.graphics.Color value);
+ method public void applyBrush(androidx.ui.painting.Paint p);
+ method public androidx.ui.graphics.vectorgraphics.SolidColor copy(androidx.ui.graphics.Color value);
+ }
+
+ public abstract sealed class VNode {
+ method public abstract void draw(androidx.ui.painting.Canvas canvas);
+ }
+
+ public final class VectorComponent extends androidx.ui.graphics.vectorgraphics.VNode {
+ ctor public VectorComponent(String name, float viewportWidth, float viewportHeight, androidx.ui.core.Px defaultWidth, androidx.ui.core.Px defaultHeight);
+ method public void draw(androidx.ui.painting.Canvas canvas);
+ method public androidx.ui.core.Px getDefaultHeight();
+ method public androidx.ui.core.Px getDefaultWidth();
+ method public String getName();
+ method public androidx.ui.graphics.vectorgraphics.GroupComponent getRoot();
+ method public int getSize();
+ method public float getViewportHeight();
+ method public float getViewportWidth();
+ method public void setDefaultHeight(androidx.ui.core.Px p);
+ method public void setDefaultWidth(androidx.ui.core.Px p);
+ method public void setViewportHeight(float p);
+ method public void setViewportWidth(float p);
+ property public final androidx.ui.graphics.vectorgraphics.GroupComponent root;
+ property public final int size;
+ }
+
+ public final class VectorKt {
+ ctor public VectorKt();
+ method public static androidx.ui.graphics.vectorgraphics.PathNode![] addPathNodes(String? pathStr);
+ method public static androidx.ui.graphics.vectorgraphics.PathNode![] createPath(Object? pathData);
+ method public static androidx.ui.painting.StrokeCap getDefaultStrokeLineCap();
+ method public static androidx.ui.painting.StrokeJoin getDefaultStrokeLineJoin();
+ method public static androidx.ui.graphics.vectorgraphics.PathNode![] getEmptyPath();
+ field public static final float DefaultAlpha = 1.0f;
+ field public static final String DefaultGroupName = "";
+ field public static final String DefaultPathName = "";
+ field public static final float DefaultPivotX = 0.0f;
+ field public static final float DefaultPivotY = 0.0f;
+ field public static final float DefaultRotation = 0.0f;
+ field public static final float DefaultScaleX = 1.0f;
+ field public static final float DefaultScaleY = 1.0f;
+ field public static final float DefaultStrokeLineMiter = 4.0f;
+ field public static final float DefaultStrokeLineWidth = 0.0f;
+ field public static final float DefaultTranslationX = 0.0f;
+ field public static final float DefaultTranslationY = 0.0f;
+ }
+
+}
+
package androidx.ui.painting {
public final class AndroidCanvasKt {
@@ -1441,7 +1624,7 @@
public static final class Gradient.Companion {
method public androidx.ui.painting.Gradient linear(androidx.ui.engine.geometry.Offset from, androidx.ui.engine.geometry.Offset to, java.util.List<androidx.ui.graphics.Color> colors, java.util.List<java.lang.Float>? colorStops = null, androidx.ui.painting.TileMode tileMode = TileMode.clamp);
- method public androidx.ui.painting.Gradient radial(androidx.ui.engine.geometry.Offset center, float radius, java.util.List<androidx.ui.graphics.Color> color, java.util.List<java.lang.Float>? colorStops, androidx.ui.painting.TileMode tileMode = TileMode.clamp, androidx.ui.vectormath64.Matrix4 matrix4, androidx.ui.engine.geometry.Offset? focal, float focalRadius);
+ method public androidx.ui.painting.Gradient radial(androidx.ui.engine.geometry.Offset center, float radius, java.util.List<androidx.ui.graphics.Color> color, java.util.List<java.lang.Float>? colorStops, androidx.ui.painting.TileMode tileMode = TileMode.clamp, androidx.ui.vectormath64.Matrix4 matrix4);
method public androidx.ui.painting.Gradient sweep(androidx.ui.engine.geometry.Offset center, java.util.List<androidx.ui.graphics.Color> colors, java.util.List<java.lang.Float> colorStops, androidx.ui.painting.TileMode tileMode = TileMode.clamp, float startAngle, float endAngle = 6.2831855f, androidx.ui.vectormath64.Matrix4 matrix4);
}
@@ -1669,6 +1852,40 @@
}
+package androidx.ui.semantics {
+
+ public final class AccessibilityAction<T extends kotlin.Function<? extends kotlin.Unit>> {
+ ctor public AccessibilityAction(String? label, T action);
+ method public String? component1();
+ method public T component2();
+ method public androidx.ui.semantics.AccessibilityAction<T> copy(String? label, T action);
+ method public T getAction();
+ method public String? getLabel();
+ }
+
+ public class SemanticsPropertyKey<T> implements kotlin.properties.ReadWriteProperty<androidx.ui.semantics.SemanticsPropertyReceiver,T> {
+ ctor public SemanticsPropertyKey(String name);
+ method public final String getName();
+ method public final T! getValue(androidx.ui.semantics.SemanticsPropertyReceiver thisRef, kotlin.reflect.KProperty<?> property);
+ method public T! merge(T? existingValue, T? newValue);
+ method public final void setValue(androidx.ui.semantics.SemanticsPropertyReceiver thisRef, kotlin.reflect.KProperty<?> property, T? value);
+ }
+
+ public interface SemanticsPropertyReceiver {
+ method public operator <T> void set(androidx.ui.semantics.SemanticsPropertyKey<T> key, T? value);
+ }
+
+}
+
+package androidx.ui.temputils {
+
+ public final class CoroutineUtilsKt {
+ ctor public CoroutineUtilsKt();
+ method public static kotlinx.coroutines.Job delay(androidx.ui.core.Duration duration, kotlin.coroutines.CoroutineContext context, kotlin.jvm.functions.Function0<kotlin.Unit> block);
+ }
+
+}
+
package androidx.ui.testutils {
public final class PointerInputTestUtilKt {
@@ -1676,6 +1893,7 @@
method public static androidx.ui.core.PointerInputChange consume(androidx.ui.core.PointerInputChange, float dx = 0f, float dy = 0f, boolean downChange = false);
method public static androidx.ui.core.PointerInputChange down(int id = 0, androidx.ui.core.Timestamp timestamp = 0.millisecondsToTimestamp(), float x = 0f, float y = 0f);
method public static java.util.List<androidx.ui.core.PointerInputChange> invokeOverAllPasses(kotlin.jvm.functions.Function2<? super java.util.List<androidx.ui.core.PointerInputChange>,? super androidx.ui.core.PointerEventPass,? extends java.util.List<androidx.ui.core.PointerInputChange>>, java.util.List<androidx.ui.core.PointerInputChange> pointerInputChanges);
+ method public static java.util.List<androidx.ui.core.PointerInputChange> invokeOverAllPasses(kotlin.jvm.functions.Function2<? super java.util.List<androidx.ui.core.PointerInputChange>,? super androidx.ui.core.PointerEventPass,? extends java.util.List<androidx.ui.core.PointerInputChange>>, androidx.ui.core.PointerInputChange... pointerInputChanges);
method public static java.util.List<androidx.ui.core.PointerInputChange> invokeOverPasses(kotlin.jvm.functions.Function2<? super java.util.List<androidx.ui.core.PointerInputChange>,? super androidx.ui.core.PointerEventPass,? extends java.util.List<androidx.ui.core.PointerInputChange>>, java.util.List<androidx.ui.core.PointerInputChange> pointerInputChanges, androidx.ui.core.PointerEventPass... pointerEventPasses);
method public static androidx.ui.core.PointerInputChange moveBy(androidx.ui.core.PointerInputChange, androidx.ui.core.Duration duration, float dx = 0f, float dy = 0f);
method public static androidx.ui.core.PointerInputChange moveTo(androidx.ui.core.PointerInputChange, androidx.ui.core.Timestamp timestamp, float x = 0f, float y = 0f);
diff --git a/ui/ui-core/build.gradle b/ui/ui-core/build.gradle
index 6817c80..bdef06e 100644
--- a/ui/ui-core/build.gradle
+++ b/ui/ui-core/build.gradle
@@ -58,6 +58,6 @@
tasks.withType(KotlinCompile).configureEach {
kotlinOptions {
- freeCompilerArgs += ["-Werror", "-Xuse-experimental=kotlin.Experimental"]
+ freeCompilerArgs += ["-Werror", "-Xuse-experimental=kotlin.Experimental", "-XXLanguage:+InlineClasses"]
}
-}
+}
\ No newline at end of file
diff --git a/ui/ui-core/src/main/java/androidx/ui/core/Dp.kt b/ui/ui-core/src/main/java/androidx/ui/core/Dp.kt
index 8168130..e7981fe 100644
--- a/ui/ui-core/src/main/java/androidx/ui/core/Dp.kt
+++ b/ui/ui-core/src/main/java/androidx/ui/core/Dp.kt
@@ -17,6 +17,7 @@
package androidx.ui.core
+import androidx.compose.Immutable
import androidx.ui.core.Dp.Companion.Hairline
import androidx.ui.lerp
import kotlin.math.max
@@ -37,6 +38,7 @@
* [toPx] is normally needed only for painting operations.
*/
@Suppress("EXPERIMENTAL_FEATURE_WARNING")
+@Immutable
data /*inline*/ class Dp(val value: Float) {
/**
* Add two [Dp]s together.
@@ -222,6 +224,7 @@
* val width = oldWidth * newTotalWidth / oldTotalWidth
*/
@Suppress("EXPERIMENTAL_FEATURE_WARNING")
+@Immutable
inline class DpSquared(val value: Float) {
/**
* Add two DimensionSquares together.
@@ -287,6 +290,7 @@
* val width = oldWidth * newTotalWidth / oldTotalWidth
*/
@Suppress("EXPERIMENTAL_FEATURE_WARNING")
+@Immutable
inline class DpCubed(val value: Float) {
/**
@@ -345,6 +349,7 @@
* val width = oldWidth * newTotalWidth / oldTotalWidth
*/
@Suppress("EXPERIMENTAL_FEATURE_WARNING")
+@Immutable
inline class DpInverse(val value: Float) {
/**
* Add two DpInverse together.
@@ -401,6 +406,7 @@
/**
* A two dimensional size using [Dp] for units
*/
+@Immutable
data class Size(val width: Dp, val height: Dp)
/**
@@ -414,6 +420,7 @@
/**
* A two-dimensional position using [Dp] for units
*/
+@Immutable
data class Position(val x: Dp, val y: Dp) {
/**
* Subtract a [Position] from another one.
@@ -452,6 +459,7 @@
/**
* A four dimensional bounds using [Dp] for units
*/
+@Immutable
data class Bounds(
val left: Dp,
val top: Dp,
diff --git a/ui/ui-core/src/main/java/androidx/ui/core/Px.kt b/ui/ui-core/src/main/java/androidx/ui/core/Px.kt
index 9985605..5295cf3 100644
--- a/ui/ui-core/src/main/java/androidx/ui/core/Px.kt
+++ b/ui/ui-core/src/main/java/androidx/ui/core/Px.kt
@@ -117,6 +117,11 @@
* Infinite px dimension.
*/
val Infinity = Px(value = Float.POSITIVE_INFINITY)
+
+ /**
+ * Zero px dimension
+ */
+ val Zero = Px(0.0f)
}
}
diff --git a/ui/ui-core/src/main/java/androidx/ui/graphics/Color.kt b/ui/ui-core/src/main/java/androidx/ui/graphics/Color.kt
index 73765cd..9644d21 100644
--- a/ui/ui-core/src/main/java/androidx/ui/graphics/Color.kt
+++ b/ui/ui-core/src/main/java/androidx/ui/graphics/Color.kt
@@ -20,6 +20,7 @@
import androidx.annotation.FloatRange
import androidx.annotation.IntRange
import androidx.annotation.Size
+import androidx.compose.Immutable
import androidx.ui.lerp
import androidx.ui.util.Float16
import kotlin.math.max
@@ -112,6 +113,7 @@
*/
@UseExperimental(kotlin.ExperimentalUnsignedTypes::class)
@AnyThread
+@Immutable
class Color constructor(private val value: ULong) {
/**
* Returns this color's color space.
diff --git a/ui/ui-core/src/main/java/androidx/ui/graphics/vectorgraphics/Brush.kt b/ui/ui-core/src/main/java/androidx/ui/graphics/vectorgraphics/Brush.kt
new file mode 100644
index 0000000..3243120
--- /dev/null
+++ b/ui/ui-core/src/main/java/androidx/ui/graphics/vectorgraphics/Brush.kt
@@ -0,0 +1,344 @@
+/*
+ * 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.ui.graphics.vectorgraphics
+
+import androidx.ui.core.Px
+import androidx.ui.engine.geometry.Offset
+import androidx.ui.graphics.Color
+import androidx.ui.painting.Gradient
+import androidx.ui.painting.Paint
+import androidx.ui.painting.TileMode
+import androidx.ui.vectormath64.Matrix4
+
+val EmptyBrush = object : Brush {
+ override fun applyBrush(p: Paint) {
+ // NO-OP
+ }
+}
+
+interface Brush {
+ fun applyBrush(p: Paint)
+}
+
+data class SolidColor(private val value: Color) : Brush {
+ override fun applyBrush(p: Paint) {
+ p.color = value
+ }
+}
+
+typealias ColorStop = Pair<Float, Color>
+
+/**
+ * Obtains actual Brush instance from Union type, throws an IllegalArgumentException
+ * if the type is other than Int, Color, Brush or null
+ */
+fun obtainBrush(brush: Any?): Brush {
+ return when (brush) {
+ is Int -> SolidColor(Color(brush))
+ is Color -> SolidColor(brush)
+ is Brush -> brush
+ null -> EmptyBrush
+ else -> throw IllegalArgumentException(brush.javaClass.simpleName +
+ "Brush must be either a Color long, LinearGradient or RadialGradient")
+ }
+}
+
+/**
+ * Creates a linear gradient with the provided colors along the given start and end coordinates.
+ * The colors are
+ *
+ * ```
+ * LinearGradient(
+ * 0.0f to Color.Aqua,
+ * 0.3f to Color.Lime,
+ * 1.0f to Color.Fuchsia,
+ * startX = Px.Zero,
+ * startY = Px(50.0f),
+ * endY = Px.Zero,
+ * endY = Px(100.0f)
+ * )
+ * ```
+ */
+fun LinearGradient(
+ vararg colors: Color,
+ startX: Px,
+ startY: Px,
+ endX: Px,
+ endY: Px,
+ tileMode: TileMode = TileMode.clamp
+): LinearGradient {
+ return LinearGradient(
+ colors.asList(),
+ null,
+ startX,
+ startY,
+ endX,
+ endY,
+ tileMode)
+}
+
+/**
+ * Creates a linear gradient with the provided colors along the given start and end coordinates.
+ * The colors are dispersed at the provided offset defined in the [ColorStop]
+ *
+ * ```
+ * LinearGradient(
+ * 0.0f to Color.Aqua,
+ * 0.3f to Color.Lime,
+ * 1.0f to Color.Fuchsia,
+ * startX = Px.Zero,
+ * startY = Px(50.0f),
+ * endY = Px.Zero,
+ * endY = Px(100.0f)
+ * )
+ * ```
+ */
+fun LinearGradient(
+ vararg colorStops: ColorStop,
+ startX: Px,
+ startY: Px,
+ endX: Px,
+ endY: Px,
+ tileMode: TileMode = TileMode.clamp
+): LinearGradient {
+ return LinearGradient(
+ List<Color>(colorStops.size) { i -> colorStops[i].second },
+ List<Float>(colorStops.size) { i -> colorStops[i].first },
+ startX,
+ startY,
+ endX,
+ endY,
+ tileMode)
+}
+
+/**
+ * Creates a radial gradient with the given colors at the provided offset defined in the [ColorStop]
+ * ```
+ * RadialGradient(
+ * 0.0f to Color.Navy,
+ * 0.3f to Color.Olive,
+ * 1.0f to Color.Teal,
+ * centerX = side1 / 2.0f,
+ * centerY = side2 / 2.0f,
+ * radius = side1 / 2.0f,
+ * tileMode = TileMode.repeated
+ * )
+ * ```
+ */
+fun RadialGradient(
+ vararg colorStops: ColorStop,
+ centerX: Float,
+ centerY: Float,
+ radius: Float,
+ tileMode: TileMode = TileMode.clamp
+): RadialGradient {
+ return RadialGradient(
+ List<Color>(colorStops.size) { i -> colorStops[i].second },
+ List<Float>(colorStops.size) { i -> colorStops[i].first },
+ centerX,
+ centerY,
+ radius,
+ tileMode
+ )
+}
+
+/**
+ * Creates a radial gradient with the given colors evenly dispersed within the gradient
+ * ```
+ * RadialGradient(
+ * Color.Navy,
+ * Color.Olive,
+ * Color.Teal,
+ * centerX = side1 / 2.0f,
+ * centerY = side2 / 2.0f,
+ * radius = side1 / 2.0f,
+ * tileMode = TileMode.repeated
+ * )
+ * ```
+ */
+fun RadialGradient(
+ vararg colors: Color,
+ centerX: Float,
+ centerY: Float,
+ radius: Float,
+ tileMode: TileMode = TileMode.clamp
+): RadialGradient {
+ return RadialGradient(colors.asList(), null, centerX, centerY, radius, tileMode)
+}
+
+/**
+ * Creates a vertical gradient with the given colors at the provided offset defined in the [ColorStop]
+ * Ex:
+ * ```
+ * VerticalGradient(
+ * Color.Aqua,
+ * Color.Lime,
+ * Color.Fuchsia,
+ * startY = Px.Zero,
+ * endY = Px(100.0f)
+ * )
+ *
+ * ```
+ */
+fun VerticalGradient(
+ vararg colors: Color,
+ startY: Px,
+ endY: Px,
+ tileMode: TileMode = TileMode.clamp
+): LinearGradient {
+ return LinearGradient(
+ colors.asList(),
+ null,
+ startX = Px.Zero,
+ startY = startY,
+ endX = Px.Zero,
+ endY = endY,
+ tileMode = tileMode)
+}
+
+/**
+ * Creates a vertical gradient with the given colors evenly dispersed within the gradient
+ * Ex:
+ * ```
+ * VerticalGradient(
+ * Color.Aqua,
+ * Color.Lime,
+ * Color.Fuchsia,
+ * startY = Px.Zero,
+ * endY = Px(100.0f)
+ * )
+ * ```
+ */
+fun VerticalGradient(
+ vararg colorStops: ColorStop,
+ startY: Px,
+ endY: Px,
+ tileMode: TileMode = TileMode.clamp
+): LinearGradient {
+ return LinearGradient(
+ List<Color>(colorStops.size) { i -> colorStops[i].second },
+ List<Float>(colorStops.size) { i -> colorStops[i].first },
+ startX = Px.Zero,
+ startY = startY,
+ endX = Px.Zero,
+ endY = endY,
+ tileMode = tileMode
+ )
+}
+
+/**
+ * Creates a horizontal gradient with the given colors evenly dispersed within the gradient
+ *
+ * Ex:
+ * ```
+ * HorizontalGradient(
+ * Color.Aqua,
+ * Color.Lime,
+ * Color.Fuchsia,
+ * startX = Px(10.0f),
+ * endX = Px(20.0f)
+ * )
+ * ```
+ */
+fun HorizontalGradient(
+ vararg colors: Color,
+ startX: Px,
+ endX: Px,
+ tileMode: TileMode = TileMode.clamp
+): LinearGradient {
+ return LinearGradient(
+ colors.asList(),
+ null,
+ startX = startX,
+ startY = Px.Zero,
+ endX = endX,
+ endY = Px.Zero,
+ tileMode = tileMode)
+}
+
+/**
+ * Creates a horizontal gradient with the given colors dispersed at the provided offset defined in the [ColorStop]
+ *
+ * Ex:
+ * ```
+ * HorizontalGradient(
+ * 0.0f to Color.Aqua,
+ * 0.3f to Color.Lime,
+ * 1.0f to Color.Fuchsia,
+ * startX = Px.Zero,
+ * endX = Px(100.0f)
+ * )
+ * ```
+ */
+fun HorizontalGradient(
+ vararg colorStops: ColorStop,
+ startX: Px,
+ endX: Px,
+ tileMode: TileMode = TileMode.clamp
+): Brush {
+ return LinearGradient(
+ List<Color>(colorStops.size) { i -> colorStops[i].second },
+ List<Float>(colorStops.size) { i -> colorStops[i].first },
+ startX = startX,
+ startY = Px.Zero,
+ endX = endX,
+ endY = Px.Zero,
+ tileMode = tileMode
+ )
+}
+
+/**
+ * Brush implementation used to apply a linear gradient on a given [Paint]
+ */
+data class LinearGradient internal constructor(
+ val colors: List<Color>,
+ val stops: List<Float>? = null,
+ val startX: Px,
+ val startY: Px,
+ val endX: Px,
+ val endY: Px,
+ val tileMode: TileMode = TileMode.clamp
+) : Brush {
+
+ override fun applyBrush(p: Paint) {
+ p.shader = Gradient.linear(
+ Offset(startX.value, startY.value),
+ Offset(endX.value, endY.value),
+ colors,
+ stops,
+ tileMode)
+ }
+}
+
+/**
+ * Brush implementation used to apply a radial gradient on a given [Paint]
+ */
+data class RadialGradient internal constructor(
+ private val colors: List<Color>,
+ private val stops: List<Float>? = null,
+ private val centerX: Float,
+ private val centerY: Float,
+ private val radius: Float,
+ private val tileMode: TileMode = TileMode.clamp
+) : Brush {
+
+ override fun applyBrush(p: Paint) {
+ p.shader = Gradient.radial(
+ Offset(centerX, centerY),
+ radius, colors, stops, tileMode, Matrix4())
+ }
+}
\ No newline at end of file
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/vectorgraphics/PathBuilder.kt b/ui/ui-core/src/main/java/androidx/ui/graphics/vectorgraphics/PathBuilder.kt
similarity index 95%
rename from ui/ui-framework/src/main/java/androidx/ui/core/vectorgraphics/PathBuilder.kt
rename to ui/ui-core/src/main/java/androidx/ui/graphics/vectorgraphics/PathBuilder.kt
index 52cb119..6c634eb 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/vectorgraphics/PathBuilder.kt
+++ b/ui/ui-core/src/main/java/androidx/ui/graphics/vectorgraphics/PathBuilder.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.ui.core.vectorgraphics
+package androidx.ui.graphics.vectorgraphics
class PathBuilder {
@@ -93,7 +93,8 @@
sweepFlag: Float,
x1: Float,
y1: Float
- ) = addNode(PathCommand.ArcTo, horizontalEllipseRadius, verticalEllipseRadius, theta,
+ ) = addNode(
+ PathCommand.ArcTo, horizontalEllipseRadius, verticalEllipseRadius, theta,
largeArcFlag, sweepFlag, x1, y1)
fun arcToRelative(
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/vectorgraphics/PathCommand.kt b/ui/ui-core/src/main/java/androidx/ui/graphics/vectorgraphics/PathCommand.kt
similarity index 96%
rename from ui/ui-framework/src/main/java/androidx/ui/core/vectorgraphics/PathCommand.kt
rename to ui/ui-core/src/main/java/androidx/ui/graphics/vectorgraphics/PathCommand.kt
index 29e3bde..b8e9f1d 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/vectorgraphics/PathCommand.kt
+++ b/ui/ui-core/src/main/java/androidx/ui/graphics/vectorgraphics/PathCommand.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.ui.core.vectorgraphics
+package androidx.ui.graphics.vectorgraphics
enum class PathCommand(private val mKey: Char) {
@@ -49,9 +49,7 @@
* Return the corresponding PathCommand for the given character key if it exists.
* If the key is unknown then IllegalArgumentException is thrown
* @return PathCommand that matches the key
- * @throws IllegalArgumentException if the key is invalid
*/
-@Throws(IllegalArgumentException::class)
fun Char.toPathCommand(): PathCommand = when (this) {
RelativeCloseKey -> PathCommand.RelativeClose
CloseKey -> PathCommand.Close
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/vectorgraphics/PathNode.kt b/ui/ui-core/src/main/java/androidx/ui/graphics/vectorgraphics/PathNode.kt
similarity index 97%
rename from ui/ui-framework/src/main/java/androidx/ui/core/vectorgraphics/PathNode.kt
rename to ui/ui-core/src/main/java/androidx/ui/graphics/vectorgraphics/PathNode.kt
index 4d0accc..57833ca 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/vectorgraphics/PathNode.kt
+++ b/ui/ui-core/src/main/java/androidx/ui/graphics/vectorgraphics/PathNode.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.ui.core.vectorgraphics
+package androidx.ui.graphics.vectorgraphics
import kotlin.text.StringBuilder
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/vectorgraphics/PathParser.kt b/ui/ui-core/src/main/java/androidx/ui/graphics/vectorgraphics/PathParser.kt
similarity index 97%
rename from ui/ui-framework/src/main/java/androidx/ui/core/vectorgraphics/PathParser.kt
rename to ui/ui-core/src/main/java/androidx/ui/graphics/vectorgraphics/PathParser.kt
index 7724ccf..553eb32 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/vectorgraphics/PathParser.kt
+++ b/ui/ui-core/src/main/java/androidx/ui/graphics/vectorgraphics/PathParser.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.ui.core.vectorgraphics
+package androidx.ui.graphics.vectorgraphics
import android.util.Log
import androidx.ui.painting.Path
@@ -51,7 +51,11 @@
private val segmentPoint = PathPoint()
private val reflectiveCtrlPoint = PathPoint()
- @Throws(java.lang.IllegalArgumentException::class, NumberFormatException::class)
+ /**
+ * Parses the path string to create a collection of PathNode instances with their corresponding
+ * arguments
+ * throws an IllegalArgumentException or NumberFormatException if the parameters are invalid
+ */
fun parsePathString(pathData: String): PathParser {
nodes.clear()
@@ -348,7 +352,9 @@
}
private fun reflectiveQuadTo(prevCmd: PathCommand, target: Path, args: FloatArray) {
- forEachPathArg(args, NUM_REFLECTIVE_QUAD_TO_ARGS) { index ->
+ forEachPathArg(args,
+ NUM_REFLECTIVE_QUAD_TO_ARGS
+ ) { index ->
val x1 = args[index]
val y1 = args[index + 1]
if (prevCmd.isQuad()) {
@@ -368,7 +374,9 @@
}
private fun relativeReflectiveQuadTo(prevCmd: PathCommand, target: Path, args: FloatArray) {
- forEachPathArg(args, NUM_REFLECTIVE_QUAD_TO_ARGS) { index ->
+ forEachPathArg(args,
+ NUM_REFLECTIVE_QUAD_TO_ARGS
+ ) { index ->
val x1 = args[index]
val y1 = args[index + 1]
if (prevCmd.isQuad()) {
@@ -448,7 +456,9 @@
}
private fun reflectiveCurveTo(prevCmd: PathCommand, target: Path, args: FloatArray) {
- forEachPathArg(args, NUM_REFLECTIVE_CURVE_TO_ARGS) { index ->
+ forEachPathArg(args,
+ NUM_REFLECTIVE_CURVE_TO_ARGS
+ ) { index ->
val x1 = args[index]
val y1 = args[index + 1]
val x2 = args[index + 2]
@@ -472,7 +482,9 @@
}
private fun relativeReflectiveCurveTo(prevCmd: PathCommand, target: Path, args: FloatArray) {
- forEachPathArg(args, NUM_REFLECTIVE_CURVE_TO_ARGS) { index ->
+ forEachPathArg(args,
+ NUM_REFLECTIVE_CURVE_TO_ARGS
+ ) { index ->
val x1 = args[index]
val y1 = args[index + 1]
val x2 = args[index + 2]
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/vectorgraphics/Vector.kt b/ui/ui-core/src/main/java/androidx/ui/graphics/vectorgraphics/Vector.kt
similarity index 67%
rename from ui/ui-framework/src/main/java/androidx/ui/core/vectorgraphics/Vector.kt
rename to ui/ui-core/src/main/java/androidx/ui/graphics/vectorgraphics/Vector.kt
index 9fd6bb1..920b59e 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/vectorgraphics/Vector.kt
+++ b/ui/ui-core/src/main/java/androidx/ui/graphics/vectorgraphics/Vector.kt
@@ -14,37 +14,33 @@
* limitations under the License.
*/
-package androidx.ui.core.vectorgraphics
+package androidx.ui.graphics.vectorgraphics
-import android.graphics.ColorFilter
import android.graphics.Matrix
-import android.graphics.PixelFormat
-import android.graphics.drawable.Drawable
-import android.view.View
-import android.view.ViewGroup
-import android.widget.ImageView
import androidx.ui.engine.geometry.Offset
import androidx.ui.painting.Canvas
import androidx.ui.painting.Image
import androidx.ui.painting.Paint
import androidx.ui.painting.PaintingStyle
+import androidx.ui.painting.Path
import androidx.ui.painting.StrokeCap
import androidx.ui.painting.StrokeJoin
import androidx.compose.Children
import androidx.compose.Composable
import androidx.compose.Emittable
import androidx.compose.composer
+import androidx.ui.core.Px
import androidx.ui.painting.withSave
-import androidx.ui.painting.Path as PaintingPath
+import kotlin.math.ceil
const val DefaultGroupName = ""
-const val DefaultRotate = 0.0f
+const val DefaultRotation = 0.0f
const val DefaultPivotX = 0.0f
const val DefaultPivotY = 0.0f
const val DefaultScaleX = 1.0f
const val DefaultScaleY = 1.0f
-const val DefaultTranslateX = 0.0f
-const val DefaultTranslateY = 0.0f
+const val DefaultTranslationX = 0.0f
+const val DefaultTranslationY = 0.0f
val EmptyPath = emptyArray<PathNode>()
@@ -79,96 +75,23 @@
PathParser().parsePathString(pathStr).toNodes()
}
-@Composable
-fun vector(
- name: String = "",
- viewportWidth: Float,
- viewportHeight: Float,
- defaultWidth: Float = viewportWidth,
- defaultHeight: Float = viewportHeight,
- @Children children: @Composable() () -> Unit
-) {
- <Vector name defaultWidth defaultHeight viewportWidth viewportHeight>
- children()
- </Vector>
-}
-
-@Composable
-fun group(
- name: String = DefaultGroupName,
- rotate: Float = DefaultRotate,
- pivotX: Float = DefaultPivotX,
- pivotY: Float = DefaultPivotY,
- scaleX: Float = DefaultScaleX,
- scaleY: Float = DefaultScaleY,
- translateX: Float = DefaultTranslateX,
- translateY: Float = DefaultTranslateY,
- clipPathData: PathData = EmptyPath,
- @Children childNodes: @Composable() () -> Unit
-) {
-
- val clipPathNodes = createPath(clipPathData)
- <Group
- name
- rotate
- pivotX
- pivotY
- scaleX
- scaleY
- translateX
- translateY
- clipPathNodes>
- childNodes()
- </Group>
-}
-
-@Composable
-fun path(
- pathData: PathData,
- name: String = DefaultPathName,
- fill: BrushType = EmptyBrush,
- fillAlpha: Float = DefaultAlpha,
- stroke: BrushType = EmptyBrush,
- strokeAlpha: Float = DefaultAlpha,
- strokeLineWidth: Float = DefaultStrokeLineWidth,
- strokeLineCap: StrokeCap = DefaultStrokeLineCap,
- strokeLineJoin: StrokeJoin = DefaultStrokeLineJoin,
- strokeLineMiter: Float = DefaultStrokeLineMiter
-) {
- val pathNodes = createPath(pathData)
- val fillBrush: Brush = obtainBrush(fill)
- val strokeBrush: Brush = obtainBrush(stroke)
-
- <Path
- name
- pathNodes
- fill=fillBrush
- fillAlpha
- stroke=strokeBrush
- strokeAlpha
- strokeLineWidth
- strokeLineCap
- strokeLineJoin
- strokeLineMiter />
-}
-
-private sealed class VNode {
+sealed class VNode {
abstract fun draw(canvas: Canvas)
}
-private class Vector(
+class VectorComponent(
val name: String = "",
- val viewportWidth: Float,
- val viewportHeight: Float,
- val defaultWidth: Float = viewportWidth,
- val defaultHeight: Float = viewportHeight
-) : VNode(), Emittable {
+ var viewportWidth: Float,
+ var viewportHeight: Float,
+ var defaultWidth: Px,
+ var defaultHeight: Px
+) : VNode() {
- private val root = Group(this@Vector.name).apply {
+ val root = GroupComponent(this@VectorComponent.name).apply {
pivotX = 0.0f
pivotY = 0.0f
- scaleX = defaultWidth / viewportWidth
- scaleY = defaultHeight / viewportHeight
+ scaleX = defaultWidth.value / viewportWidth
+ scaleY = defaultHeight.value / viewportHeight
}
/**
@@ -185,8 +108,8 @@
var targetImage = cachedImage
if (targetImage == null) {
targetImage = Image(
- kotlin.math.ceil(defaultWidth).toInt(),
- kotlin.math.ceil(defaultHeight).toInt()
+ ceil(defaultWidth.value).toInt(),
+ ceil(defaultHeight.value).toInt()
)
cachedImage = targetImage
root.draw(Canvas(targetImage))
@@ -204,21 +127,9 @@
append("\tviewportHeight: ").append(viewportHeight).append("\n")
}
}
-
- override fun emitInsertAt(index: Int, instance: Emittable) {
- root.emitInsertAt(index, instance)
- }
-
- override fun emitMove(from: Int, to: Int, count: Int) {
- root.emitMove(from, to, count)
- }
-
- override fun emitRemoveAt(index: Int, count: Int) {
- root.emitRemoveAt(index, count)
- }
}
-private class Path(val name: String) : VNode(), Emittable {
+class PathComponent(val name: String) : VNode() {
var fill: Brush = EmptyBrush
set(value) {
@@ -292,7 +203,7 @@
private var isPathDirty = true
- private val path = PaintingPath()
+ private val path = Path()
private var fillPaint: Paint? = null
private var strokePaint: Paint? = null
@@ -366,24 +277,12 @@
}
}
- override fun emitInsertAt(index: Int, instance: Emittable) {
- throw IllegalArgumentException("Unable to insert Emittable into Path")
- }
-
- override fun emitMove(from: Int, to: Int, count: Int) {
- throw IllegalArgumentException("Unable to move Emittable within Path")
- }
-
- override fun emitRemoveAt(index: Int, count: Int) {
- throw IllegalArgumentException("Unable to remove Emittable from Path")
- }
-
override fun toString(): String {
return path.toString()
}
}
-private class Group(val name: String = DefaultGroupName) : VNode(), Emittable {
+class GroupComponent(val name: String = DefaultGroupName) : VNode() {
private var groupMatrix: Matrix? = null
@@ -400,7 +299,7 @@
private var isClipPathDirty = true
- private var clipPath: PaintingPath? = null
+ private var clipPath: Path? = null
private var parser: PathParser? = null
private fun updateClipPath() {
@@ -415,7 +314,7 @@
var targetClip = clipPath
if (targetClip == null) {
- targetClip = PaintingPath()
+ targetClip = Path()
clipPath = targetClip
} else {
targetClip.reset()
@@ -425,7 +324,7 @@
}
}
- var rotate: Float = DefaultRotate
+ var rotation: Float = DefaultRotation
set(value) {
field = value
isMatrixDirty = true
@@ -455,13 +354,13 @@
isMatrixDirty = true
}
- var translateX: Float = DefaultTranslateX
+ var translationX: Float = DefaultTranslationX
set(value) {
field = value
isMatrixDirty = true
}
- var translateY: Float = DefaultTranslateY
+ var translationY: Float = DefaultTranslationY
set(value) {
field = value
isMatrixDirty = true
@@ -482,23 +381,21 @@
reset()
postTranslate(-pivotX, -pivotY)
postScale(scaleX, scaleY)
- postRotate(rotate, 0f, 0f)
- postTranslate(translateX + pivotX,
- translateY + pivotY)
+ postRotate(rotation, 0f, 0f)
+ postTranslate(translationX + pivotX,
+ translationY + pivotY)
}
}
- override fun emitInsertAt(index: Int, instance: Emittable) {
- if (instance is VNode) {
- if (index < size) {
- children[index] = instance
- } else {
- children.add(instance)
- }
+ fun insertAt(index: Int, instance: VNode) {
+ if (index < size) {
+ children[index] = instance
+ } else {
+ children.add(instance)
}
}
- override fun emitMove(from: Int, to: Int, count: Int) {
+ fun move(from: Int, to: Int, count: Int) {
if (from > to) {
var current = to
repeat(count) {
@@ -516,7 +413,7 @@
}
}
- override fun emitRemoveAt(index: Int, count: Int) {
+ fun remove(index: Int, count: Int) {
repeat(count) {
children.removeAt(index)
}
@@ -563,7 +460,7 @@
}
}
-private fun createPath(pathData: PathData): Array<PathNode> {
+fun createPath(pathData: PathData): Array<PathNode> {
@Suppress("UNCHECKED_CAST")
return when (pathData) {
is Array<*> -> pathData as Array<PathNode>
@@ -575,39 +472,4 @@
}
else -> throw IllegalArgumentException("Must be array of PathNodes or PathDelegate")
}
-}
-
-// Temporary glue logic to wrap a Vector asset into an ImageView
-fun adoptVectorGraphic(parent: Any?, child: Any?): View? {
- return if (parent is ViewGroup && child is Vector) {
- val imageView = ImageView(parent.context)
- imageView.scaleType = ImageView.ScaleType.FIT_CENTER
- imageView.setImageDrawable(VectorGraphicDrawable(child))
- imageView
- } else {
- null
- }
-}
-
-private class VectorGraphicDrawable(private val vector: Vector) : Drawable() {
-
- override fun getIntrinsicWidth(): Int = Math.round(vector.defaultWidth)
-
- override fun getIntrinsicHeight(): Int = Math.round(vector.defaultHeight)
-
- override fun draw(canvas: android.graphics.Canvas) {
- vector.draw(androidx.ui.painting.Canvas(canvas))
- }
-
- override fun setAlpha(alpha: Int) {
- // TODO support modifying alpha for root of tree down to each node
- TODO("not implemented")
- }
-
- override fun getOpacity(): Int = PixelFormat.UNKNOWN
-
- override fun setColorFilter(colorFilter: ColorFilter?) {
- // TODO support color tinting
- TODO("not implemented")
- }
-}
+}
\ No newline at end of file
diff --git a/ui/ui-core/src/main/java/androidx/ui/painting/Gradient.kt b/ui/ui-core/src/main/java/androidx/ui/painting/Gradient.kt
index 5094d42..03266c7 100644
--- a/ui/ui-core/src/main/java/androidx/ui/painting/Gradient.kt
+++ b/ui/ui-core/src/main/java/androidx/ui/painting/Gradient.kt
@@ -106,25 +106,19 @@
color: List<Color>,
colorStops: List<Float>?,
tileMode: TileMode = TileMode.clamp,
- @Suppress("UNUSED_PARAMETER") matrix4: Matrix4,
- focal: Offset?,
- focalRadius: Float
+ @Suppress("UNUSED_PARAMETER") matrix4: Matrix4
): Gradient {
_validateColorStops(color, colorStops)
- if (focal == null || (focal == center && focalRadius == 0.0f)) {
- TODO("Migration/njawad: add focal support to RadialGradient in framework")
- } else {
- // TODO(Migration/njawad use matrix parameter in creation of RadialGradient)
- val radial = android.graphics.RadialGradient(
- center.dx,
- center.dy,
- radius,
- toIntArray(color),
- toFloatArray(colorStops),
- toFrameworkTileMode(tileMode)
- )
- return Gradient(radial)
- }
+ // TODO(Migration/njawad use matrix parameter in creation of RadialGradient)
+ val radial = android.graphics.RadialGradient(
+ center.dx,
+ center.dy,
+ radius,
+ toIntArray(color),
+ toFloatArray(colorStops),
+ toFrameworkTileMode(tileMode)
+ )
+ return Gradient(radial)
}
/**
@@ -202,9 +196,9 @@
private fun _validateColorStops(colors: List<Color>, colorStops: List<Float>?) {
if (colorStops == null) {
- if (colors.size != 2) {
+ if (colors.size < 2) {
throw IllegalArgumentException(
- "colors must have length 2 if colorStops " +
+ "colors must have length of at least 2 if colorStops " +
"is omitted."
)
}
diff --git a/ui/ui-core/src/main/java/androidx/ui/semantics/SemanticsActions.kt b/ui/ui-core/src/main/java/androidx/ui/semantics/SemanticsActions.kt
deleted file mode 100644
index c1511f5..0000000
--- a/ui/ui-core/src/main/java/androidx/ui/semantics/SemanticsActions.kt
+++ /dev/null
@@ -1,324 +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.ui.core.semantics
-
-import androidx.ui.text.TextSelection
-
-private const val INDEX_TAP = 1 shl 0
-private const val INDEX_LONG_PRESS = 1 shl 1
-private const val INDEX_SCROLL_LEFT = 1 shl 2
-private const val INDEX_SCROLL_RIGHT = 1 shl 3
-private const val INDEX_SCROLL_UP = 1 shl 4
-private const val INDEX_SCROLL_DOWN = 1 shl 5
-private const val INDEX_INCREASE = 1 shl 6
-private const val INDEX_DECREASE = 1 shl 7
-private const val INDEX_SHOW_ON_SCREEN = 1 shl 8
-private const val INDEX_MOVE_CURSOR_FORWARD_BY_CHARACTER = 1 shl 9
-private const val INDEX_MOVE_CURSOR_BACKWARD_BY_CHARACTER = 1 shl 10
-private const val INDEX_SET_SELECTION = 1 shl 11
-private const val INDEX_COPY = 1 shl 12
-private const val INDEX_CUT = 1 shl 13
-private const val INDEX_PASTE = 1 shl 14
-private const val INDEX_DID_GAIN_ACCESSIBILITY_FOCUS = 1 shl 15
-private const val INDEX_DID_LOSE_ACCESSIBILITY_FOCUS = 1 shl 16
-private const val INDEX_CUSTOM_ACTION = 1 shl 17
-private const val INDEX_DISMISS = 1 shl 18
-private const val INDEX_MOVE_CURSOR_FORWARD_BY_WORD = 1 shl 19
-private const val INDEX_MOVE_CURSOR_BACKWARD_BY_WORD = 1 shl 20
-
-private typealias VoidCallback = () -> Unit
-
-/**
- * Signature for [SemanticsActionType]s that move the cursor.
- *
- * If `extendSelection` is set to true the cursor movement should extend the
- * current selection or (if nothing is currently selected) start a selection.
- */
-typealias MoveCursorHandler = (extendSelection: Boolean) -> Unit
-
-/**
- * Signature for the [SemanticsActionType.SetSelection] handlers to change the
- * text selection (or re-position the cursor) to `selection`.
- */
-typealias SetSelectionHandler = (selection: TextSelection) -> Unit
-
-data class SemanticsAction<T : Any>(val type: SemanticsActionType<T>, val handler: T) {
- fun invokeHandler(args: Any?) {
- @Suppress("UNCHECKED_CAST")
- when (type.numArguments) {
- 0 -> (handler as VoidCallback)()
- 1 -> (handler as (Any?) -> Unit)(args)
- else -> throw IllegalStateException("Invalid number of arguments: ${type.numArguments}")
- }
- }
-}
-
-/**
- * The possible actions that can be conveyed from the operating system
- * accessibility APIs to a semantics node.
- */
-class SemanticsActionType<T> private constructor(
- /**
- * The name of the action.
- */
- private val name: String,
- /**
- * The numerical value for this action.
- *
- * Each action has one bit set in this bit field.
- */
- // TODO(ryanmentley): this should be internal, but can't because we need it from other packages
- val bitmask: Int,
- internal val numArguments: Int
-) {
- companion object {
- /**
- * The equivalent of a user briefly tapping the screen with the finger
- * without moving it.
- */
- val Tap = SemanticsActionType<VoidCallback>("Tap", INDEX_TAP, 0)
-
- /**
- * The equivalent of a user pressing and holding the screen with the finger
- * for a few seconds without moving it.
- */
- val LongPress = SemanticsActionType<VoidCallback>("LongPress", INDEX_LONG_PRESS, 0)
-
- /**
- * The equivalent of a user moving their finger across the screen from right
- * to left.
- *
- * This action should be recognized by controls that are horizontally
- * scrollable.
- */
- val ScrollLeft = SemanticsActionType<VoidCallback>("ScrollLeft", INDEX_SCROLL_LEFT, 0)
-
- /**
- * The equivalent of a user moving their finger across the screen from left
- * to right.
- *
- * This action should be recognized by controls that are horizontally
- * scrollable.
- */
- val ScrollRight = SemanticsActionType<VoidCallback>("ScrollRight", INDEX_SCROLL_RIGHT, 0)
-
- /**
- * The equivalent of a user moving their finger across the screen from
- * bottom to top.
- *
- * This action should be recognized by controls that are vertically
- * scrollable.
- */
- val ScrollUp = SemanticsActionType<VoidCallback>("ScrollUp", INDEX_SCROLL_UP, 0)
-
- /**
- * The equivalent of a user moving their finger across the screen from top
- * to bottom.
- *
- * This action should be recognized by controls that are vertically
- * scrollable.
- */
- val ScrollDown = SemanticsActionType<VoidCallback>("ScrollDown", INDEX_SCROLL_DOWN, 0)
-
- /**
- * A request to increase the value represented by the semantics node.
- *
- * For example, this action might be recognized by a slider control.
- */
- val Increase = SemanticsActionType<VoidCallback>("Increase", INDEX_INCREASE, 0)
-
- /**
- * A request to decrease the value represented by the semantics node.
- *
- * For example, this action might be recognized by a slider control.
- */
- val Decrease = SemanticsActionType<VoidCallback>("Decrease", INDEX_DECREASE, 0)
-
- /**
- * A request to fully show the semantics node on screen.
- *
- * For example, this action might be send to a node in a scrollable list that
- * is partially off screen to bring it on screen.
- */
- val ShowOnScreen = SemanticsActionType<VoidCallback>(
- "ShowOnScreen", INDEX_SHOW_ON_SCREEN, 0
- )
-
- /**
- * Move the cursor forward by one character.
- *
- * This is for example used by the cursor control in text fields.
- *
- * The action includes a boolean argument, which indicates whether the cursor
- * movement should extend (or start) a selection.
- */
- val MoveCursorForwardByCharacter = SemanticsActionType<MoveCursorHandler>(
- "MoveCursorForwardByCharacter", INDEX_MOVE_CURSOR_FORWARD_BY_CHARACTER, 1
- )
-
- /**
- * Move the cursor backward by one character.
- *
- * This is for example used by the cursor control in text fields.
- *
- * The action includes a boolean argument, which indicates whether the cursor
- * movement should extend (or start) a selection.
- */
- val MoveCursorBackwardByCharacter = SemanticsActionType<MoveCursorHandler>(
- "MoveCursorBackwardByCharacter", INDEX_MOVE_CURSOR_BACKWARD_BY_CHARACTER, 1
- )
-
- /**
- * Set the text selection to the given range.
- *
- * The provided argument is a Map<String, int> which includes the keys `base`
- * and `extent` indicating where the selection within the `value` of the
- * semantics node should start and where it should end. Values for both
- * keys can range from 0 to length of `value` (inclusive).
- *
- * Setting `base` and `extent` to the same value will move the cursor to
- * that position (without selecting anything).
- */
- val SetSelection = SemanticsActionType<SetSelectionHandler>(
- "SetSelection", INDEX_SET_SELECTION, 1
- )
-
- /** Copy the current selection to the clipboard. */
- val Copy = SemanticsActionType<VoidCallback>("Copy", INDEX_COPY, 0)
-
- /** Cut the current selection and place it in the clipboard. */
- val Cut = SemanticsActionType<VoidCallback>("Cut", INDEX_CUT, 0)
-
- /** Paste the current content of the clipboard. */
- val Paste = SemanticsActionType<VoidCallback>("Paste", INDEX_PASTE, 0)
-
- /**
- * Indicates that the nodes has gained accessibility focus.
- *
- * This handler is invoked when the node annotated with this handler gains
- * the accessibility focus. The accessibility focus is the
- * green (on Android with TalkBack) or black (on iOS with VoiceOver)
- * rectangle shown on screen to indicate what element an accessibility
- * user is currently interacting with.
- *
- * The accessibility focus is different from the input focus. The input focus
- * is usually held by the element that currently responds to keyboard inputs.
- * Accessibility focus and input focus can be held by two different nodes!
- */
- val DidGainAccessibilityFocus = SemanticsActionType<VoidCallback>(
- "DidGainAccessibilityFocus", INDEX_DID_GAIN_ACCESSIBILITY_FOCUS, 0
- )
-
- /**
- * Indicates that the nodes has lost accessibility focus.
- *
- * This handler is invoked when the node annotated with this handler
- * loses the accessibility focus. The accessibility focus is
- * the green (on Android with TalkBack) or black (on iOS with VoiceOver)
- * rectangle shown on screen to indicate what element an accessibility
- * user is currently interacting with.
- *
- * The accessibility focus is different from the input focus. The input focus
- * is usually held by the element that currently responds to keyboard inputs.
- * Accessibility focus and input focus can be held by two different nodes!
- */
- val DidLoseAccessibilityFocus = SemanticsActionType<VoidCallback>(
- "DidLoseAccessibilityFocus", INDEX_DID_LOSE_ACCESSIBILITY_FOCUS, 0
- )
-
- /**
- * Indicates that the user has invoked a custom accessibility action.
- *
- * This handler is added automatically whenever a custom accessibility
- * action is added to a semantics node.
- */
- // TODO(ryanmentley): this needs parameters
- val CustomAction = SemanticsActionType<VoidCallback>(
- "CustomAction", INDEX_CUSTOM_ACTION, 0
- )
-
- /**
- * A request that the node should be dismissed.
- *
- * A [Snackbar], for example, may have a dismiss action to indicate to the
- * user that it can be removed after it is no longer relevant. On Android,
- * (with TalkBack) special hint text is spoken when focusing the node and
- * a custom action is availible in the local context menu. On iOS,
- * (with VoiceOver) users can perform a standard gesture to dismiss it.
- */
- val Dismiss = SemanticsActionType<VoidCallback>("Dismiss", INDEX_DISMISS, 0)
-
- /**
- * Move the cursor forward by one word.
- *
- * This is for example used by the cursor control in text fields.
- *
- * The action includes a boolean argument, which indicates whether the cursor
- * movement should extend (or start) a selection.
- */
- val MoveCursorForwardByWord = SemanticsActionType<MoveCursorHandler>(
- "MoveCursorForwardByWord", INDEX_MOVE_CURSOR_FORWARD_BY_WORD, 1
- )
-
- /**
- * Move the cursor backward by one word.
- *
- * This is for example used by the cursor control in text fields.
- *
- * The action includes a boolean argument, which indicates whether the cursor
- * movement should extend (or start) a selection.
- */
- val MoveCursorBackwardByWord = SemanticsActionType<MoveCursorHandler>(
- "MoveCursorBackwardByWord", INDEX_MOVE_CURSOR_BACKWARD_BY_WORD, 1
- )
-
- /**
- * The possible semantics actions.
- *
- * The map's key is the [bitmask] of the action and the value is the action
- * itself.
- */
- // TODO(ryanmentley): maybe unneeded, remove if not needed in final impl
- private val values: Map<Int, SemanticsActionType<*>> = mapOf(
- INDEX_TAP to Tap,
- INDEX_LONG_PRESS to LongPress,
- INDEX_SCROLL_LEFT to ScrollLeft,
- INDEX_SCROLL_RIGHT to ScrollRight,
- INDEX_SCROLL_UP to ScrollUp,
- INDEX_SCROLL_DOWN to ScrollDown,
- INDEX_INCREASE to Increase,
- INDEX_DECREASE to Decrease,
- INDEX_SHOW_ON_SCREEN to ShowOnScreen,
- INDEX_MOVE_CURSOR_FORWARD_BY_CHARACTER to MoveCursorForwardByCharacter,
- INDEX_MOVE_CURSOR_BACKWARD_BY_CHARACTER to MoveCursorBackwardByCharacter,
- INDEX_SET_SELECTION to SetSelection,
- INDEX_COPY to Copy,
- INDEX_CUT to Cut,
- INDEX_PASTE to Paste,
- INDEX_DID_GAIN_ACCESSIBILITY_FOCUS to DidGainAccessibilityFocus,
- INDEX_DID_LOSE_ACCESSIBILITY_FOCUS to DidLoseAccessibilityFocus,
- INDEX_CUSTOM_ACTION to CustomAction,
- INDEX_DISMISS to Dismiss,
- INDEX_MOVE_CURSOR_FORWARD_BY_WORD to MoveCursorForwardByWord,
- INDEX_MOVE_CURSOR_BACKWARD_BY_WORD to MoveCursorBackwardByWord
- )
- }
-
- override fun toString(): String {
- return "SemanticsActionType.$name"
- }
-}
\ No newline at end of file
diff --git a/ui/ui-core/src/main/java/androidx/ui/semantics/SemanticsProperties.kt b/ui/ui-core/src/main/java/androidx/ui/semantics/SemanticsProperties.kt
new file mode 100644
index 0000000..a0a9251
--- /dev/null
+++ b/ui/ui-core/src/main/java/androidx/ui/semantics/SemanticsProperties.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.ui.semantics
+
+import kotlin.properties.ReadWriteProperty
+import kotlin.reflect.KProperty
+
+open class SemanticsPropertyKey<T>(
+ /**
+ * The name of the property. Should be the same as the constant from which it is accessed.
+ */
+ val name: String
+) :
+ ReadWriteProperty<SemanticsPropertyReceiver, T> {
+ /**
+ * Subclasses that wish to implement merging should override this to output the merged value
+ *
+ * This implementation always throws IllegalStateException. It should be overridden for
+ * properties that can be merged.
+ */
+ open fun merge(existingValue: T, newValue: T): T {
+ throw IllegalStateException(
+ "merge function called on unmergeable property $name. " +
+ "You may need to add a semantic boundary."
+ )
+ }
+
+ /**
+ * Throws [UnsupportedOperationException]. Should not be called.
+ */
+ // noinspection DeprecatedCallableAddReplaceWith
+ // TODO(KT-32770): Re-deprecate this
+ // @Deprecated(
+ // message = "You cannot retrieve a semantics property directly - " +
+ // "use one of the SemanticsConfiguration.getOr* methods instead",
+ // level = DeprecationLevel.ERROR
+ // )
+ // TODO(KT-6519): Remove this getter entirely
+ final override fun getValue(thisRef: SemanticsPropertyReceiver, property: KProperty<*>): T {
+ throw UnsupportedOperationException(
+ "You cannot retrieve a semantics property directly - " +
+ "use one of the SemanticsConfiguration.getOr* methods instead"
+ )
+ }
+
+ final override fun setValue(
+ thisRef: SemanticsPropertyReceiver,
+ property: KProperty<*>,
+ value: T
+ ) {
+ thisRef[this] = value
+ }
+}
+
+// This needs to be in core because it needs to be accessible from platform
+data class AccessibilityAction<T : Function<Unit>>(val label: String?, val action: T)
+
+interface SemanticsPropertyReceiver {
+ operator fun <T> set(key: SemanticsPropertyKey<T>, value: T)
+}
diff --git a/ui/ui-core/src/main/java/androidx/ui/temputils/CoroutineUtils.kt b/ui/ui-core/src/main/java/androidx/ui/temputils/CoroutineUtils.kt
new file mode 100644
index 0000000..a0bfc403
--- /dev/null
+++ b/ui/ui-core/src/main/java/androidx/ui/temputils/CoroutineUtils.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.ui.temputils
+
+import androidx.ui.core.Duration
+import androidx.ui.core.inMilliseconds
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+import kotlin.coroutines.CoroutineContext
+
+/*
+ * This file is a temporary place to utilize coroutines before they are work in the IR compiler.
+ */
+
+/**
+ * Run [block] after [duration] time passes using [context].
+ *
+ * @return [Job] which is a reference to the running coroutine such that it can be cancelled via [Job.cancel].
+ */
+fun delay(duration: Duration, context: CoroutineContext, block: () -> Unit) =
+ CoroutineScope(context).launch {
+ kotlinx.coroutines.delay(duration.inMilliseconds())
+ block()
+ }
\ No newline at end of file
diff --git a/ui/ui-core/src/main/java/androidx/ui/testutils/PointerInputTestUtil.kt b/ui/ui-core/src/main/java/androidx/ui/testutils/PointerInputTestUtil.kt
index 335e2c9..e00bfbf 100644
--- a/ui/ui-core/src/main/java/androidx/ui/testutils/PointerInputTestUtil.kt
+++ b/ui/ui-core/src/main/java/androidx/ui/testutils/PointerInputTestUtil.kt
@@ -96,3 +96,14 @@
PointerEventPass.PostUp,
PointerEventPass.PostDown
)
+
+fun PointerInputHandler.invokeOverAllPasses(
+ vararg pointerInputChanges: PointerInputChange
+) = invokeOverPasses(
+ pointerInputChanges.toList(),
+ PointerEventPass.InitialDown,
+ PointerEventPass.PreUp,
+ PointerEventPass.PreDown,
+ PointerEventPass.PostUp,
+ PointerEventPass.PostDown
+)
\ No newline at end of file
diff --git a/ui/ui-foundation/api/1.0.0-alpha01.txt b/ui/ui-foundation/api/1.0.0-alpha01.txt
index 26ee3a8..3329533 100644
--- a/ui/ui-foundation/api/1.0.0-alpha01.txt
+++ b/ui/ui-foundation/api/1.0.0-alpha01.txt
@@ -8,7 +8,7 @@
public final class ColoredRectKt {
ctor public ColoredRectKt();
- method public static void ColoredRect(androidx.ui.core.vectorgraphics.Brush brush, androidx.ui.core.Dp? width = null, androidx.ui.core.Dp? height = null);
+ method public static void ColoredRect(androidx.ui.graphics.vectorgraphics.Brush brush, androidx.ui.core.Dp? width = null, androidx.ui.core.Dp? height = null);
method public static void ColoredRect(androidx.ui.graphics.Color color, androidx.ui.core.Dp? width = null, androidx.ui.core.Dp? height = null);
}
@@ -17,50 +17,66 @@
method public static void DeterminateProgressIndicator(@FloatRange(from=0.0, to=1.0) float progress, kotlin.jvm.functions.Function0<kotlin.Unit> children);
}
+ public final class DialogKt {
+ ctor public DialogKt();
+ method public static void Dialog(kotlin.jvm.functions.Function0<kotlin.Unit> onCloseRequest, kotlin.jvm.functions.Function0<kotlin.Unit> children);
+ }
+
public final class SimpleImageKt {
ctor public SimpleImageKt();
method public static void SimpleImage(androidx.ui.painting.Image image, androidx.ui.graphics.Color? tint = null);
}
+ public final class Strings {
+ method public String getChecked();
+ method public String getIndeterminate();
+ method public String getNotSelected();
+ method public String getSelected();
+ method public String getUnchecked();
+ property public final String Checked;
+ property public final String Indeterminate;
+ property public final String NotSelected;
+ property public final String Selected;
+ property public final String Unchecked;
+ field public static final androidx.ui.foundation.Strings! INSTANCE;
+ }
+
}
-package androidx.ui.foundation.gestures {
+package androidx.ui.foundation.animation {
- public final class AnchorsFlingConfig implements androidx.ui.foundation.gestures.FlingConfig {
- ctor public AnchorsFlingConfig(java.util.List<java.lang.Float> animationAnchors, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onAnimationFinished, androidx.animation.AnimationBuilder<java.lang.Float> animationBuilder, androidx.animation.DecayAnimation decayAnimation);
- method public java.util.List<java.lang.Float> component1();
+ public final class AnimatedFloatDragController implements androidx.ui.foundation.gestures.DragValueController {
+ ctor public AnimatedFloatDragController(float initialValue, androidx.ui.foundation.animation.FlingConfig? flingConfig);
+ method public androidx.animation.AnimatedFloat getAnimatedFloat();
+ method public float getCurrentValue();
+ method public void onDrag(float target);
+ method public void onDragEnd(float velocity, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueSettled);
+ method public void setBounds(float min, float max);
+ property public final androidx.animation.AnimatedFloat animatedFloat;
+ property public float currentValue;
+ }
+
+ public final class FlingConfig {
+ ctor public FlingConfig(androidx.animation.DecayAnimation decayAnimation, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onAnimationFinished, kotlin.jvm.functions.Function1<? super java.lang.Float,androidx.animation.TargetAnimation> adjustTarget);
+ ctor public FlingConfig();
+ method public androidx.animation.DecayAnimation component1();
method public kotlin.jvm.functions.Function2<java.lang.Float,java.lang.Boolean,kotlin.Unit>? component2();
- method public androidx.animation.AnimationBuilder<java.lang.Float> component3();
- method public androidx.animation.DecayAnimation component4();
- method public androidx.ui.foundation.gestures.AnchorsFlingConfig copy(java.util.List<java.lang.Float> animationAnchors, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onAnimationFinished, androidx.animation.AnimationBuilder<java.lang.Float> animationBuilder, androidx.animation.DecayAnimation decayAnimation);
- method public void fling(androidx.animation.AnimatedFloat value, float startVelocity);
- method public java.util.List<java.lang.Float> getAnimationAnchors();
- method public androidx.animation.AnimationBuilder<java.lang.Float> getAnimationBuilder();
+ method public kotlin.jvm.functions.Function1<java.lang.Float,androidx.animation.TargetAnimation> component3();
+ method public androidx.ui.foundation.animation.FlingConfig copy(androidx.animation.DecayAnimation decayAnimation, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onAnimationFinished, kotlin.jvm.functions.Function1<? super java.lang.Float,androidx.animation.TargetAnimation> adjustTarget);
+ method public kotlin.jvm.functions.Function1<java.lang.Float,androidx.animation.TargetAnimation> getAdjustTarget();
method public androidx.animation.DecayAnimation getDecayAnimation();
method public kotlin.jvm.functions.Function2<java.lang.Float,java.lang.Boolean,kotlin.Unit>? getOnAnimationFinished();
}
- public final class AnimatedDraggableKt {
- ctor public AnimatedDraggableKt();
- method public static void AnimatedDraggable(androidx.ui.foundation.gestures.DragDirection dragDirection, float startValue, float minValue = Float.MIN_VALUE, float maxValue = Float.MAX_VALUE, androidx.ui.foundation.gestures.FlingConfig? flingConfig = null, kotlin.jvm.functions.Function1<? super androidx.animation.BaseAnimatedValue<java.lang.Float>,kotlin.Unit> children);
+ public final class FlingConfigKt {
+ ctor public FlingConfigKt();
+ method public static androidx.ui.foundation.animation.FlingConfig AnchorsFlingConfig(java.util.List<java.lang.Float> anchors, androidx.animation.AnimationBuilder<java.lang.Float> animationBuilder = PhysicsBuilder(), kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onAnimationFinished = null, androidx.animation.DecayAnimation decayAnimation = ExponentialDecay());
+ method public static void fling(androidx.animation.AnimatedFloat, androidx.ui.foundation.animation.FlingConfig config, float startVelocity);
}
- public final class DecayFlingConfig implements androidx.ui.foundation.gestures.FlingConfig {
- ctor public DecayFlingConfig(androidx.animation.DecayAnimation decayAnimation, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onFlingFinished, kotlin.jvm.functions.Function1<? super java.lang.Float,androidx.animation.TargetAnimation> adjustTarget);
- method public androidx.animation.DecayAnimation component1();
- method public kotlin.jvm.functions.Function2<java.lang.Float,java.lang.Boolean,kotlin.Unit>? component2();
- method public kotlin.jvm.functions.Function1<java.lang.Float,androidx.animation.TargetAnimation> component3();
- method public androidx.ui.foundation.gestures.DecayFlingConfig copy(androidx.animation.DecayAnimation decayAnimation, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onFlingFinished, kotlin.jvm.functions.Function1<? super java.lang.Float,androidx.animation.TargetAnimation> adjustTarget);
- method public void fling(androidx.animation.AnimatedFloat value, float startVelocity);
- method public kotlin.jvm.functions.Function1<java.lang.Float,androidx.animation.TargetAnimation> getAdjustTarget();
- method public androidx.animation.DecayAnimation getDecayAnimation();
- method public kotlin.jvm.functions.Function2<java.lang.Float,java.lang.Boolean,kotlin.Unit>? getOnFlingFinished();
- }
+}
- public final class DefaultFlingConfig implements androidx.ui.foundation.gestures.FlingConfig {
- method public void fling(androidx.animation.AnimatedFloat value, float startVelocity);
- field public static final androidx.ui.foundation.gestures.DefaultFlingConfig! INSTANCE;
- }
+package androidx.ui.foundation.gestures {
public abstract sealed class DragDirection {
}
@@ -73,8 +89,33 @@
field public static final androidx.ui.foundation.gestures.DragDirection.Vertical! INSTANCE;
}
- public interface FlingConfig {
- method public void fling(androidx.animation.AnimatedFloat value, float startVelocity);
+ public interface DragValueController {
+ method public float getCurrentValue();
+ method public void onDrag(float target);
+ method public void onDragEnd(float velocity, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueSettled);
+ method public void setBounds(float min, float max);
+ property public abstract float currentValue;
+ }
+
+ public final class DraggableCallback {
+ ctor public DraggableCallback(kotlin.jvm.functions.Function0<kotlin.Unit> onDragStarted, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onDragSettled);
+ ctor public DraggableCallback();
+ }
+
+ public final class DraggableKt {
+ ctor public DraggableKt();
+ method public static void Draggable(androidx.ui.foundation.gestures.DragDirection dragDirection, float minValue = Float.MIN_VALUE, float maxValue = Float.MAX_VALUE, androidx.ui.foundation.gestures.DragValueController valueController = +memo(minValue, {
+ <init>(minValue)
+}), androidx.ui.foundation.gestures.DraggableCallback? callback = null, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> children);
+ }
+
+ public final class FloatDragValueController implements androidx.ui.foundation.gestures.DragValueController {
+ ctor public FloatDragValueController(float initialValue);
+ method public float getCurrentValue();
+ method public void onDrag(float target);
+ method public void onDragEnd(float velocity, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueSettled);
+ method public void setBounds(float min, float max);
+ property public float currentValue;
}
}
@@ -100,12 +141,36 @@
}
+package androidx.ui.foundation.semantics {
+
+ public final class FoundationSemanticsProperties {
+ method public androidx.ui.semantics.SemanticsPropertyKey<java.lang.Boolean> getInMutuallyExclusiveGroup();
+ method public androidx.ui.semantics.SemanticsPropertyKey<java.lang.Boolean> getSelected();
+ method public androidx.ui.semantics.SemanticsPropertyKey<androidx.ui.foundation.selection.ToggleableState> getToggleableState();
+ property public final androidx.ui.semantics.SemanticsPropertyKey<java.lang.Boolean> InMutuallyExclusiveGroup;
+ property public final androidx.ui.semantics.SemanticsPropertyKey<java.lang.Boolean> Selected;
+ property public final androidx.ui.semantics.SemanticsPropertyKey<androidx.ui.foundation.selection.ToggleableState> ToggleableState;
+ field public static final androidx.ui.foundation.semantics.FoundationSemanticsProperties! INSTANCE;
+ }
+
+ public final class FoundationSemanticsPropertiesKt {
+ ctor public FoundationSemanticsPropertiesKt();
+ method public static boolean getInMutuallyExclusiveGroup(androidx.ui.semantics.SemanticsPropertyReceiver);
+ method public static boolean getSelected(androidx.ui.semantics.SemanticsPropertyReceiver);
+ method public static androidx.ui.foundation.selection.ToggleableState getToggleableState(androidx.ui.semantics.SemanticsPropertyReceiver);
+ method public static void setInMutuallyExclusiveGroup(androidx.ui.semantics.SemanticsPropertyReceiver, boolean p);
+ method public static void setSelected(androidx.ui.semantics.SemanticsPropertyReceiver, boolean p);
+ method public static void setToggleableState(androidx.ui.semantics.SemanticsPropertyReceiver, androidx.ui.foundation.selection.ToggleableState p);
+ }
+
+}
+
package androidx.ui.foundation.shape {
public final class DrawShapeKt {
ctor public DrawShapeKt();
method public static void DrawShape(androidx.ui.engine.geometry.Shape shape, androidx.ui.graphics.Color color);
- method public static void DrawShape(androidx.ui.engine.geometry.Shape shape, androidx.ui.core.vectorgraphics.Brush brush);
+ method public static void DrawShape(androidx.ui.engine.geometry.Shape shape, androidx.ui.graphics.vectorgraphics.Brush brush);
}
public final class GenericShape implements androidx.ui.engine.geometry.Shape {
@@ -124,11 +189,11 @@
package androidx.ui.foundation.shape.border {
public final class Border {
- ctor public Border(androidx.ui.core.vectorgraphics.Brush brush, androidx.ui.core.Dp width);
- method public androidx.ui.core.vectorgraphics.Brush component1();
+ ctor public Border(androidx.ui.graphics.vectorgraphics.Brush brush, androidx.ui.core.Dp width);
+ method public androidx.ui.graphics.vectorgraphics.Brush component1();
method public androidx.ui.core.Dp component2();
- method public androidx.ui.foundation.shape.border.Border copy(androidx.ui.core.vectorgraphics.Brush brush, androidx.ui.core.Dp width);
- method public androidx.ui.core.vectorgraphics.Brush getBrush();
+ method public androidx.ui.foundation.shape.border.Border copy(androidx.ui.graphics.vectorgraphics.Brush brush, androidx.ui.core.Dp width);
+ method public androidx.ui.graphics.vectorgraphics.Brush getBrush();
method public androidx.ui.core.Dp getWidth();
}
diff --git a/ui/ui-foundation/api/current.txt b/ui/ui-foundation/api/current.txt
index 26ee3a8..3329533 100644
--- a/ui/ui-foundation/api/current.txt
+++ b/ui/ui-foundation/api/current.txt
@@ -8,7 +8,7 @@
public final class ColoredRectKt {
ctor public ColoredRectKt();
- method public static void ColoredRect(androidx.ui.core.vectorgraphics.Brush brush, androidx.ui.core.Dp? width = null, androidx.ui.core.Dp? height = null);
+ method public static void ColoredRect(androidx.ui.graphics.vectorgraphics.Brush brush, androidx.ui.core.Dp? width = null, androidx.ui.core.Dp? height = null);
method public static void ColoredRect(androidx.ui.graphics.Color color, androidx.ui.core.Dp? width = null, androidx.ui.core.Dp? height = null);
}
@@ -17,50 +17,66 @@
method public static void DeterminateProgressIndicator(@FloatRange(from=0.0, to=1.0) float progress, kotlin.jvm.functions.Function0<kotlin.Unit> children);
}
+ public final class DialogKt {
+ ctor public DialogKt();
+ method public static void Dialog(kotlin.jvm.functions.Function0<kotlin.Unit> onCloseRequest, kotlin.jvm.functions.Function0<kotlin.Unit> children);
+ }
+
public final class SimpleImageKt {
ctor public SimpleImageKt();
method public static void SimpleImage(androidx.ui.painting.Image image, androidx.ui.graphics.Color? tint = null);
}
+ public final class Strings {
+ method public String getChecked();
+ method public String getIndeterminate();
+ method public String getNotSelected();
+ method public String getSelected();
+ method public String getUnchecked();
+ property public final String Checked;
+ property public final String Indeterminate;
+ property public final String NotSelected;
+ property public final String Selected;
+ property public final String Unchecked;
+ field public static final androidx.ui.foundation.Strings! INSTANCE;
+ }
+
}
-package androidx.ui.foundation.gestures {
+package androidx.ui.foundation.animation {
- public final class AnchorsFlingConfig implements androidx.ui.foundation.gestures.FlingConfig {
- ctor public AnchorsFlingConfig(java.util.List<java.lang.Float> animationAnchors, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onAnimationFinished, androidx.animation.AnimationBuilder<java.lang.Float> animationBuilder, androidx.animation.DecayAnimation decayAnimation);
- method public java.util.List<java.lang.Float> component1();
+ public final class AnimatedFloatDragController implements androidx.ui.foundation.gestures.DragValueController {
+ ctor public AnimatedFloatDragController(float initialValue, androidx.ui.foundation.animation.FlingConfig? flingConfig);
+ method public androidx.animation.AnimatedFloat getAnimatedFloat();
+ method public float getCurrentValue();
+ method public void onDrag(float target);
+ method public void onDragEnd(float velocity, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueSettled);
+ method public void setBounds(float min, float max);
+ property public final androidx.animation.AnimatedFloat animatedFloat;
+ property public float currentValue;
+ }
+
+ public final class FlingConfig {
+ ctor public FlingConfig(androidx.animation.DecayAnimation decayAnimation, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onAnimationFinished, kotlin.jvm.functions.Function1<? super java.lang.Float,androidx.animation.TargetAnimation> adjustTarget);
+ ctor public FlingConfig();
+ method public androidx.animation.DecayAnimation component1();
method public kotlin.jvm.functions.Function2<java.lang.Float,java.lang.Boolean,kotlin.Unit>? component2();
- method public androidx.animation.AnimationBuilder<java.lang.Float> component3();
- method public androidx.animation.DecayAnimation component4();
- method public androidx.ui.foundation.gestures.AnchorsFlingConfig copy(java.util.List<java.lang.Float> animationAnchors, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onAnimationFinished, androidx.animation.AnimationBuilder<java.lang.Float> animationBuilder, androidx.animation.DecayAnimation decayAnimation);
- method public void fling(androidx.animation.AnimatedFloat value, float startVelocity);
- method public java.util.List<java.lang.Float> getAnimationAnchors();
- method public androidx.animation.AnimationBuilder<java.lang.Float> getAnimationBuilder();
+ method public kotlin.jvm.functions.Function1<java.lang.Float,androidx.animation.TargetAnimation> component3();
+ method public androidx.ui.foundation.animation.FlingConfig copy(androidx.animation.DecayAnimation decayAnimation, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onAnimationFinished, kotlin.jvm.functions.Function1<? super java.lang.Float,androidx.animation.TargetAnimation> adjustTarget);
+ method public kotlin.jvm.functions.Function1<java.lang.Float,androidx.animation.TargetAnimation> getAdjustTarget();
method public androidx.animation.DecayAnimation getDecayAnimation();
method public kotlin.jvm.functions.Function2<java.lang.Float,java.lang.Boolean,kotlin.Unit>? getOnAnimationFinished();
}
- public final class AnimatedDraggableKt {
- ctor public AnimatedDraggableKt();
- method public static void AnimatedDraggable(androidx.ui.foundation.gestures.DragDirection dragDirection, float startValue, float minValue = Float.MIN_VALUE, float maxValue = Float.MAX_VALUE, androidx.ui.foundation.gestures.FlingConfig? flingConfig = null, kotlin.jvm.functions.Function1<? super androidx.animation.BaseAnimatedValue<java.lang.Float>,kotlin.Unit> children);
+ public final class FlingConfigKt {
+ ctor public FlingConfigKt();
+ method public static androidx.ui.foundation.animation.FlingConfig AnchorsFlingConfig(java.util.List<java.lang.Float> anchors, androidx.animation.AnimationBuilder<java.lang.Float> animationBuilder = PhysicsBuilder(), kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onAnimationFinished = null, androidx.animation.DecayAnimation decayAnimation = ExponentialDecay());
+ method public static void fling(androidx.animation.AnimatedFloat, androidx.ui.foundation.animation.FlingConfig config, float startVelocity);
}
- public final class DecayFlingConfig implements androidx.ui.foundation.gestures.FlingConfig {
- ctor public DecayFlingConfig(androidx.animation.DecayAnimation decayAnimation, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onFlingFinished, kotlin.jvm.functions.Function1<? super java.lang.Float,androidx.animation.TargetAnimation> adjustTarget);
- method public androidx.animation.DecayAnimation component1();
- method public kotlin.jvm.functions.Function2<java.lang.Float,java.lang.Boolean,kotlin.Unit>? component2();
- method public kotlin.jvm.functions.Function1<java.lang.Float,androidx.animation.TargetAnimation> component3();
- method public androidx.ui.foundation.gestures.DecayFlingConfig copy(androidx.animation.DecayAnimation decayAnimation, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onFlingFinished, kotlin.jvm.functions.Function1<? super java.lang.Float,androidx.animation.TargetAnimation> adjustTarget);
- method public void fling(androidx.animation.AnimatedFloat value, float startVelocity);
- method public kotlin.jvm.functions.Function1<java.lang.Float,androidx.animation.TargetAnimation> getAdjustTarget();
- method public androidx.animation.DecayAnimation getDecayAnimation();
- method public kotlin.jvm.functions.Function2<java.lang.Float,java.lang.Boolean,kotlin.Unit>? getOnFlingFinished();
- }
+}
- public final class DefaultFlingConfig implements androidx.ui.foundation.gestures.FlingConfig {
- method public void fling(androidx.animation.AnimatedFloat value, float startVelocity);
- field public static final androidx.ui.foundation.gestures.DefaultFlingConfig! INSTANCE;
- }
+package androidx.ui.foundation.gestures {
public abstract sealed class DragDirection {
}
@@ -73,8 +89,33 @@
field public static final androidx.ui.foundation.gestures.DragDirection.Vertical! INSTANCE;
}
- public interface FlingConfig {
- method public void fling(androidx.animation.AnimatedFloat value, float startVelocity);
+ public interface DragValueController {
+ method public float getCurrentValue();
+ method public void onDrag(float target);
+ method public void onDragEnd(float velocity, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueSettled);
+ method public void setBounds(float min, float max);
+ property public abstract float currentValue;
+ }
+
+ public final class DraggableCallback {
+ ctor public DraggableCallback(kotlin.jvm.functions.Function0<kotlin.Unit> onDragStarted, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onDragSettled);
+ ctor public DraggableCallback();
+ }
+
+ public final class DraggableKt {
+ ctor public DraggableKt();
+ method public static void Draggable(androidx.ui.foundation.gestures.DragDirection dragDirection, float minValue = Float.MIN_VALUE, float maxValue = Float.MAX_VALUE, androidx.ui.foundation.gestures.DragValueController valueController = +memo(minValue, {
+ <init>(minValue)
+}), androidx.ui.foundation.gestures.DraggableCallback? callback = null, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> children);
+ }
+
+ public final class FloatDragValueController implements androidx.ui.foundation.gestures.DragValueController {
+ ctor public FloatDragValueController(float initialValue);
+ method public float getCurrentValue();
+ method public void onDrag(float target);
+ method public void onDragEnd(float velocity, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueSettled);
+ method public void setBounds(float min, float max);
+ property public float currentValue;
}
}
@@ -100,12 +141,36 @@
}
+package androidx.ui.foundation.semantics {
+
+ public final class FoundationSemanticsProperties {
+ method public androidx.ui.semantics.SemanticsPropertyKey<java.lang.Boolean> getInMutuallyExclusiveGroup();
+ method public androidx.ui.semantics.SemanticsPropertyKey<java.lang.Boolean> getSelected();
+ method public androidx.ui.semantics.SemanticsPropertyKey<androidx.ui.foundation.selection.ToggleableState> getToggleableState();
+ property public final androidx.ui.semantics.SemanticsPropertyKey<java.lang.Boolean> InMutuallyExclusiveGroup;
+ property public final androidx.ui.semantics.SemanticsPropertyKey<java.lang.Boolean> Selected;
+ property public final androidx.ui.semantics.SemanticsPropertyKey<androidx.ui.foundation.selection.ToggleableState> ToggleableState;
+ field public static final androidx.ui.foundation.semantics.FoundationSemanticsProperties! INSTANCE;
+ }
+
+ public final class FoundationSemanticsPropertiesKt {
+ ctor public FoundationSemanticsPropertiesKt();
+ method public static boolean getInMutuallyExclusiveGroup(androidx.ui.semantics.SemanticsPropertyReceiver);
+ method public static boolean getSelected(androidx.ui.semantics.SemanticsPropertyReceiver);
+ method public static androidx.ui.foundation.selection.ToggleableState getToggleableState(androidx.ui.semantics.SemanticsPropertyReceiver);
+ method public static void setInMutuallyExclusiveGroup(androidx.ui.semantics.SemanticsPropertyReceiver, boolean p);
+ method public static void setSelected(androidx.ui.semantics.SemanticsPropertyReceiver, boolean p);
+ method public static void setToggleableState(androidx.ui.semantics.SemanticsPropertyReceiver, androidx.ui.foundation.selection.ToggleableState p);
+ }
+
+}
+
package androidx.ui.foundation.shape {
public final class DrawShapeKt {
ctor public DrawShapeKt();
method public static void DrawShape(androidx.ui.engine.geometry.Shape shape, androidx.ui.graphics.Color color);
- method public static void DrawShape(androidx.ui.engine.geometry.Shape shape, androidx.ui.core.vectorgraphics.Brush brush);
+ method public static void DrawShape(androidx.ui.engine.geometry.Shape shape, androidx.ui.graphics.vectorgraphics.Brush brush);
}
public final class GenericShape implements androidx.ui.engine.geometry.Shape {
@@ -124,11 +189,11 @@
package androidx.ui.foundation.shape.border {
public final class Border {
- ctor public Border(androidx.ui.core.vectorgraphics.Brush brush, androidx.ui.core.Dp width);
- method public androidx.ui.core.vectorgraphics.Brush component1();
+ ctor public Border(androidx.ui.graphics.vectorgraphics.Brush brush, androidx.ui.core.Dp width);
+ method public androidx.ui.graphics.vectorgraphics.Brush component1();
method public androidx.ui.core.Dp component2();
- method public androidx.ui.foundation.shape.border.Border copy(androidx.ui.core.vectorgraphics.Brush brush, androidx.ui.core.Dp width);
- method public androidx.ui.core.vectorgraphics.Brush getBrush();
+ method public androidx.ui.foundation.shape.border.Border copy(androidx.ui.graphics.vectorgraphics.Brush brush, androidx.ui.core.Dp width);
+ method public androidx.ui.graphics.vectorgraphics.Brush getBrush();
method public androidx.ui.core.Dp getWidth();
}
diff --git a/ui/ui-foundation/api/restricted_1.0.0-alpha01.txt b/ui/ui-foundation/api/restricted_1.0.0-alpha01.txt
index 26ee3a8..3329533 100644
--- a/ui/ui-foundation/api/restricted_1.0.0-alpha01.txt
+++ b/ui/ui-foundation/api/restricted_1.0.0-alpha01.txt
@@ -8,7 +8,7 @@
public final class ColoredRectKt {
ctor public ColoredRectKt();
- method public static void ColoredRect(androidx.ui.core.vectorgraphics.Brush brush, androidx.ui.core.Dp? width = null, androidx.ui.core.Dp? height = null);
+ method public static void ColoredRect(androidx.ui.graphics.vectorgraphics.Brush brush, androidx.ui.core.Dp? width = null, androidx.ui.core.Dp? height = null);
method public static void ColoredRect(androidx.ui.graphics.Color color, androidx.ui.core.Dp? width = null, androidx.ui.core.Dp? height = null);
}
@@ -17,50 +17,66 @@
method public static void DeterminateProgressIndicator(@FloatRange(from=0.0, to=1.0) float progress, kotlin.jvm.functions.Function0<kotlin.Unit> children);
}
+ public final class DialogKt {
+ ctor public DialogKt();
+ method public static void Dialog(kotlin.jvm.functions.Function0<kotlin.Unit> onCloseRequest, kotlin.jvm.functions.Function0<kotlin.Unit> children);
+ }
+
public final class SimpleImageKt {
ctor public SimpleImageKt();
method public static void SimpleImage(androidx.ui.painting.Image image, androidx.ui.graphics.Color? tint = null);
}
+ public final class Strings {
+ method public String getChecked();
+ method public String getIndeterminate();
+ method public String getNotSelected();
+ method public String getSelected();
+ method public String getUnchecked();
+ property public final String Checked;
+ property public final String Indeterminate;
+ property public final String NotSelected;
+ property public final String Selected;
+ property public final String Unchecked;
+ field public static final androidx.ui.foundation.Strings! INSTANCE;
+ }
+
}
-package androidx.ui.foundation.gestures {
+package androidx.ui.foundation.animation {
- public final class AnchorsFlingConfig implements androidx.ui.foundation.gestures.FlingConfig {
- ctor public AnchorsFlingConfig(java.util.List<java.lang.Float> animationAnchors, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onAnimationFinished, androidx.animation.AnimationBuilder<java.lang.Float> animationBuilder, androidx.animation.DecayAnimation decayAnimation);
- method public java.util.List<java.lang.Float> component1();
+ public final class AnimatedFloatDragController implements androidx.ui.foundation.gestures.DragValueController {
+ ctor public AnimatedFloatDragController(float initialValue, androidx.ui.foundation.animation.FlingConfig? flingConfig);
+ method public androidx.animation.AnimatedFloat getAnimatedFloat();
+ method public float getCurrentValue();
+ method public void onDrag(float target);
+ method public void onDragEnd(float velocity, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueSettled);
+ method public void setBounds(float min, float max);
+ property public final androidx.animation.AnimatedFloat animatedFloat;
+ property public float currentValue;
+ }
+
+ public final class FlingConfig {
+ ctor public FlingConfig(androidx.animation.DecayAnimation decayAnimation, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onAnimationFinished, kotlin.jvm.functions.Function1<? super java.lang.Float,androidx.animation.TargetAnimation> adjustTarget);
+ ctor public FlingConfig();
+ method public androidx.animation.DecayAnimation component1();
method public kotlin.jvm.functions.Function2<java.lang.Float,java.lang.Boolean,kotlin.Unit>? component2();
- method public androidx.animation.AnimationBuilder<java.lang.Float> component3();
- method public androidx.animation.DecayAnimation component4();
- method public androidx.ui.foundation.gestures.AnchorsFlingConfig copy(java.util.List<java.lang.Float> animationAnchors, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onAnimationFinished, androidx.animation.AnimationBuilder<java.lang.Float> animationBuilder, androidx.animation.DecayAnimation decayAnimation);
- method public void fling(androidx.animation.AnimatedFloat value, float startVelocity);
- method public java.util.List<java.lang.Float> getAnimationAnchors();
- method public androidx.animation.AnimationBuilder<java.lang.Float> getAnimationBuilder();
+ method public kotlin.jvm.functions.Function1<java.lang.Float,androidx.animation.TargetAnimation> component3();
+ method public androidx.ui.foundation.animation.FlingConfig copy(androidx.animation.DecayAnimation decayAnimation, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onAnimationFinished, kotlin.jvm.functions.Function1<? super java.lang.Float,androidx.animation.TargetAnimation> adjustTarget);
+ method public kotlin.jvm.functions.Function1<java.lang.Float,androidx.animation.TargetAnimation> getAdjustTarget();
method public androidx.animation.DecayAnimation getDecayAnimation();
method public kotlin.jvm.functions.Function2<java.lang.Float,java.lang.Boolean,kotlin.Unit>? getOnAnimationFinished();
}
- public final class AnimatedDraggableKt {
- ctor public AnimatedDraggableKt();
- method public static void AnimatedDraggable(androidx.ui.foundation.gestures.DragDirection dragDirection, float startValue, float minValue = Float.MIN_VALUE, float maxValue = Float.MAX_VALUE, androidx.ui.foundation.gestures.FlingConfig? flingConfig = null, kotlin.jvm.functions.Function1<? super androidx.animation.BaseAnimatedValue<java.lang.Float>,kotlin.Unit> children);
+ public final class FlingConfigKt {
+ ctor public FlingConfigKt();
+ method public static androidx.ui.foundation.animation.FlingConfig AnchorsFlingConfig(java.util.List<java.lang.Float> anchors, androidx.animation.AnimationBuilder<java.lang.Float> animationBuilder = PhysicsBuilder(), kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onAnimationFinished = null, androidx.animation.DecayAnimation decayAnimation = ExponentialDecay());
+ method public static void fling(androidx.animation.AnimatedFloat, androidx.ui.foundation.animation.FlingConfig config, float startVelocity);
}
- public final class DecayFlingConfig implements androidx.ui.foundation.gestures.FlingConfig {
- ctor public DecayFlingConfig(androidx.animation.DecayAnimation decayAnimation, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onFlingFinished, kotlin.jvm.functions.Function1<? super java.lang.Float,androidx.animation.TargetAnimation> adjustTarget);
- method public androidx.animation.DecayAnimation component1();
- method public kotlin.jvm.functions.Function2<java.lang.Float,java.lang.Boolean,kotlin.Unit>? component2();
- method public kotlin.jvm.functions.Function1<java.lang.Float,androidx.animation.TargetAnimation> component3();
- method public androidx.ui.foundation.gestures.DecayFlingConfig copy(androidx.animation.DecayAnimation decayAnimation, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onFlingFinished, kotlin.jvm.functions.Function1<? super java.lang.Float,androidx.animation.TargetAnimation> adjustTarget);
- method public void fling(androidx.animation.AnimatedFloat value, float startVelocity);
- method public kotlin.jvm.functions.Function1<java.lang.Float,androidx.animation.TargetAnimation> getAdjustTarget();
- method public androidx.animation.DecayAnimation getDecayAnimation();
- method public kotlin.jvm.functions.Function2<java.lang.Float,java.lang.Boolean,kotlin.Unit>? getOnFlingFinished();
- }
+}
- public final class DefaultFlingConfig implements androidx.ui.foundation.gestures.FlingConfig {
- method public void fling(androidx.animation.AnimatedFloat value, float startVelocity);
- field public static final androidx.ui.foundation.gestures.DefaultFlingConfig! INSTANCE;
- }
+package androidx.ui.foundation.gestures {
public abstract sealed class DragDirection {
}
@@ -73,8 +89,33 @@
field public static final androidx.ui.foundation.gestures.DragDirection.Vertical! INSTANCE;
}
- public interface FlingConfig {
- method public void fling(androidx.animation.AnimatedFloat value, float startVelocity);
+ public interface DragValueController {
+ method public float getCurrentValue();
+ method public void onDrag(float target);
+ method public void onDragEnd(float velocity, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueSettled);
+ method public void setBounds(float min, float max);
+ property public abstract float currentValue;
+ }
+
+ public final class DraggableCallback {
+ ctor public DraggableCallback(kotlin.jvm.functions.Function0<kotlin.Unit> onDragStarted, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onDragSettled);
+ ctor public DraggableCallback();
+ }
+
+ public final class DraggableKt {
+ ctor public DraggableKt();
+ method public static void Draggable(androidx.ui.foundation.gestures.DragDirection dragDirection, float minValue = Float.MIN_VALUE, float maxValue = Float.MAX_VALUE, androidx.ui.foundation.gestures.DragValueController valueController = +memo(minValue, {
+ <init>(minValue)
+}), androidx.ui.foundation.gestures.DraggableCallback? callback = null, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> children);
+ }
+
+ public final class FloatDragValueController implements androidx.ui.foundation.gestures.DragValueController {
+ ctor public FloatDragValueController(float initialValue);
+ method public float getCurrentValue();
+ method public void onDrag(float target);
+ method public void onDragEnd(float velocity, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueSettled);
+ method public void setBounds(float min, float max);
+ property public float currentValue;
}
}
@@ -100,12 +141,36 @@
}
+package androidx.ui.foundation.semantics {
+
+ public final class FoundationSemanticsProperties {
+ method public androidx.ui.semantics.SemanticsPropertyKey<java.lang.Boolean> getInMutuallyExclusiveGroup();
+ method public androidx.ui.semantics.SemanticsPropertyKey<java.lang.Boolean> getSelected();
+ method public androidx.ui.semantics.SemanticsPropertyKey<androidx.ui.foundation.selection.ToggleableState> getToggleableState();
+ property public final androidx.ui.semantics.SemanticsPropertyKey<java.lang.Boolean> InMutuallyExclusiveGroup;
+ property public final androidx.ui.semantics.SemanticsPropertyKey<java.lang.Boolean> Selected;
+ property public final androidx.ui.semantics.SemanticsPropertyKey<androidx.ui.foundation.selection.ToggleableState> ToggleableState;
+ field public static final androidx.ui.foundation.semantics.FoundationSemanticsProperties! INSTANCE;
+ }
+
+ public final class FoundationSemanticsPropertiesKt {
+ ctor public FoundationSemanticsPropertiesKt();
+ method public static boolean getInMutuallyExclusiveGroup(androidx.ui.semantics.SemanticsPropertyReceiver);
+ method public static boolean getSelected(androidx.ui.semantics.SemanticsPropertyReceiver);
+ method public static androidx.ui.foundation.selection.ToggleableState getToggleableState(androidx.ui.semantics.SemanticsPropertyReceiver);
+ method public static void setInMutuallyExclusiveGroup(androidx.ui.semantics.SemanticsPropertyReceiver, boolean p);
+ method public static void setSelected(androidx.ui.semantics.SemanticsPropertyReceiver, boolean p);
+ method public static void setToggleableState(androidx.ui.semantics.SemanticsPropertyReceiver, androidx.ui.foundation.selection.ToggleableState p);
+ }
+
+}
+
package androidx.ui.foundation.shape {
public final class DrawShapeKt {
ctor public DrawShapeKt();
method public static void DrawShape(androidx.ui.engine.geometry.Shape shape, androidx.ui.graphics.Color color);
- method public static void DrawShape(androidx.ui.engine.geometry.Shape shape, androidx.ui.core.vectorgraphics.Brush brush);
+ method public static void DrawShape(androidx.ui.engine.geometry.Shape shape, androidx.ui.graphics.vectorgraphics.Brush brush);
}
public final class GenericShape implements androidx.ui.engine.geometry.Shape {
@@ -124,11 +189,11 @@
package androidx.ui.foundation.shape.border {
public final class Border {
- ctor public Border(androidx.ui.core.vectorgraphics.Brush brush, androidx.ui.core.Dp width);
- method public androidx.ui.core.vectorgraphics.Brush component1();
+ ctor public Border(androidx.ui.graphics.vectorgraphics.Brush brush, androidx.ui.core.Dp width);
+ method public androidx.ui.graphics.vectorgraphics.Brush component1();
method public androidx.ui.core.Dp component2();
- method public androidx.ui.foundation.shape.border.Border copy(androidx.ui.core.vectorgraphics.Brush brush, androidx.ui.core.Dp width);
- method public androidx.ui.core.vectorgraphics.Brush getBrush();
+ method public androidx.ui.foundation.shape.border.Border copy(androidx.ui.graphics.vectorgraphics.Brush brush, androidx.ui.core.Dp width);
+ method public androidx.ui.graphics.vectorgraphics.Brush getBrush();
method public androidx.ui.core.Dp getWidth();
}
diff --git a/ui/ui-foundation/api/restricted_current.txt b/ui/ui-foundation/api/restricted_current.txt
index 26ee3a8..3329533 100644
--- a/ui/ui-foundation/api/restricted_current.txt
+++ b/ui/ui-foundation/api/restricted_current.txt
@@ -8,7 +8,7 @@
public final class ColoredRectKt {
ctor public ColoredRectKt();
- method public static void ColoredRect(androidx.ui.core.vectorgraphics.Brush brush, androidx.ui.core.Dp? width = null, androidx.ui.core.Dp? height = null);
+ method public static void ColoredRect(androidx.ui.graphics.vectorgraphics.Brush brush, androidx.ui.core.Dp? width = null, androidx.ui.core.Dp? height = null);
method public static void ColoredRect(androidx.ui.graphics.Color color, androidx.ui.core.Dp? width = null, androidx.ui.core.Dp? height = null);
}
@@ -17,50 +17,66 @@
method public static void DeterminateProgressIndicator(@FloatRange(from=0.0, to=1.0) float progress, kotlin.jvm.functions.Function0<kotlin.Unit> children);
}
+ public final class DialogKt {
+ ctor public DialogKt();
+ method public static void Dialog(kotlin.jvm.functions.Function0<kotlin.Unit> onCloseRequest, kotlin.jvm.functions.Function0<kotlin.Unit> children);
+ }
+
public final class SimpleImageKt {
ctor public SimpleImageKt();
method public static void SimpleImage(androidx.ui.painting.Image image, androidx.ui.graphics.Color? tint = null);
}
+ public final class Strings {
+ method public String getChecked();
+ method public String getIndeterminate();
+ method public String getNotSelected();
+ method public String getSelected();
+ method public String getUnchecked();
+ property public final String Checked;
+ property public final String Indeterminate;
+ property public final String NotSelected;
+ property public final String Selected;
+ property public final String Unchecked;
+ field public static final androidx.ui.foundation.Strings! INSTANCE;
+ }
+
}
-package androidx.ui.foundation.gestures {
+package androidx.ui.foundation.animation {
- public final class AnchorsFlingConfig implements androidx.ui.foundation.gestures.FlingConfig {
- ctor public AnchorsFlingConfig(java.util.List<java.lang.Float> animationAnchors, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onAnimationFinished, androidx.animation.AnimationBuilder<java.lang.Float> animationBuilder, androidx.animation.DecayAnimation decayAnimation);
- method public java.util.List<java.lang.Float> component1();
+ public final class AnimatedFloatDragController implements androidx.ui.foundation.gestures.DragValueController {
+ ctor public AnimatedFloatDragController(float initialValue, androidx.ui.foundation.animation.FlingConfig? flingConfig);
+ method public androidx.animation.AnimatedFloat getAnimatedFloat();
+ method public float getCurrentValue();
+ method public void onDrag(float target);
+ method public void onDragEnd(float velocity, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueSettled);
+ method public void setBounds(float min, float max);
+ property public final androidx.animation.AnimatedFloat animatedFloat;
+ property public float currentValue;
+ }
+
+ public final class FlingConfig {
+ ctor public FlingConfig(androidx.animation.DecayAnimation decayAnimation, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onAnimationFinished, kotlin.jvm.functions.Function1<? super java.lang.Float,androidx.animation.TargetAnimation> adjustTarget);
+ ctor public FlingConfig();
+ method public androidx.animation.DecayAnimation component1();
method public kotlin.jvm.functions.Function2<java.lang.Float,java.lang.Boolean,kotlin.Unit>? component2();
- method public androidx.animation.AnimationBuilder<java.lang.Float> component3();
- method public androidx.animation.DecayAnimation component4();
- method public androidx.ui.foundation.gestures.AnchorsFlingConfig copy(java.util.List<java.lang.Float> animationAnchors, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onAnimationFinished, androidx.animation.AnimationBuilder<java.lang.Float> animationBuilder, androidx.animation.DecayAnimation decayAnimation);
- method public void fling(androidx.animation.AnimatedFloat value, float startVelocity);
- method public java.util.List<java.lang.Float> getAnimationAnchors();
- method public androidx.animation.AnimationBuilder<java.lang.Float> getAnimationBuilder();
+ method public kotlin.jvm.functions.Function1<java.lang.Float,androidx.animation.TargetAnimation> component3();
+ method public androidx.ui.foundation.animation.FlingConfig copy(androidx.animation.DecayAnimation decayAnimation, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onAnimationFinished, kotlin.jvm.functions.Function1<? super java.lang.Float,androidx.animation.TargetAnimation> adjustTarget);
+ method public kotlin.jvm.functions.Function1<java.lang.Float,androidx.animation.TargetAnimation> getAdjustTarget();
method public androidx.animation.DecayAnimation getDecayAnimation();
method public kotlin.jvm.functions.Function2<java.lang.Float,java.lang.Boolean,kotlin.Unit>? getOnAnimationFinished();
}
- public final class AnimatedDraggableKt {
- ctor public AnimatedDraggableKt();
- method public static void AnimatedDraggable(androidx.ui.foundation.gestures.DragDirection dragDirection, float startValue, float minValue = Float.MIN_VALUE, float maxValue = Float.MAX_VALUE, androidx.ui.foundation.gestures.FlingConfig? flingConfig = null, kotlin.jvm.functions.Function1<? super androidx.animation.BaseAnimatedValue<java.lang.Float>,kotlin.Unit> children);
+ public final class FlingConfigKt {
+ ctor public FlingConfigKt();
+ method public static androidx.ui.foundation.animation.FlingConfig AnchorsFlingConfig(java.util.List<java.lang.Float> anchors, androidx.animation.AnimationBuilder<java.lang.Float> animationBuilder = PhysicsBuilder(), kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onAnimationFinished = null, androidx.animation.DecayAnimation decayAnimation = ExponentialDecay());
+ method public static void fling(androidx.animation.AnimatedFloat, androidx.ui.foundation.animation.FlingConfig config, float startVelocity);
}
- public final class DecayFlingConfig implements androidx.ui.foundation.gestures.FlingConfig {
- ctor public DecayFlingConfig(androidx.animation.DecayAnimation decayAnimation, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onFlingFinished, kotlin.jvm.functions.Function1<? super java.lang.Float,androidx.animation.TargetAnimation> adjustTarget);
- method public androidx.animation.DecayAnimation component1();
- method public kotlin.jvm.functions.Function2<java.lang.Float,java.lang.Boolean,kotlin.Unit>? component2();
- method public kotlin.jvm.functions.Function1<java.lang.Float,androidx.animation.TargetAnimation> component3();
- method public androidx.ui.foundation.gestures.DecayFlingConfig copy(androidx.animation.DecayAnimation decayAnimation, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Boolean,kotlin.Unit>? onFlingFinished, kotlin.jvm.functions.Function1<? super java.lang.Float,androidx.animation.TargetAnimation> adjustTarget);
- method public void fling(androidx.animation.AnimatedFloat value, float startVelocity);
- method public kotlin.jvm.functions.Function1<java.lang.Float,androidx.animation.TargetAnimation> getAdjustTarget();
- method public androidx.animation.DecayAnimation getDecayAnimation();
- method public kotlin.jvm.functions.Function2<java.lang.Float,java.lang.Boolean,kotlin.Unit>? getOnFlingFinished();
- }
+}
- public final class DefaultFlingConfig implements androidx.ui.foundation.gestures.FlingConfig {
- method public void fling(androidx.animation.AnimatedFloat value, float startVelocity);
- field public static final androidx.ui.foundation.gestures.DefaultFlingConfig! INSTANCE;
- }
+package androidx.ui.foundation.gestures {
public abstract sealed class DragDirection {
}
@@ -73,8 +89,33 @@
field public static final androidx.ui.foundation.gestures.DragDirection.Vertical! INSTANCE;
}
- public interface FlingConfig {
- method public void fling(androidx.animation.AnimatedFloat value, float startVelocity);
+ public interface DragValueController {
+ method public float getCurrentValue();
+ method public void onDrag(float target);
+ method public void onDragEnd(float velocity, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueSettled);
+ method public void setBounds(float min, float max);
+ property public abstract float currentValue;
+ }
+
+ public final class DraggableCallback {
+ ctor public DraggableCallback(kotlin.jvm.functions.Function0<kotlin.Unit> onDragStarted, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onDragSettled);
+ ctor public DraggableCallback();
+ }
+
+ public final class DraggableKt {
+ ctor public DraggableKt();
+ method public static void Draggable(androidx.ui.foundation.gestures.DragDirection dragDirection, float minValue = Float.MIN_VALUE, float maxValue = Float.MAX_VALUE, androidx.ui.foundation.gestures.DragValueController valueController = +memo(minValue, {
+ <init>(minValue)
+}), androidx.ui.foundation.gestures.DraggableCallback? callback = null, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> children);
+ }
+
+ public final class FloatDragValueController implements androidx.ui.foundation.gestures.DragValueController {
+ ctor public FloatDragValueController(float initialValue);
+ method public float getCurrentValue();
+ method public void onDrag(float target);
+ method public void onDragEnd(float velocity, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueSettled);
+ method public void setBounds(float min, float max);
+ property public float currentValue;
}
}
@@ -100,12 +141,36 @@
}
+package androidx.ui.foundation.semantics {
+
+ public final class FoundationSemanticsProperties {
+ method public androidx.ui.semantics.SemanticsPropertyKey<java.lang.Boolean> getInMutuallyExclusiveGroup();
+ method public androidx.ui.semantics.SemanticsPropertyKey<java.lang.Boolean> getSelected();
+ method public androidx.ui.semantics.SemanticsPropertyKey<androidx.ui.foundation.selection.ToggleableState> getToggleableState();
+ property public final androidx.ui.semantics.SemanticsPropertyKey<java.lang.Boolean> InMutuallyExclusiveGroup;
+ property public final androidx.ui.semantics.SemanticsPropertyKey<java.lang.Boolean> Selected;
+ property public final androidx.ui.semantics.SemanticsPropertyKey<androidx.ui.foundation.selection.ToggleableState> ToggleableState;
+ field public static final androidx.ui.foundation.semantics.FoundationSemanticsProperties! INSTANCE;
+ }
+
+ public final class FoundationSemanticsPropertiesKt {
+ ctor public FoundationSemanticsPropertiesKt();
+ method public static boolean getInMutuallyExclusiveGroup(androidx.ui.semantics.SemanticsPropertyReceiver);
+ method public static boolean getSelected(androidx.ui.semantics.SemanticsPropertyReceiver);
+ method public static androidx.ui.foundation.selection.ToggleableState getToggleableState(androidx.ui.semantics.SemanticsPropertyReceiver);
+ method public static void setInMutuallyExclusiveGroup(androidx.ui.semantics.SemanticsPropertyReceiver, boolean p);
+ method public static void setSelected(androidx.ui.semantics.SemanticsPropertyReceiver, boolean p);
+ method public static void setToggleableState(androidx.ui.semantics.SemanticsPropertyReceiver, androidx.ui.foundation.selection.ToggleableState p);
+ }
+
+}
+
package androidx.ui.foundation.shape {
public final class DrawShapeKt {
ctor public DrawShapeKt();
method public static void DrawShape(androidx.ui.engine.geometry.Shape shape, androidx.ui.graphics.Color color);
- method public static void DrawShape(androidx.ui.engine.geometry.Shape shape, androidx.ui.core.vectorgraphics.Brush brush);
+ method public static void DrawShape(androidx.ui.engine.geometry.Shape shape, androidx.ui.graphics.vectorgraphics.Brush brush);
}
public final class GenericShape implements androidx.ui.engine.geometry.Shape {
@@ -124,11 +189,11 @@
package androidx.ui.foundation.shape.border {
public final class Border {
- ctor public Border(androidx.ui.core.vectorgraphics.Brush brush, androidx.ui.core.Dp width);
- method public androidx.ui.core.vectorgraphics.Brush component1();
+ ctor public Border(androidx.ui.graphics.vectorgraphics.Brush brush, androidx.ui.core.Dp width);
+ method public androidx.ui.graphics.vectorgraphics.Brush component1();
method public androidx.ui.core.Dp component2();
- method public androidx.ui.foundation.shape.border.Border copy(androidx.ui.core.vectorgraphics.Brush brush, androidx.ui.core.Dp width);
- method public androidx.ui.core.vectorgraphics.Brush getBrush();
+ method public androidx.ui.foundation.shape.border.Border copy(androidx.ui.graphics.vectorgraphics.Brush brush, androidx.ui.core.Dp width);
+ method public androidx.ui.graphics.vectorgraphics.Brush getBrush();
method public androidx.ui.core.Dp getWidth();
}
diff --git a/ui/ui-foundation/build.gradle b/ui/ui-foundation/build.gradle
index e2568bd..105aab3 100644
--- a/ui/ui-foundation/build.gradle
+++ b/ui/ui-foundation/build.gradle
@@ -40,6 +40,7 @@
implementation project(":ui:ui-animation")
implementation project(':ui:ui-framework')
implementation project(':ui:ui-layout')
+ implementation project(':ui:ui-platform')
implementation project(':ui:ui-text')
testImplementation(ANDROIDX_TEST_RULES)
@@ -48,6 +49,7 @@
androidTestImplementation project(':ui:ui-test')
+ androidTestImplementation(ANDROIDX_TEST_UIAUTOMATOR)
androidTestImplementation(ANDROIDX_TEST_RULES)
androidTestImplementation(ANDROIDX_TEST_RUNNER)
androidTestImplementation(JUNIT)
diff --git a/ui/ui-foundation/integration-tests/foundation-demos/src/main/java/androidx/ui/foundation/demos/AnimatedDraggableActivity.kt b/ui/ui-foundation/integration-tests/foundation-demos/src/main/java/androidx/ui/foundation/demos/AnimatedDraggableActivity.kt
index d33545a..5f277c2 100644
--- a/ui/ui-foundation/integration-tests/foundation-demos/src/main/java/androidx/ui/foundation/demos/AnimatedDraggableActivity.kt
+++ b/ui/ui-foundation/integration-tests/foundation-demos/src/main/java/androidx/ui/foundation/demos/AnimatedDraggableActivity.kt
@@ -19,8 +19,12 @@
import android.app.Activity
import android.os.Bundle
import androidx.compose.composer
+import androidx.ui.core.dp
import androidx.ui.core.setContent
-import androidx.ui.foundation.samples.AnimatedDraggableSample
+import androidx.ui.foundation.samples.AnchoredDraggableSample
+import androidx.ui.foundation.samples.DraggableSample
+import androidx.ui.layout.Column
+import androidx.ui.layout.HeightSpacer
import androidx.ui.layout.Wrap
class AnimatedDraggableActivity : Activity() {
@@ -29,7 +33,11 @@
super.onCreate(savedInstanceState)
setContent {
Wrap {
- AnimatedDraggableSample()
+ Column {
+ DraggableSample()
+ HeightSpacer(100.dp)
+ AnchoredDraggableSample()
+ }
}
}
}
diff --git a/ui/ui-foundation/integration-tests/samples/src/main/java/androidx/ui/foundation/samples/AnimatedDraggableSamples.kt b/ui/ui-foundation/integration-tests/samples/src/main/java/androidx/ui/foundation/samples/AnimatedDraggableSamples.kt
deleted file mode 100644
index 3714185..0000000
--- a/ui/ui-foundation/integration-tests/samples/src/main/java/androidx/ui/foundation/samples/AnimatedDraggableSamples.kt
+++ /dev/null
@@ -1,72 +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.ui.foundation.samples
-
-import androidx.annotation.Sampled
-import androidx.compose.Composable
-import androidx.compose.composer
-import androidx.compose.unaryPlus
-import androidx.ui.core.dp
-import androidx.ui.core.withDensity
-import androidx.ui.foundation.ColoredRect
-import androidx.ui.foundation.gestures.AnchorsFlingConfig
-import androidx.ui.foundation.gestures.AnimatedDraggable
-import androidx.ui.foundation.gestures.DragDirection
-import androidx.ui.foundation.shape.DrawShape
-import androidx.ui.foundation.shape.RectangleShape
-import androidx.ui.graphics.Color
-import androidx.ui.layout.Alignment
-import androidx.ui.layout.Container
-import androidx.ui.layout.Padding
-
-@Sampled
-@Composable
-fun AnimatedDraggableSample() {
- // Composable that users can drag over 300 dp. There are 3 anchors
- // and the value will gravitate to 0, 150 or 300 dp
- val max = 300.dp
- val min = 0.dp
- val (minPx, maxPx) = +withDensity {
- min.toPx().value to max.toPx().value
- }
-
- AnimatedDraggable(
- dragDirection = DragDirection.Horizontal,
- startValue = minPx,
- minValue = minPx,
- maxValue = maxPx,
- // Specify an anchored behavior for the fling with anchors at max, min and center.
- flingConfig = AnchorsFlingConfig(listOf(minPx, maxPx / 2, maxPx))
- ) { dragValue ->
-
- // dragValue is the AnimatedFloat with current value in progress
- // of dragging or animating
- val draggedDp = +withDensity {
- dragValue.value.toDp()
- }
- val squareSize = 50.dp
-
- // Draw a seekbar-like widget that has a black background
- // with a red square that moves along the drag
- Container(width = max + squareSize, alignment = Alignment.CenterLeft) {
- DrawShape(RectangleShape, Color.Black)
- Padding(left = draggedDp) {
- ColoredRect(Color.Red, width = squareSize, height = squareSize)
- }
- }
- }
-}
\ No newline at end of file
diff --git a/ui/ui-foundation/integration-tests/samples/src/main/java/androidx/ui/foundation/samples/ColoredRectSamples.kt b/ui/ui-foundation/integration-tests/samples/src/main/java/androidx/ui/foundation/samples/ColoredRectSamples.kt
index d90e8ac3..5a688a7 100644
--- a/ui/ui-foundation/integration-tests/samples/src/main/java/androidx/ui/foundation/samples/ColoredRectSamples.kt
+++ b/ui/ui-foundation/integration-tests/samples/src/main/java/androidx/ui/foundation/samples/ColoredRectSamples.kt
@@ -20,9 +20,9 @@
import androidx.compose.Composable
import androidx.compose.composer
import androidx.ui.core.dp
-import androidx.ui.core.vectorgraphics.SolidColor
import androidx.ui.foundation.ColoredRect
import androidx.ui.graphics.Color
+import androidx.ui.graphics.vectorgraphics.SolidColor
@Sampled
@Composable
diff --git a/ui/ui-foundation/integration-tests/samples/src/main/java/androidx/ui/foundation/samples/DialogSample.kt b/ui/ui-foundation/integration-tests/samples/src/main/java/androidx/ui/foundation/samples/DialogSample.kt
new file mode 100644
index 0000000..2c01062c
--- /dev/null
+++ b/ui/ui-foundation/integration-tests/samples/src/main/java/androidx/ui/foundation/samples/DialogSample.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.ui.foundation.samples
+
+import androidx.annotation.Sampled
+import androidx.compose.Composable
+import androidx.compose.composer
+import androidx.compose.state
+import androidx.compose.unaryPlus
+import androidx.ui.core.Text
+import androidx.ui.foundation.Dialog
+
+@Sampled
+@Composable
+fun DialogSample() {
+ val openDialog = +state { true }
+
+ if (openDialog.value) {
+ Dialog(onCloseRequest = { openDialog.value = false }) {
+ Text("This is a Dialog. Click outside to dismiss.")
+ }
+ }
+}
\ No newline at end of file
diff --git a/ui/ui-foundation/integration-tests/samples/src/main/java/androidx/ui/foundation/samples/DraggableSamples.kt b/ui/ui-foundation/integration-tests/samples/src/main/java/androidx/ui/foundation/samples/DraggableSamples.kt
new file mode 100644
index 0000000..8706d04
--- /dev/null
+++ b/ui/ui-foundation/integration-tests/samples/src/main/java/androidx/ui/foundation/samples/DraggableSamples.kt
@@ -0,0 +1,102 @@
+/*
+ * 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.ui.foundation.samples
+
+import androidx.annotation.Sampled
+import androidx.compose.Composable
+import androidx.compose.composer
+import androidx.compose.memo
+import androidx.compose.unaryPlus
+import androidx.ui.animation.animatedFloat
+import androidx.ui.core.dp
+import androidx.ui.core.withDensity
+import androidx.ui.foundation.ColoredRect
+import androidx.ui.foundation.animation.AnchorsFlingConfig
+import androidx.ui.foundation.animation.AnimatedFloatDragController
+import androidx.ui.foundation.gestures.DragDirection
+import androidx.ui.foundation.gestures.Draggable
+import androidx.ui.foundation.shape.DrawShape
+import androidx.ui.foundation.shape.RectangleShape
+import androidx.ui.graphics.Color
+import androidx.ui.layout.Alignment
+import androidx.ui.layout.Container
+import androidx.ui.layout.Padding
+
+@Sampled
+@Composable
+fun DraggableSample() {
+ // Composable that users can drag over 300 dp.
+ val max = 300.dp
+ val min = 0.dp
+ val (minPx, maxPx) = +withDensity {
+ min.toPx().value to max.toPx().value
+ }
+ Draggable(DragDirection.Horizontal, minPx, maxPx) { dragValue ->
+ // dragValue is the current value in progress of dragging
+ val draggedDp = +withDensity {
+ dragValue.toDp()
+ }
+ val squareSize = 50.dp
+
+ // Draw a seekbar-like widget that has a black background
+ // with a red square that moves along the drag
+ Container(width = max + squareSize, alignment = Alignment.CenterLeft) {
+ DrawShape(RectangleShape, Color.Black)
+ Padding(left = draggedDp) {
+ ColoredRect(Color.Red, width = squareSize, height = squareSize)
+ }
+ }
+ }
+}
+
+@Sampled
+@Composable
+fun AnchoredDraggableSample() {
+ // Composable that users can drag over 300 dp. There are 3 anchors
+ // and the value will gravitate to 0, 150 or 300 dp
+ val max = 300.dp
+ val min = 0.dp
+ val (minPx, maxPx) = +withDensity {
+ min.toPx().value to max.toPx().value
+ }
+ // define anchors and related animation controller
+ val anchors = listOf(minPx, maxPx, maxPx / 2)
+ val flingConfig = +memo { AnchorsFlingConfig(anchors) }
+ val dragController = +memo { AnimatedFloatDragController(minPx, flingConfig) }
+
+ Draggable(
+ dragDirection = DragDirection.Horizontal,
+ valueController = dragController,
+ minValue = minPx,
+ maxValue = maxPx
+ ) { dragValue ->
+ // dragValue is the current value in progress
+ // of dragging or animation
+ val draggedDp = +withDensity {
+ dragValue.toDp()
+ }
+ val squareSize = 50.dp
+ // Draw a seekbar-like widget that has a black background
+ // with a red square that moves along the drag
+ Container(width = max + squareSize, alignment = Alignment.CenterLeft) {
+ DrawShape(RectangleShape, Color.Black)
+ Padding(left = draggedDp) {
+ ColoredRect(Color.Red, width = squareSize, height = squareSize)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ui/ui-foundation/src/androidTest/java/androidx/ui/foundation/ClickableTest.kt b/ui/ui-foundation/src/androidTest/java/androidx/ui/foundation/ClickableTest.kt
index 920868f..2f7e1c3 100644
--- a/ui/ui-foundation/src/androidTest/java/androidx/ui/foundation/ClickableTest.kt
+++ b/ui/ui-foundation/src/androidTest/java/androidx/ui/foundation/ClickableTest.kt
@@ -54,8 +54,7 @@
findByTag("myClickable")
.assertSemanticsIsEqualTo(
createFullSemantics(
- isEnabled = true,
- isButton = true
+ isEnabled = true
)
)
}
@@ -75,8 +74,7 @@
findByTag("myClickable")
.assertSemanticsIsEqualTo(
createFullSemantics(
- isEnabled = false,
- isButton = true
+ isEnabled = false
)
)
}
diff --git a/ui/ui-foundation/src/androidTest/java/androidx/ui/foundation/DialogUiTest.kt b/ui/ui-foundation/src/androidTest/java/androidx/ui/foundation/DialogUiTest.kt
new file mode 100644
index 0000000..763e71c
--- /dev/null
+++ b/ui/ui-foundation/src/androidTest/java/androidx/ui/foundation/DialogUiTest.kt
@@ -0,0 +1,173 @@
+/*
+ * 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.ui.foundation
+
+import androidx.test.filters.MediumTest
+import androidx.ui.test.createComposeRule
+import androidx.compose.composer
+import androidx.compose.state
+import androidx.compose.unaryPlus
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import androidx.test.uiautomator.UiDevice
+import androidx.ui.core.Text
+import androidx.ui.semantics.accessibilityLabel
+import androidx.ui.test.assertDoesNotExist
+import androidx.ui.test.assertIsVisible
+import androidx.ui.test.doClick
+import androidx.ui.test.findByText
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@MediumTest
+@RunWith(JUnit4::class)
+class DialogUiTest {
+ @get:Rule
+ val composeTestRule = createComposeRule(disableTransitions = true)
+
+ private val defaultText = "dialogText"
+
+ @Test
+ fun dialogTest_isShowingContent() {
+ composeTestRule.setContent {
+ val showDialog = +state { true }
+
+ if (showDialog.value) {
+ Dialog(onCloseRequest = {}) {
+ Text(defaultText)
+ }
+ }
+ }
+
+ findByText(defaultText).assertIsVisible()
+ }
+
+ @Test
+ fun dialogTest_isNotDismissed_whenClicked() {
+ val textBeforeClick = "textBeforeClick"
+ val textAfterClick = "textAfterClick"
+
+ composeTestRule.setContent {
+ val showDialog = +state { true }
+ val text = +state { textBeforeClick }
+
+ if (showDialog.value) {
+ Dialog(onCloseRequest = {
+ showDialog.value = false
+ }) {
+ Clickable(onClick = { text.value = textAfterClick }) {
+ Text(text = text.value)
+ }
+ }
+ }
+ }
+
+ findByText(textBeforeClick).assertIsVisible()
+
+ // Click inside the dialog
+ findByText(textBeforeClick).doClick()
+
+ // Check that the Clickable was pressed and that the Dialog is still visible
+ findByText(textAfterClick).assertIsVisible()
+ }
+
+ @Test
+ fun dialogTest_isDismissed_whenSpecified() {
+ composeTestRule.setContent {
+ val showDialog = +state { true }
+
+ if (showDialog.value) {
+ Dialog(onCloseRequest = { showDialog.value = false }) {
+ Text(defaultText)
+ }
+ }
+ }
+
+ findByText(defaultText).assertIsVisible()
+
+ // Click outside the dialog to dismiss it
+ val outsideX = 0
+ val outsideY = composeTestRule.displayMetrics.heightPixels / 2
+ UiDevice.getInstance(getInstrumentation()).click(outsideX, outsideY)
+
+ assertDoesNotExist { accessibilityLabel == defaultText }
+ }
+
+ @Test
+ fun dialogTest_isNotDismissed_whenNotSpecified() {
+ composeTestRule.setContent {
+ val showDialog = +state { true }
+
+ if (showDialog.value) {
+ Dialog(onCloseRequest = {}) {
+ Text(defaultText)
+ }
+ }
+ }
+
+ findByText(defaultText).assertIsVisible()
+
+ // Click outside the dialog to try to dismiss it
+ val outsideX = 0
+ val outsideY = composeTestRule.displayMetrics.heightPixels / 2
+ UiDevice.getInstance(getInstrumentation()).click(outsideX, outsideY)
+
+ // The Dialog should still be visible
+ findByText(defaultText).assertIsVisible()
+ }
+
+ @Test
+ fun dialogTest_isDismissed_whenSpecified_backButtonPressed() {
+ composeTestRule.setContent {
+ val showDialog = +state { true }
+
+ if (showDialog.value) {
+ Dialog(onCloseRequest = { showDialog.value = false }) {
+ Text(defaultText)
+ }
+ }
+ }
+
+ findByText(defaultText).assertIsVisible()
+
+ // Click the back button to dismiss the Dialog
+ UiDevice.getInstance(getInstrumentation()).pressBack()
+
+ assertDoesNotExist { accessibilityLabel == defaultText }
+ }
+
+ @Test
+ fun dialogTest_isNotDismissed_whenNotSpecified_backButtonPressed() {
+ composeTestRule.setContent {
+ val showDialog = +state { true }
+
+ if (showDialog.value) {
+ Dialog(onCloseRequest = {}) {
+ Text(defaultText)
+ }
+ }
+ }
+
+ findByText(defaultText).assertIsVisible()
+
+ // Click the back button to try to dismiss the dialog
+ UiDevice.getInstance(getInstrumentation()).pressBack()
+
+ // The Dialog should still be visible
+ findByText(defaultText).assertIsVisible()
+ }
+}
\ No newline at end of file
diff --git a/ui/ui-foundation/src/androidTest/java/androidx/ui/foundation/MutuallyExclusiveSetItemTest.kt b/ui/ui-foundation/src/androidTest/java/androidx/ui/foundation/MutuallyExclusiveSetItemTest.kt
index 7351089..b91dc58 100644
--- a/ui/ui-foundation/src/androidTest/java/androidx/ui/foundation/MutuallyExclusiveSetItemTest.kt
+++ b/ui/ui-foundation/src/androidTest/java/androidx/ui/foundation/MutuallyExclusiveSetItemTest.kt
@@ -22,14 +22,15 @@
import androidx.test.filters.MediumTest
import androidx.ui.core.Text
import androidx.ui.foundation.selection.MutuallyExclusiveSetItem
-import androidx.ui.test.assertIsNotSelected
import androidx.ui.test.assertIsSelected
+import androidx.ui.test.assertIsUnselected
import androidx.ui.test.assertSemanticsIsEqualTo
import androidx.ui.test.createComposeRule
import androidx.ui.test.createFullSemantics
import androidx.ui.test.doClick
import androidx.ui.test.find
import androidx.ui.test.findAll
+import androidx.ui.test.isInMutuallyExclusiveGroup
import com.google.common.truth.Truth
import org.junit.Rule
import org.junit.Test
@@ -71,11 +72,11 @@
}
find { isInMutuallyExclusiveGroup }
- .assertIsNotSelected()
+ .assertIsUnselected()
.doClick()
.assertIsSelected()
.doClick()
- .assertIsNotSelected()
+ .assertIsUnselected()
}
@Test
@@ -88,8 +89,8 @@
}
find { isInMutuallyExclusiveGroup }
- .assertIsNotSelected()
+ .assertIsUnselected()
.doClick()
- .assertIsNotSelected()
+ .assertIsUnselected()
}
}
\ No newline at end of file
diff --git a/ui/ui-foundation/src/main/java/androidx/ui/foundation/Clickable.kt b/ui/ui-foundation/src/main/java/androidx/ui/foundation/Clickable.kt
index 1730ce1..66473034 100644
--- a/ui/ui-foundation/src/main/java/androidx/ui/foundation/Clickable.kt
+++ b/ui/ui-foundation/src/main/java/androidx/ui/foundation/Clickable.kt
@@ -16,13 +16,12 @@
package androidx.ui.foundation
-import androidx.ui.core.Semantics
+import androidx.ui.semantics.Semantics
import androidx.ui.core.gesture.PressReleasedGestureDetector
-import androidx.ui.core.semantics.SemanticsAction
-import androidx.ui.core.semantics.SemanticsActionType
-import androidx.compose.Children
-import androidx.compose.Composable
import androidx.compose.composer
+import androidx.compose.Composable
+import androidx.ui.semantics.enabled
+import androidx.ui.semantics.onClick
/**
* Combines [PressReleasedGestureDetector] and [Semantics] for the clickable
@@ -40,16 +39,14 @@
fun Clickable(
onClick: (() -> Unit)? = null,
consumeDownOnStart: Boolean = false,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
Semantics(
- button = true,
- enabled = (onClick != null),
- actions = if (onClick != null) {
- // TODO(ryanmentley): The unnecessary generic type specification works around an IR bug
- listOf<SemanticsAction<*>>(SemanticsAction(SemanticsActionType.Tap, onClick))
- } else {
- emptyList<SemanticsAction<*>>()
+ properties = {
+ enabled = (onClick != null)
+ if (onClick != null) {
+ onClick(action = onClick)
+ }
}
) {
PressReleasedGestureDetector(
diff --git a/ui/ui-foundation/src/main/java/androidx/ui/foundation/ColoredRect.kt b/ui/ui-foundation/src/main/java/androidx/ui/foundation/ColoredRect.kt
index 757de7f..ab71fd2 100644
--- a/ui/ui-foundation/src/main/java/androidx/ui/foundation/ColoredRect.kt
+++ b/ui/ui-foundation/src/main/java/androidx/ui/foundation/ColoredRect.kt
@@ -20,15 +20,12 @@
import androidx.compose.composer
import androidx.compose.trace
import androidx.ui.core.Dp
-import androidx.ui.core.Draw
-import androidx.ui.core.toRect
-import androidx.ui.core.vectorgraphics.Brush
-import androidx.ui.core.vectorgraphics.SolidColor
import androidx.ui.foundation.shape.DrawShape
import androidx.ui.foundation.shape.RectangleShape
import androidx.ui.graphics.Color
+import androidx.ui.graphics.vectorgraphics.Brush
+import androidx.ui.graphics.vectorgraphics.SolidColor
import androidx.ui.layout.Container
-import androidx.ui.painting.Paint
/**
* Component that represents a rectangle painted with the specified [Brush].
diff --git a/ui/ui-foundation/src/main/java/androidx/ui/foundation/DeterminateProgressIndicator.kt b/ui/ui-foundation/src/main/java/androidx/ui/foundation/DeterminateProgressIndicator.kt
index 1182bfe..49a0b33 100644
--- a/ui/ui-foundation/src/main/java/androidx/ui/foundation/DeterminateProgressIndicator.kt
+++ b/ui/ui-foundation/src/main/java/androidx/ui/foundation/DeterminateProgressIndicator.kt
@@ -17,10 +17,10 @@
package androidx.ui.foundation
import androidx.annotation.FloatRange
-import androidx.ui.core.Semantics
-import androidx.compose.Children
+import androidx.ui.semantics.Semantics
import androidx.compose.Composable
import androidx.compose.composer
+import androidx.ui.semantics.accessibilityValue
/**
* Contains the [Semantics] required for a determinate progress indicator, that represents progress
@@ -36,12 +36,12 @@
@Composable
fun DeterminateProgressIndicator(
@FloatRange(from = 0.0, to = 1.0) progress: Float,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
if (progress !in 0f..1f) {
throw IllegalArgumentException("Progress must be between 0.0 and 1.0")
}
- Semantics(value = ("$progress")) {
+ Semantics(properties = { accessibilityValue = "$progress" }) {
children()
}
}
diff --git a/ui/ui-foundation/src/main/java/androidx/ui/foundation/Dialog.kt b/ui/ui-foundation/src/main/java/androidx/ui/foundation/Dialog.kt
new file mode 100644
index 0000000..b6ad191
--- /dev/null
+++ b/ui/ui-foundation/src/main/java/androidx/ui/foundation/Dialog.kt
@@ -0,0 +1,100 @@
+/*
+ * 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.ui.foundation
+
+import android.app.Dialog
+import android.content.Context
+import android.view.MotionEvent
+import android.widget.FrameLayout
+import androidx.compose.Children
+import androidx.compose.Composable
+import androidx.compose.ambient
+import androidx.compose.disposeComposition
+import androidx.compose.memo
+import androidx.compose.onActive
+import androidx.compose.onCommit
+import androidx.compose.onDispose
+import androidx.compose.unaryPlus
+import androidx.ui.core.ContextAmbient
+import androidx.ui.core.setContent
+
+/**
+ * Opens a dialog with the given content.
+ *
+ * The dialog is visible as long as it is part of the composition hierarchy.
+ * In order to let the user dismiss the Dialog, the implementation of [onCloseRequest] should
+ * contain a way to remove to remove the dialog from the composition hierarchy.
+ *
+ * Example usage:
+ *
+ * @sample androidx.ui.foundation.samples.DialogSample
+ *
+ * @param onCloseRequest Executes when the user tries to dismiss the Dialog.
+ * @param children The content to be displayed inside the dialog.
+ */
+@Composable
+fun Dialog(onCloseRequest: () -> Unit, children: @Composable() () -> Unit) {
+ val context = +ambient(ContextAmbient)
+
+ val dialog = +memo { DialogWrapper(context, onCloseRequest) }
+
+ +onActive {
+ dialog.show()
+
+ onDispose {
+ dialog.dismiss()
+ dialog.disposeComposition()
+ }
+ }
+
+ +onCommit {
+ dialog.setContent(children)
+ }
+}
+
+private class DialogWrapper(context: Context, val onCloseRequest: () -> Unit) : Dialog(context) {
+ val frameLayout = FrameLayout(context)
+ init {
+ setContentView(frameLayout)
+ }
+
+ fun setContent(children: @Composable() () -> Unit) {
+ frameLayout.setContent(children)
+ }
+
+ fun disposeComposition() {
+ frameLayout.disposeComposition()
+ }
+
+ override fun onTouchEvent(event: MotionEvent): Boolean {
+ val result = super.onTouchEvent(event)
+ if (result) {
+ onCloseRequest()
+ }
+
+ return result
+ }
+
+ override fun cancel() {
+ // Prevents the dialog from dismissing itself
+ return
+ }
+
+ override fun onBackPressed() {
+ onCloseRequest()
+ }
+}
diff --git a/ui/ui-foundation/src/main/java/androidx/ui/foundation/Strings.kt b/ui/ui-foundation/src/main/java/androidx/ui/foundation/Strings.kt
new file mode 100644
index 0000000..b9d0281
--- /dev/null
+++ b/ui/ui-foundation/src/main/java/androidx/ui/foundation/Strings.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.ui.foundation
+
+// TODO(b/138327849): (STOPSHIP) Move all of these to resources once we have a real resources system,
+// then delete this class
+@Suppress("MayBeConstant") // The compiler gets unhappy if these are const (b/138328700)
+object Strings {
+ val Checked = "Checked"
+ val Unchecked = "Unchecked"
+ val Indeterminate = "Indeterminate"
+ val Selected = "Selected"
+ val NotSelected = "Not selected"
+}
\ No newline at end of file
diff --git a/ui/ui-foundation/src/main/java/androidx/ui/foundation/animation/AnimatedFloatDragController.kt b/ui/ui-foundation/src/main/java/androidx/ui/foundation/animation/AnimatedFloatDragController.kt
new file mode 100644
index 0000000..c47f27e
--- /dev/null
+++ b/ui/ui-foundation/src/main/java/androidx/ui/foundation/animation/AnimatedFloatDragController.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.ui.foundation.animation
+
+import androidx.animation.AnimatedFloat
+import androidx.animation.ValueHolder
+import androidx.compose.Model
+import androidx.ui.foundation.gestures.DragValueController
+import androidx.ui.foundation.gestures.Draggable
+import androidx.ui.lerp
+
+/**
+ * Controller that proxy all dragging events to [AnimatedFloat] and [FlingConfig]
+ *
+ * It makes it possible to have animation support for [Draggable] composable
+ * as well as to have fling animation after drag has ended, which is defined by [FlingConfig]
+ *
+ * @param initialValue initial value for AnimatedFloat to set it up
+ * @param flingConfig sets behavior of the fling after drag has ended.
+ * Default is null, which means no fling will occur no matter the velocity
+ */
+class AnimatedFloatDragController(
+ initialValue: Float,
+ private val flingConfig: FlingConfig? = null
+) : DragValueController {
+
+ val animatedFloat = AnimatedFloat(AnimValueHolder(initialValue, ::lerp))
+
+ override val currentValue
+ get() = animatedFloat.value
+
+ override fun setBounds(min: Float, max: Float) = animatedFloat.setBounds(min, max)
+
+ override fun onDrag(target: Float) {
+ animatedFloat.snapTo(target)
+ }
+
+ override fun onDragEnd(velocity: Float, onValueSettled: (Float) -> Unit) {
+ if (flingConfig != null) {
+ val config =
+ flingConfig.copy(onAnimationFinished = { value: Float, cancelled: Boolean ->
+ if (!cancelled) onValueSettled(value)
+ flingConfig.onAnimationFinished?.invoke(value, cancelled)
+ })
+ animatedFloat.fling(config, velocity)
+ } else {
+ onValueSettled(animatedFloat.value)
+ }
+ }
+}
+
+@Model
+private class AnimValueHolder<T>(
+ override var value: T,
+ override val interpolator: (T, T, Float) -> T
+) : ValueHolder<T>
diff --git a/ui/ui-foundation/src/main/java/androidx/ui/foundation/animation/FlingConfig.kt b/ui/ui-foundation/src/main/java/androidx/ui/foundation/animation/FlingConfig.kt
new file mode 100644
index 0000000..52e3d5e
--- /dev/null
+++ b/ui/ui-foundation/src/main/java/androidx/ui/foundation/animation/FlingConfig.kt
@@ -0,0 +1,97 @@
+/*
+ * 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.ui.foundation.animation
+
+import androidx.animation.AnimatedFloat
+import androidx.animation.AnimationBuilder
+import androidx.animation.DecayAnimation
+import androidx.animation.ExponentialDecay
+import androidx.animation.PhysicsBuilder
+import androidx.animation.TargetAnimation
+import androidx.animation.fling
+import kotlin.math.abs
+
+/**
+ * Class to specify fling behavior.
+ *
+ * When drag has ended, this class specifies what to do given the velocity
+ * with which drag ended and AnimatedFloat instance to perform fling on and read current value.
+ *
+ * Config that provides natural fling with customizable behaviour
+ * e.g fling friction or result target adjustment.
+ *
+ * The most common Decay animation is [ExponentialDecay].
+ *
+ * If you want to only be able to drag/animate between predefined set of values,
+ * consider using [AnchorsFlingConfig] function to generate such behaviour.
+ *
+ * @param decayAnimation the animation to control fling behaviour
+ * @param onAnimationFinished callback to be invoked when fling finishes by decay
+ * or being interrupted by gesture input.
+ * Consider second boolean param "cancelled" to know what happened.
+ * @param adjustTarget callback to be called at the start of fling
+ * so the final value for fling can be adjusted
+ */
+data class FlingConfig(
+ val decayAnimation: DecayAnimation = ExponentialDecay(),
+ val onAnimationFinished: ((finalValue: Float, cancelled: Boolean) -> Unit)? = null,
+ val adjustTarget: (Float) -> TargetAnimation? = { null }
+)
+
+/**
+ * Starts a fling animation with the specified starting velocity and fling configuration.
+ *
+ * @param config configuration that specifies fling behaviour
+ * @param startVelocity Starting velocity of the fling animation
+ */
+fun AnimatedFloat.fling(config: FlingConfig, startVelocity: Float) {
+ fling(
+ startVelocity,
+ config.decayAnimation,
+ config.adjustTarget,
+ { config.onAnimationFinished?.invoke(value, it) }
+ )
+}
+
+/**
+ * Create fling config with anchors will make sure that after drag has ended,
+ * the value will be animated to one of the points from the predefined list.
+ *
+ * It takes velocity into account, though value will be animated to the closest
+ * point in provided list considering velocity.
+ *
+ * @param animationAnchors set of anchors to animate to
+ * @param onAnimationFinished callback to be invoked when animation value reaches desired anchor
+ * or fling being interrupted by gesture input.
+ * Consider the second boolean param "cancelled" to know what happened.
+ * @param animationBuilder animation which will be used for animations
+ * @param decayAnimation decay animation to be used to calculate closest point in the anchors set
+ * considering velocity.
+ */
+fun AnchorsFlingConfig(
+ anchors: List<Float>,
+ animationBuilder: AnimationBuilder<Float> = PhysicsBuilder(),
+ onAnimationFinished: ((finalValue: Float, cancelled: Boolean) -> Unit)? = null,
+ decayAnimation: DecayAnimation = ExponentialDecay()
+): FlingConfig {
+ val adjustTarget: (Float) -> TargetAnimation? = { target ->
+ val point = anchors.minBy { abs(it - target) }
+ val adjusted = point ?: target
+ TargetAnimation(adjusted, animationBuilder)
+ }
+ return FlingConfig(decayAnimation, onAnimationFinished, adjustTarget)
+}
\ No newline at end of file
diff --git a/ui/ui-foundation/src/main/java/androidx/ui/foundation/gestures/AnimatedDraggable.kt b/ui/ui-foundation/src/main/java/androidx/ui/foundation/gestures/AnimatedDraggable.kt
deleted file mode 100644
index 39e47e1..0000000
--- a/ui/ui-foundation/src/main/java/androidx/ui/foundation/gestures/AnimatedDraggable.kt
+++ /dev/null
@@ -1,98 +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.ui.foundation.gestures
-
-import androidx.animation.AnimatedFloat
-import androidx.animation.BaseAnimatedValue
-import androidx.compose.Composable
-import androidx.compose.composer
-import androidx.compose.unaryPlus
-import androidx.ui.animation.animatedFloat
-import androidx.ui.core.PxPosition
-import androidx.ui.core.gesture.DragGestureDetector
-import androidx.ui.core.gesture.DragObserver
-import androidx.ui.core.px
-
-/**
- * Component that provides drag, fling and animation logic for one [Float] value.
- *
- * The common usecase for this component is when you need to be able to drag/scroll something
- * on the screen and also one or more of the following:
- * 1. Fling support when scrolling/dragging ends with velocity.
- * 2. Stable anchors points support for dragging value,
- * e.g. be able to drag between only predefined set of values.
- * 3. Automatic animation of draggable value, e.g emulate drag by click.
- *
- * @see [FlingConfig] to control anchors or fling intensity.
- *
- * This component provides high-level API and ownership for [AnimatedFloat]
- * and returns it as a parameter for its children.
- *
- * If you need only drag support without animations, consider using [DragGestureDetector] instead.
- *
- * If you need only animations without gesture support, consider using [animatedFloat] instead.
- *
- * @sample androidx.ui.foundation.samples.AnimatedDraggableSample
- *
- * @param dragDirection direction in which drag should be happening
- * @param startValue value to set as initial for draggable/animating value in this component
- * @param minValue lower bound for draggable/animating value in this component
- * @param maxValue upper bound for draggable/animating value in this component
- * Either [DragDirection.Vertical] or [DragDirection.Horizontal]
- * @param flingConfig sets behavior of the fling after drag has ended.
- * Default is null, which means no drag will occur no matter the velocity
- */
-@Composable
-fun AnimatedDraggable(
- dragDirection: DragDirection,
- startValue: Float,
- minValue: Float = Float.MIN_VALUE,
- maxValue: Float = Float.MAX_VALUE,
- flingConfig: FlingConfig? = null,
- children: @Composable() (BaseAnimatedValue<Float>) -> Unit
-) {
- val animFloat = (+animatedFloat(startValue)).apply {
- setBounds(minValue, maxValue)
- }
- DragGestureDetector(
- canDrag = { direction ->
- dragDirection.isDraggableInDirection(direction, minValue, animFloat.value, maxValue)
- },
- dragObserver = object : DragObserver {
-
- override fun onDrag(dragDistance: PxPosition): PxPosition {
- val projected = dragDirection.project(dragDistance)
- val newValue = (animFloat.value + projected).coerceIn(minValue, maxValue)
- val consumed = newValue - animFloat.value
- animFloat.snapTo(newValue)
- val fractionConsumed = if (projected == 0f) 0f else consumed / projected
- return PxPosition(
- dragDirection.xProjection(dragDistance.x).px * fractionConsumed,
- dragDirection.yProjection(dragDistance.y).px * fractionConsumed
- )
- }
-
- override fun onStop(velocity: PxPosition) {
- val projected = dragDirection.project(velocity)
- flingConfig?.fling(animFloat, projected)
- }
- }
- ) {
- children(animFloat)
- }
-}
-
diff --git a/ui/ui-foundation/src/main/java/androidx/ui/foundation/gestures/DragDirection.kt b/ui/ui-foundation/src/main/java/androidx/ui/foundation/gestures/DragDirection.kt
index abd6b48..4c16ec7 100644
--- a/ui/ui-foundation/src/main/java/androidx/ui/foundation/gestures/DragDirection.kt
+++ b/ui/ui-foundation/src/main/java/androidx/ui/foundation/gestures/DragDirection.kt
@@ -21,7 +21,7 @@
import androidx.ui.core.PxPosition
/**
- * Draggable Direction specifies the direction in which you can drag an [AnimatedDraggable].
+ * Draggable Direction specifies the direction in which you can drag an [Draggable].
* It can be either [Horizontal] or [Vertical].
*/
sealed class DragDirection {
@@ -39,7 +39,7 @@
internal open fun project(pos: PxPosition) = xProjection(pos.x) + yProjection(pos.y)
/**
- * Horizontal direction of dragging in [AnimatedDraggable].
+ * Horizontal direction of dragging in [Draggable].
*/
object Horizontal : DragDirection() {
internal override val xProjection: (Px) -> Float = { it.value }
@@ -60,7 +60,7 @@
}
/**
- * Vertical direction of dragging in [AnimatedDraggable].
+ * Vertical direction of dragging in [Draggable].
*/
object Vertical : DragDirection() {
internal override val xProjection: (Px) -> Float = { 0f }
diff --git a/ui/ui-foundation/src/main/java/androidx/ui/foundation/gestures/DragValueController.kt b/ui/ui-foundation/src/main/java/androidx/ui/foundation/gestures/DragValueController.kt
new file mode 100644
index 0000000..fcf668f
--- /dev/null
+++ b/ui/ui-foundation/src/main/java/androidx/ui/foundation/gestures/DragValueController.kt
@@ -0,0 +1,90 @@
+/*
+ * 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.ui.foundation.gestures
+
+import androidx.compose.Model
+
+/**
+ * Interface that defines behaviour of value that is dragged via [Draggable]
+ */
+interface DragValueController {
+ /**
+ * Current Float value of controlled value
+ */
+ val currentValue: Float
+
+ /**
+ * Perform drag to [target] on controlled value
+ *
+ * @param target the resulted position for controlled value
+ */
+ fun onDrag(target: Float)
+
+ /**
+ * Perform finishing activities when drag has ended with given velocity
+ *
+ * This is a good place to start fling or consume velocity in some other way
+ * if you need to do so.
+ *
+ * Callback passed to this function *must* be called after controlled value reached it's final
+ * destination, e.g. it might be an immediate drag end or the end of the fling.
+ *
+ * @param velocity the velocity value when drag has ended
+ * @param onValueSettled callback to call after fling has ended and controlled value settled
+ */
+ fun onDragEnd(velocity: Float, onValueSettled: (Float) -> Unit)
+
+ /**
+ * Set bounds for controlled value
+ *
+ * @param min lower bound for dragging
+ * @param max upper bound for dragging
+ */
+ fun setBounds(min: Float, max: Float)
+}
+
+@Model
+private class FloatValueHolder(var inner: Float)
+
+/**
+ * Simple [DragValueController] that backs up single [Float] value with no fling support
+ *
+ * @param initialValue the initial value to set for controlled Float value
+ */
+class FloatDragValueController(initialValue: Float) : DragValueController {
+ override val currentValue: Float
+ get() = value.inner
+
+ private val value = FloatValueHolder(initialValue)
+ private var minBound = Float.MIN_VALUE
+ private var maxBound = Float.MAX_VALUE
+
+ override fun onDrag(target: Float) {
+ this.value.inner = target.coerceIn(minBound, maxBound)
+ }
+
+ override fun onDragEnd(velocity: Float, onValueSettled: (Float) -> Unit) {
+ onValueSettled(currentValue)
+ }
+
+ override fun setBounds(min: Float, max: Float) {
+ val changed = minBound != min || maxBound != max
+ minBound = min
+ maxBound = max
+ if (changed) value.inner = value.inner.coerceIn(minBound, maxBound)
+ }
+}
\ No newline at end of file
diff --git a/ui/ui-foundation/src/main/java/androidx/ui/foundation/gestures/Draggable.kt b/ui/ui-foundation/src/main/java/androidx/ui/foundation/gestures/Draggable.kt
new file mode 100644
index 0000000..5fbcfc9
--- /dev/null
+++ b/ui/ui-foundation/src/main/java/androidx/ui/foundation/gestures/Draggable.kt
@@ -0,0 +1,115 @@
+/*
+ * 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.ui.foundation.gestures
+
+import androidx.compose.Composable
+import androidx.compose.composer
+import androidx.compose.memo
+import androidx.compose.unaryPlus
+import androidx.ui.core.PxPosition
+import androidx.ui.foundation.animation.AnimatedFloatDragController
+import androidx.ui.foundation.animation.AnchorsFlingConfig
+import androidx.ui.core.gesture.DragGestureDetector
+import androidx.ui.core.gesture.DragObserver
+import androidx.ui.core.px
+
+/**
+ * Component that provides high-level drag functionality reflected in one value
+ *
+ * The common usecase for this component is when you need to be able to drag/scroll something
+ * on the screen and represent it as one value via [DragValueController].
+ *
+ * If you need to control the whole dragging flow, consider using [DragGestureDetector] instead.
+ *
+ * @sample androidx.ui.foundation.samples.DraggableSample
+ *
+ * By using [AnimatedFloatDragController] with [AnchorsFlingConfig] you can achieve behaviour
+ * when value is gravitating to predefined set of points after drag has ended.
+ *
+ * @sample androidx.ui.foundation.samples.AnchoredDraggableSample
+ *
+ * @param dragDirection direction in which drag should be happening.
+ * Either [DragDirection.Vertical] or [DragDirection.Horizontal]
+ * @param minValue lower bound for draggable value in this component
+ * @param maxValue upper bound for draggable value in this component
+ * @param valueController controller to control value and how it will consume drag events,
+ * such as drag, fling or change of dragging bounds. The default is [FloatDragValueController],
+ * which provides simple move-as-much-as-user-drags login with no fling support.
+ * @param callback callback to react to drag events
+ */
+@Composable
+fun Draggable(
+ dragDirection: DragDirection,
+ minValue: Float = Float.MIN_VALUE,
+ maxValue: Float = Float.MAX_VALUE,
+ valueController: DragValueController = +memo(minValue) { FloatDragValueController(minValue) },
+ callback: DraggableCallback? = null,
+ children: @Composable() (Float) -> Unit
+) {
+ fun current() = valueController.currentValue
+ +memo(valueController, minValue, maxValue) {
+ valueController.setBounds(minValue, maxValue)
+ }
+ DragGestureDetector(
+ canDrag = { direction ->
+ dragDirection.isDraggableInDirection(direction, minValue, current(), maxValue)
+ },
+ dragObserver = object : DragObserver {
+
+ override fun onDrag(dragDistance: PxPosition): PxPosition {
+ callback?.notifyDrag()
+ val projected = dragDirection.project(dragDistance)
+ val newValue = (current() + projected).coerceIn(minValue, maxValue)
+ val consumed = newValue - current()
+ valueController.onDrag(newValue)
+ val fractionConsumed = if (projected == 0f) 0f else consumed / projected
+ return PxPosition(
+ dragDirection.xProjection(dragDistance.x).px * fractionConsumed,
+ dragDirection.yProjection(dragDistance.y).px * fractionConsumed
+ )
+ }
+
+ override fun onStop(velocity: PxPosition) {
+ val projected = dragDirection.project(velocity)
+ valueController.onDragEnd(projected) {
+ callback?.notifyFinished(it)
+ }
+ }
+ }
+ ) {
+ children(current())
+ }
+}
+
+
+class DraggableCallback(
+ private val onDragStarted: () -> Unit = {},
+ private val onDragSettled: (Float) -> Unit = {}
+) {
+ private var startNotified: Boolean = false
+ internal fun notifyDrag() {
+ if (!startNotified) {
+ startNotified = true
+ onDragStarted()
+ }
+ }
+
+ internal fun notifyFinished(final: Float) {
+ startNotified = false
+ onDragSettled(final)
+ }
+}
\ No newline at end of file
diff --git a/ui/ui-foundation/src/main/java/androidx/ui/foundation/gestures/FlingConfig.kt b/ui/ui-foundation/src/main/java/androidx/ui/foundation/gestures/FlingConfig.kt
deleted file mode 100644
index f2b41be..0000000
--- a/ui/ui-foundation/src/main/java/androidx/ui/foundation/gestures/FlingConfig.kt
+++ /dev/null
@@ -1,129 +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.ui.foundation.gestures
-
-import androidx.animation.AnimatedFloat
-import androidx.animation.AnimationBuilder
-import androidx.animation.DecayAnimation
-import androidx.animation.ExponentialDecay
-import androidx.animation.PhysicsBuilder
-import androidx.animation.TargetAnimation
-import androidx.animation.fling
-import kotlin.math.abs
-
-/**
- * Interface to specify fling behavior in [AnimatedDraggable].
- *
- * When drag has ended, this class specifies what to do given the velocity
- * with which drag ended and AnimatedFloat instance to perform fling on and read current value.
- *
- * If you need natural fling support, use [DefaultFlingConfig] or
- * [DecayFlingConfig] to control how much friction is applied to the fling
- *
- * If you want to only be able to drag/animate between predefined set of values,
- * consider using [AnchorsFlingConfig].
- *
- */
-interface FlingConfig {
- fun fling(value: AnimatedFloat, startVelocity: Float)
-}
-
-/**
- * Fling config with anchors will make sure that after drag has ended,
- * the value will be animated to one of the points from the predefined list.
- *
- * It takes velocity into account, though value will be animated to the closest
- * point in provided list considering velocity.
- *
- * @see ExponentialDecay to understand when to pass your own decayAnimation.
- *
- * @param animationAnchors set of anchors to animate to
- * @param onAnimationFinished callback to be invoked when animation value reaches desired anchor
- * or fling being interrupted by gesture input.
- * Consider the second boolean param "cancelled" to know what happened.
- * @param animationBuilder animation which will be used for animations
- * @param decayAnimation decay animation to be used to calculate closest point in the anchors set
- * considering velocity.
- */
-data class AnchorsFlingConfig(
- val animationAnchors: List<Float>,
- val onAnimationFinished: ((finishValue: Float, cancelled: Boolean) -> Unit)? = null,
- val animationBuilder: AnimationBuilder<Float> = PhysicsBuilder(),
- val decayAnimation: DecayAnimation = ExponentialDecay()
-) : FlingConfig {
-
- private val adjust: (Float) -> TargetAnimation? = { target ->
- val point = animationAnchors.minBy { abs(it - target) }
- val adjusted = point ?: target
- TargetAnimation(adjusted, animationBuilder)
- }
-
- override fun fling(
- value: AnimatedFloat,
- startVelocity: Float
- ) {
- value.fling(
- startVelocity,
- decayAnimation,
- adjust,
- onAnimationFinished?.let {
- { cancelled: Boolean -> it.invoke(value.value, cancelled) }
- }
- )
- }
-}
-
-/**
- * Config that provides natural fling with customizable decay behavior
- * e.g fling friction or velocity threshold. Natural fling config doesn't
- * specify where this fling will end.
- *
- * The most common Decay animation is [ExponentialDecay].
- *
- * @param decayAnimation the animation to control fling behaviour
- * @param onFlingFinished callback to be invoked when fling finishes by decay
- * or being interrupted by gesture input.
- * Consider second boolean param "cancelled" to know what happened.
- * @param adjustTarget callback to be called at the start of fling
- * so the final value for fling can be adjusted
- */
-data class DecayFlingConfig(
- val decayAnimation: DecayAnimation,
- val onFlingFinished: ((finishValue: Float, cancelled: Boolean) -> Unit)? = null,
- val adjustTarget: (Float) -> TargetAnimation? = { null }
-) : FlingConfig {
- override fun fling(value: AnimatedFloat, startVelocity: Float) {
- value.fling(
- startVelocity,
- decayAnimation,
- adjustTarget = adjustTarget,
- onFinished = onFlingFinished?.let {
- { cancelled: Boolean -> it.invoke(value.value, cancelled) }
- }
- )
- }
-}
-
-/**
- * Default fling config sets decay animation to [ExponentialDecay] to provide natural fling
- * and no calls no callback when fling finishes.
- */
-object DefaultFlingConfig : FlingConfig {
- override fun fling(value: AnimatedFloat, startVelocity: Float) {
- value.fling(startVelocity, ExponentialDecay())
- }
-}
\ No newline at end of file
diff --git a/ui/ui-foundation/src/main/java/androidx/ui/foundation/selection/MutuallyExclusiveSetItem.kt b/ui/ui-foundation/src/main/java/androidx/ui/foundation/selection/MutuallyExclusiveSetItem.kt
index af44074..6646965 100644
--- a/ui/ui-foundation/src/main/java/androidx/ui/foundation/selection/MutuallyExclusiveSetItem.kt
+++ b/ui/ui-foundation/src/main/java/androidx/ui/foundation/selection/MutuallyExclusiveSetItem.kt
@@ -16,35 +16,40 @@
package androidx.ui.foundation.selection
-import androidx.compose.Children
import androidx.compose.Composable
import androidx.compose.composer
import androidx.ui.core.gesture.PressReleasedGestureDetector
-import androidx.ui.core.Semantics
-import androidx.ui.core.semantics.SemanticsAction
-import androidx.ui.core.semantics.SemanticsActionType
+import androidx.ui.foundation.Strings
+import androidx.ui.semantics.Semantics
+import androidx.ui.foundation.semantics.inMutuallyExclusiveGroup
+import androidx.ui.foundation.semantics.selected
+import androidx.ui.semantics.onClick
+import androidx.ui.semantics.accessibilityValue
/**
- * Component for representing one option out of many
- * in mutually exclusion set, e.g [androidx.ui.material.RadioGroup]
- *
- * Provides click handling as well as [Semantics] for accessibility
- *
- * @param selected whether or not this item is selected in mutually exclusion set
- * @param onClick callback to invoke when this item is clicked
- */
+* Component for representing one option out of many
+* in mutually exclusion set, e.g [androidx.ui.material.RadioGroup]
+*
+* Provides click handling as well as [Semantics] for accessibility
+*
+* @param selected whether or not this item is selected in mutually exclusion set
+* @param onClick callback to invoke when this item is clicked
+*/
@Composable
fun MutuallyExclusiveSetItem(
selected: Boolean,
onClick: () -> Unit,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
// TODO: when semantics can be merged, we should make this use Clickable internally rather
- // than duplicating logic
+ // than duplicating logic
Semantics(
- inMutuallyExclusiveGroup = true,
- selected = selected,
- actions = listOf<SemanticsAction<*>>(SemanticsAction(SemanticsActionType.Tap, onClick))) {
+ properties = {
+ inMutuallyExclusiveGroup = true
+ this.selected = selected
+ this.accessibilityValue = if (selected) Strings.Selected else Strings.NotSelected
+ onClick(action = onClick)
+ }) {
PressReleasedGestureDetector(
onRelease = onClick,
consumeDownOnStart = false
diff --git a/ui/ui-foundation/src/main/java/androidx/ui/foundation/selection/Toggleable.kt b/ui/ui-foundation/src/main/java/androidx/ui/foundation/selection/Toggleable.kt
index 6531828..ae08bf0 100644
--- a/ui/ui-foundation/src/main/java/androidx/ui/foundation/selection/Toggleable.kt
+++ b/ui/ui-foundation/src/main/java/androidx/ui/foundation/selection/Toggleable.kt
@@ -16,40 +16,48 @@
package androidx.ui.foundation.selection
-import androidx.compose.Children
import androidx.compose.Composable
import androidx.compose.composer
import androidx.ui.core.gesture.PressReleasedGestureDetector
-import androidx.ui.core.semantics.SemanticsAction
-import androidx.ui.core.semantics.SemanticsActionType
-import androidx.ui.core.Semantics
+import androidx.ui.foundation.Strings
+import androidx.ui.foundation.semantics.toggleableState
+import androidx.ui.semantics.Semantics
+import androidx.ui.semantics.enabled
+import androidx.ui.semantics.onClick
+import androidx.ui.semantics.accessibilityValue
@Composable
fun Toggleable(
value: ToggleableState = ToggleableState.Checked,
onToggle: (() -> Unit)? = null,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
- val actions = if (onToggle != null) {
- listOf(SemanticsAction(SemanticsActionType.Tap, onToggle))
- } else {
- emptyList()
- }
PressReleasedGestureDetector(
onRelease = onToggle,
- consumeDownOnStart = false) {
+ consumeDownOnStart = false
+ ) {
// TODO: enabled should not be hardcoded
- // TODO(pavlis): Semantics currently doesn't support 4 states (only checked / unchecked / not checkable).
- Semantics(
- checked = (value == ToggleableState.Checked),
- enabled = true,
- actions = actions
- ) {
+ // TODO(pavlis): Handle multiple states for Semantics
+ Semantics(properties = {
+ this.accessibilityValue = when (value) {
+ // TODO(ryanmentley): These should be set by Checkbox, Switch, etc.
+ ToggleableState.Checked -> Strings.Checked
+ ToggleableState.Unchecked -> Strings.Unchecked
+ ToggleableState.Indeterminate -> Strings.Indeterminate
+ }
+ this.toggleableState = value
+ this.enabled = true
+
+ if (onToggle != null) {
+ onClick(action = onToggle, label = "Toggle")
+ }
+ }) {
children()
}
}
}
+// TODO: These shouldn't use checkbox-specific language
enum class ToggleableState {
Checked,
Unchecked,
diff --git a/ui/ui-foundation/src/main/java/androidx/ui/foundation/semantics/FoundationSemanticsProperties.kt b/ui/ui-foundation/src/main/java/androidx/ui/foundation/semantics/FoundationSemanticsProperties.kt
new file mode 100644
index 0000000..7525b72
--- /dev/null
+++ b/ui/ui-foundation/src/main/java/androidx/ui/foundation/semantics/FoundationSemanticsProperties.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.ui.foundation.semantics
+
+import androidx.ui.foundation.selection.ToggleableState
+import androidx.ui.semantics.SemanticsPropertyKey
+import androidx.ui.semantics.SemanticsPropertyReceiver
+
+/**
+ * Semantics properties that apply to the Compose Foundation UI elements. Used for making
+ * assertions in testing.
+ */
+object FoundationSemanticsProperties {
+ // TODO(ryanmentley): Is this useful?
+ val InMutuallyExclusiveGroup = SemanticsPropertyKey<Boolean>("InMutuallyExclusiveGroup")
+
+ val Selected = SemanticsPropertyKey<Boolean>("Selected")
+
+ // TODO(ryanmentley): Can we think of a better name?
+ val ToggleableState = SemanticsPropertyKey<ToggleableState>("ToggleableState")
+}
+
+// TODO(ryanmentley): should these be public? is that confusing?
+
+// TODO(ryanmentley): This one is kind of weird...it's sort of nonsense if this one is ever false
+var SemanticsPropertyReceiver.inMutuallyExclusiveGroup
+ by FoundationSemanticsProperties.InMutuallyExclusiveGroup
+
+var SemanticsPropertyReceiver.selected by FoundationSemanticsProperties.Selected
+
+var SemanticsPropertyReceiver.toggleableState
+ by FoundationSemanticsProperties.ToggleableState
diff --git a/ui/ui-foundation/src/main/java/androidx/ui/foundation/shape/DrawShape.kt b/ui/ui-foundation/src/main/java/androidx/ui/foundation/shape/DrawShape.kt
index 6a093bb..1cd22be 100644
--- a/ui/ui-foundation/src/main/java/androidx/ui/foundation/shape/DrawShape.kt
+++ b/ui/ui-foundation/src/main/java/androidx/ui/foundation/shape/DrawShape.kt
@@ -22,12 +22,12 @@
import androidx.compose.unaryPlus
import androidx.ui.core.Draw
import androidx.ui.core.PxSize
-import androidx.ui.core.vectorgraphics.Brush
-import androidx.ui.core.vectorgraphics.SolidColor
import androidx.ui.engine.geometry.Outline
import androidx.ui.engine.geometry.Shape
import androidx.ui.engine.geometry.drawOutline
import androidx.ui.graphics.Color
+import androidx.ui.graphics.vectorgraphics.Brush
+import androidx.ui.graphics.vectorgraphics.SolidColor
import androidx.ui.painting.Paint
/**
diff --git a/ui/ui-foundation/src/main/java/androidx/ui/foundation/shape/border/Border.kt b/ui/ui-foundation/src/main/java/androidx/ui/foundation/shape/border/Border.kt
index 3a66465..62f871a 100644
--- a/ui/ui-foundation/src/main/java/androidx/ui/foundation/shape/border/Border.kt
+++ b/ui/ui-foundation/src/main/java/androidx/ui/foundation/shape/border/Border.kt
@@ -17,9 +17,9 @@
package androidx.ui.foundation.shape.border
import androidx.ui.core.Dp
-import androidx.ui.core.vectorgraphics.Brush
-import androidx.ui.core.vectorgraphics.SolidColor
import androidx.ui.graphics.Color
+import androidx.ui.graphics.vectorgraphics.Brush
+import androidx.ui.graphics.vectorgraphics.SolidColor
/**
* A border of a shape.
diff --git a/ui/ui-framework/api/1.0.0-alpha01.txt b/ui/ui-framework/api/1.0.0-alpha01.txt
index 8ecec90..fbd9ec5 100644
--- a/ui/ui-framework/api/1.0.0-alpha01.txt
+++ b/ui/ui-framework/api/1.0.0-alpha01.txt
@@ -6,12 +6,23 @@
method public static void Clip(androidx.ui.engine.geometry.Shape shape, kotlin.jvm.functions.Function0<kotlin.Unit> children);
}
+ public final class ComplexLayoutBlockReceiver implements androidx.ui.core.DensityReceiver {
+ method public androidx.ui.core.Density getDensity();
+ method public androidx.ui.core.LayoutResult layoutResult(androidx.ui.core.IntPx width, androidx.ui.core.IntPx height, kotlin.jvm.functions.Function1<? super androidx.ui.core.PositioningBlockReceiver,kotlin.Unit> block);
+ method public androidx.ui.core.IntPx maxIntrinsicHeight(androidx.ui.core.Measurable, androidx.ui.core.IntPx w);
+ method public androidx.ui.core.IntPx maxIntrinsicWidth(androidx.ui.core.Measurable, androidx.ui.core.IntPx h);
+ method public androidx.ui.core.Placeable measure(androidx.ui.core.Measurable, androidx.ui.core.Constraints constraints);
+ method public androidx.ui.core.IntPx minIntrinsicHeight(androidx.ui.core.Measurable, androidx.ui.core.IntPx w);
+ method public androidx.ui.core.IntPx minIntrinsicWidth(androidx.ui.core.Measurable, androidx.ui.core.IntPx h);
+ property public androidx.ui.core.Density density;
+ }
+
public final class ComplexLayoutReceiver {
- method public void layout(kotlin.jvm.functions.Function3<? super androidx.ui.core.LayoutBlockReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.Constraints,kotlin.Unit> layoutBlock);
- method public void maxIntrinsicHeight(kotlin.jvm.functions.Function3<? super androidx.ui.core.IntrinsicMeasurementsReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.IntPx,androidx.ui.core.IntPx> maxIntrinsicHeightBlock);
- method public void maxIntrinsicWidth(kotlin.jvm.functions.Function3<? super androidx.ui.core.IntrinsicMeasurementsReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.IntPx,androidx.ui.core.IntPx> maxIntrinsicWidthBlock);
- method public void minIntrinsicHeight(kotlin.jvm.functions.Function3<? super androidx.ui.core.IntrinsicMeasurementsReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.IntPx,androidx.ui.core.IntPx> minIntrinsicHeightBlock);
- method public void minIntrinsicWidth(kotlin.jvm.functions.Function3<? super androidx.ui.core.IntrinsicMeasurementsReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.IntPx,androidx.ui.core.IntPx> minIntrinsicWidthBlock);
+ method public void layout(kotlin.jvm.functions.Function3<? super androidx.ui.core.ComplexLayoutBlockReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.Constraints,androidx.ui.core.LayoutResult> layoutBlock);
+ method public void maxIntrinsicHeight(kotlin.jvm.functions.Function3<? super androidx.ui.core.IntrinsicMeasurementReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.IntPx,androidx.ui.core.IntPx> maxIntrinsicHeightBlock);
+ method public void maxIntrinsicWidth(kotlin.jvm.functions.Function3<? super androidx.ui.core.IntrinsicMeasurementReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.IntPx,androidx.ui.core.IntPx> maxIntrinsicWidthBlock);
+ method public void minIntrinsicHeight(kotlin.jvm.functions.Function3<? super androidx.ui.core.IntrinsicMeasurementReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.IntPx,androidx.ui.core.IntPx> minIntrinsicHeightBlock);
+ method public void minIntrinsicWidth(kotlin.jvm.functions.Function3<? super androidx.ui.core.IntrinsicMeasurementReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.IntPx,androidx.ui.core.IntPx> minIntrinsicWidthBlock);
}
public final class DrawKt {
@@ -32,12 +43,7 @@
method public androidx.ui.text.TextStyle? getTextStyle();
}
- public final class InputFieldKt {
- ctor public InputFieldKt();
- method public static void InputField(androidx.ui.input.EditorState value, androidx.ui.core.EditorStyle editorStyle, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function1<? super androidx.ui.input.EditorState,kotlin.Unit> onValueChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.input.ImeAction,kotlin.Unit> onImeActionPerformed = {});
- }
-
- public final class IntrinsicMeasurementsReceiver implements androidx.ui.core.DensityReceiver {
+ public final class IntrinsicMeasurementReceiver implements androidx.ui.core.DensityReceiver {
method public androidx.ui.core.Density getDensity();
method public androidx.ui.core.IntPx maxIntrinsicHeight(androidx.ui.core.Measurable, androidx.ui.core.IntPx w);
method public androidx.ui.core.IntPx maxIntrinsicWidth(androidx.ui.core.Measurable, androidx.ui.core.IntPx h);
@@ -47,13 +53,10 @@
}
public final class LayoutBlockReceiver implements androidx.ui.core.DensityReceiver {
+ method public operator java.util.List<androidx.ui.core.Measurable> get(java.util.List<? extends androidx.ui.core.Measurable>, kotlin.jvm.functions.Function0<kotlin.Unit> children);
method public androidx.ui.core.Density getDensity();
- method public void layoutResult(androidx.ui.core.IntPx width, androidx.ui.core.IntPx height, kotlin.jvm.functions.Function1<? super androidx.ui.core.PositioningBlockReceiver,kotlin.Unit> block);
- method public androidx.ui.core.IntPx maxIntrinsicHeight(androidx.ui.core.Measurable, androidx.ui.core.IntPx w);
- method public androidx.ui.core.IntPx maxIntrinsicWidth(androidx.ui.core.Measurable, androidx.ui.core.IntPx h);
+ method public androidx.ui.core.LayoutResult layout(androidx.ui.core.IntPx width, androidx.ui.core.IntPx height, kotlin.jvm.functions.Function1<? super androidx.ui.core.PositioningBlockReceiver,kotlin.Unit> block);
method public androidx.ui.core.Placeable measure(androidx.ui.core.Measurable, androidx.ui.core.Constraints constraints);
- method public androidx.ui.core.IntPx minIntrinsicHeight(androidx.ui.core.Measurable, androidx.ui.core.IntPx w);
- method public androidx.ui.core.IntPx minIntrinsicWidth(androidx.ui.core.Measurable, androidx.ui.core.IntPx h);
property public androidx.ui.core.Density density;
}
@@ -72,19 +75,18 @@
public final class LayoutKt {
ctor public LayoutKt();
method public static void ComplexLayout(kotlin.jvm.functions.Function0<kotlin.Unit> children, kotlin.jvm.functions.Function1<? super androidx.ui.core.ComplexLayoutReceiver,kotlin.Unit> block);
- method public static void Layout(kotlin.jvm.functions.Function0<kotlin.Unit> children, kotlin.jvm.functions.Function3<? super androidx.ui.core.LayoutReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.Constraints,kotlin.Unit> layoutBlock);
- method public static void Layout(kotlin.jvm.functions.Function0<kotlin.Unit>![] childrenArray, kotlin.jvm.functions.Function3<? super androidx.ui.core.LayoutReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.Constraints,kotlin.Unit> layoutBlock);
+ method public static void Layout(kotlin.jvm.functions.Function0<kotlin.Unit> children, kotlin.jvm.functions.Function3<? super androidx.ui.core.LayoutBlockReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.Constraints,androidx.ui.core.LayoutResult> layoutBlock);
+ method public static void Layout(kotlin.jvm.functions.Function0<kotlin.Unit>![] childrenArray, kotlin.jvm.functions.Function3<? super androidx.ui.core.LayoutBlockReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.Constraints,androidx.ui.core.LayoutResult> layoutBlock);
method public static void OnChildPositioned(kotlin.jvm.functions.Function1<? super androidx.ui.core.LayoutCoordinates,kotlin.Unit> onPositioned, kotlin.jvm.functions.Function0<kotlin.Unit> children);
method public static void OnPositioned(kotlin.jvm.functions.Function1<? super androidx.ui.core.LayoutCoordinates,kotlin.Unit> onPositioned);
method public static void WithConstraints(kotlin.jvm.functions.Function1<? super androidx.ui.core.Constraints,kotlin.Unit> children);
}
- public final class LayoutReceiver implements androidx.ui.core.DensityReceiver {
- method public operator java.util.List<androidx.ui.core.Measurable> get(java.util.List<? extends androidx.ui.core.Measurable>, kotlin.jvm.functions.Function0<kotlin.Unit> children);
- method public androidx.ui.core.Density getDensity();
- method public void layout(androidx.ui.core.IntPx width, androidx.ui.core.IntPx height, kotlin.jvm.functions.Function1<? super androidx.ui.core.PositioningBlockReceiver,kotlin.Unit> block);
- method public androidx.ui.core.Placeable measure(androidx.ui.core.Measurable, androidx.ui.core.Constraints constraints);
- property public androidx.ui.core.Density density;
+ public final class LayoutResult {
+ field public static final androidx.ui.core.LayoutResult.Companion! Companion;
+ }
+
+ public static final class LayoutResult.Companion {
}
public interface Measurable {
@@ -92,6 +94,11 @@
property public abstract Object? parentData;
}
+ public interface OffsetMap {
+ method public int originalToTransformed(int offset);
+ method public int transformedToOriginal(int offset);
+ }
+
public final class OpacityKt {
ctor public OpacityKt();
method public static void Opacity(@FloatRange(from=0.0, to=1.0) float opacity, kotlin.jvm.functions.Function0<kotlin.Unit> children);
@@ -102,6 +109,13 @@
method public static void ParentData(Object data, kotlin.jvm.functions.Function0<kotlin.Unit> children);
}
+ public final class PasswordVisualTransformation implements androidx.ui.core.VisualTransformation {
+ ctor public PasswordVisualTransformation(char mask);
+ ctor public PasswordVisualTransformation();
+ method public androidx.ui.core.TransformedText filter(androidx.ui.text.AnnotatedString text);
+ method public char getMask();
+ }
+
public abstract class Placeable {
ctor public Placeable();
method public abstract androidx.ui.core.IntPx getHeight();
@@ -127,11 +141,6 @@
method public static void RepaintBoundary(String? name = null, kotlin.jvm.functions.Function0<kotlin.Unit> children);
}
- public final class SemanticsKt {
- ctor public SemanticsKt();
- method public static void Semantics(boolean container = false, boolean explicitChildNodes = false, Boolean? enabled = null, Boolean? checked = null, Boolean? selected = null, Boolean? button = null, Boolean? inMutuallyExclusiveGroup = null, Boolean? hidden = null, String? label = null, String? value = null, androidx.ui.text.style.TextDirection? textDirection = null, String? testTag = null, java.util.List<? extends androidx.ui.core.semantics.SemanticsAction<?>> actions = emptyList(), kotlin.jvm.functions.Function0<kotlin.Unit> children);
- }
-
public final class TestTagProviderKt {
ctor public TestTagProviderKt();
method public static void TestTag(String tag, kotlin.jvm.functions.Function0<kotlin.Unit> children);
@@ -139,6 +148,11 @@
method public static androidx.compose.Ambient<java.lang.String> getTestTagAmbient();
}
+ public final class TextFieldKt {
+ ctor public TextFieldKt();
+ method public static void TextField(androidx.ui.input.EditorModel value, androidx.ui.core.EditorStyle editorStyle, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function1<? super androidx.ui.input.EditorModel,kotlin.Unit> onValueChange = {}, kotlin.jvm.functions.Function0<kotlin.Unit> onFocus = {}, kotlin.jvm.functions.Function0<kotlin.Unit> onBlur = {}, kotlin.jvm.functions.Function1<? super androidx.ui.input.ImeAction,kotlin.Unit> onImeActionPerformed = {}, androidx.ui.core.VisualTransformation? visualTransformation = null);
+ }
+
public final class TextKt {
ctor public TextKt();
method public static void CurrentTextStyleProvider(androidx.ui.text.TextStyle value, kotlin.jvm.functions.Function0<kotlin.Unit> children);
@@ -174,12 +188,30 @@
method public androidx.ui.core.TextSpanComposition getComposer();
}
+ public final class TransformedText {
+ ctor public TransformedText(androidx.ui.text.AnnotatedString transformedText, androidx.ui.core.OffsetMap offsetMap);
+ method public androidx.ui.text.AnnotatedString component1();
+ method public androidx.ui.core.OffsetMap component2();
+ method public androidx.ui.core.TransformedText copy(androidx.ui.text.AnnotatedString transformedText, androidx.ui.core.OffsetMap offsetMap);
+ method public androidx.ui.core.OffsetMap getOffsetMap();
+ method public androidx.ui.text.AnnotatedString getTransformedText();
+ }
+
+ public interface VisualTransformation {
+ method public androidx.ui.core.TransformedText filter(androidx.ui.text.AnnotatedString text);
+ }
+
+ public final class VisualTransformationKt {
+ ctor public VisualTransformationKt();
+ }
+
public final class WrapperKt {
ctor public WrapperKt();
method public static void ComposeView(kotlin.jvm.functions.Function0<kotlin.Unit> children);
method public static void WithDensity(kotlin.jvm.functions.Function1<? super androidx.ui.core.DensityReceiver,kotlin.Unit> block);
method @CheckResult(suggest="+") public static androidx.compose.Effect<androidx.ui.core.Density> ambientDensity();
method public static androidx.compose.Ambient<android.content.Context> getContextAmbient();
+ method public static androidx.compose.Ambient<kotlin.coroutines.CoroutineContext> getCoroutineContextAmbient();
method public static androidx.compose.Ambient<androidx.ui.core.Density> getDensityAmbient();
method public static androidx.compose.CompositionContext? setContent(android.app.Activity, kotlin.jvm.functions.Function0<kotlin.Unit> content);
method public static androidx.compose.CompositionContext? setContent(android.view.ViewGroup, kotlin.jvm.functions.Function0<kotlin.Unit> content);
@@ -211,6 +243,11 @@
method public static androidx.ui.core.Duration getZoomControlsTimeout();
}
+ public final class DoubleTapGestureDetectorKt {
+ ctor public DoubleTapGestureDetectorKt();
+ method public static void DoubleTapGestureDetector(kotlin.jvm.functions.Function1<? super androidx.ui.core.PxPosition,kotlin.Unit> onDoubleTap, kotlin.jvm.functions.Function0<kotlin.Unit> children);
+ }
+
public final class DragGestureDetectorKt {
ctor public DragGestureDetectorKt();
method public static void DragGestureDetector(kotlin.jvm.functions.Function1<? super androidx.ui.core.Direction,java.lang.Boolean>? canDrag = null, androidx.ui.core.gesture.DragObserver? dragObserver = null, kotlin.jvm.functions.Function0<kotlin.Unit> children);
@@ -222,6 +259,11 @@
method public default void onStop(androidx.ui.core.PxPosition velocity);
}
+ public final class LongPressGestureDetectorKt {
+ ctor public LongPressGestureDetectorKt();
+ method public static void LongPressGestureDetector(kotlin.jvm.functions.Function1<? super androidx.ui.core.PxPosition,kotlin.Unit> onLongPress, kotlin.jvm.functions.Function0<kotlin.Unit> children);
+ }
+
public final class PressGestureDetectorKt {
ctor public PressGestureDetectorKt();
method public static void PressGestureDetector(kotlin.jvm.functions.Function1<? super androidx.ui.core.PxPosition,kotlin.Unit>? onPress = null, kotlin.jvm.functions.Function0<kotlin.Unit>? onRelease = null, kotlin.jvm.functions.Function0<kotlin.Unit>? onCancel = null, kotlin.jvm.functions.Function0<kotlin.Unit> children);
@@ -261,16 +303,16 @@
package androidx.ui.core.selection {
public final class Selection {
- ctor public Selection(androidx.ui.engine.geometry.Rect startOffset, androidx.ui.engine.geometry.Rect endOffset, androidx.ui.core.LayoutCoordinates? startLayoutCoordinates, androidx.ui.core.LayoutCoordinates? endLayoutCoordinates);
- method public androidx.ui.engine.geometry.Rect component1();
- method public androidx.ui.engine.geometry.Rect component2();
+ ctor public Selection(androidx.ui.core.PxPosition startCoordinates, androidx.ui.core.PxPosition endCoordinates, androidx.ui.core.LayoutCoordinates? startLayoutCoordinates, androidx.ui.core.LayoutCoordinates? endLayoutCoordinates);
+ method public androidx.ui.core.PxPosition component1();
+ method public androidx.ui.core.PxPosition component2();
method public androidx.ui.core.LayoutCoordinates? component3();
method public androidx.ui.core.LayoutCoordinates? component4();
- method public androidx.ui.core.selection.Selection copy(androidx.ui.engine.geometry.Rect startOffset, androidx.ui.engine.geometry.Rect endOffset, androidx.ui.core.LayoutCoordinates? startLayoutCoordinates, androidx.ui.core.LayoutCoordinates? endLayoutCoordinates);
+ method public androidx.ui.core.selection.Selection copy(androidx.ui.core.PxPosition startCoordinates, androidx.ui.core.PxPosition endCoordinates, androidx.ui.core.LayoutCoordinates? startLayoutCoordinates, androidx.ui.core.LayoutCoordinates? endLayoutCoordinates);
+ method public androidx.ui.core.PxPosition getEndCoordinates();
method public androidx.ui.core.LayoutCoordinates? getEndLayoutCoordinates();
- method public androidx.ui.engine.geometry.Rect getEndOffset();
+ method public androidx.ui.core.PxPosition getStartCoordinates();
method public androidx.ui.core.LayoutCoordinates? getStartLayoutCoordinates();
- method public androidx.ui.engine.geometry.Rect getStartOffset();
}
public final class SelectionContainerKt {
@@ -299,143 +341,74 @@
package androidx.ui.core.vectorgraphics {
- public interface Brush {
- method public void applyBrush(androidx.ui.painting.Paint p);
+ public final class VectorAsset {
+ method public androidx.ui.core.Px getDefaultHeight();
+ method public androidx.ui.core.Px getDefaultWidth();
+ method public String getName();
+ method public androidx.ui.core.vectorgraphics.VectorGroup getRoot();
+ method public float getViewportHeight();
+ method public float getViewportWidth();
}
- public final class BrushKt {
- ctor public BrushKt();
- method public static androidx.ui.core.vectorgraphics.Brush getEmptyBrush();
- method public static androidx.ui.core.vectorgraphics.Brush obtainBrush(Object? brush);
+ public final class VectorAssetBuilder {
+ ctor public VectorAssetBuilder(String name, androidx.ui.core.Px defaultWidth, androidx.ui.core.Px defaultHeight, float viewportWidth, float viewportHeight);
+ method public androidx.ui.core.vectorgraphics.VectorAssetBuilder addPath(Object? pathData, String name = "", Object fill = EmptyBrush, float fillAlpha = 1.0f, Object stroke = EmptyBrush, float strokeAlpha = 1.0f, float strokeLineWidth = 0.0f, androidx.ui.painting.StrokeCap strokeLineCap = DefaultStrokeLineCap, androidx.ui.painting.StrokeJoin strokeLineJoin = DefaultStrokeLineJoin, float strokeLineMiter = 4.0f);
+ method public androidx.ui.core.vectorgraphics.VectorAsset build();
+ method public void ensureNotConsumed();
+ method public androidx.ui.core.Px getDefaultHeight();
+ method public androidx.ui.core.Px getDefaultWidth();
+ method public String getName();
+ method public float getViewportHeight();
+ method public float getViewportWidth();
+ method public androidx.ui.core.vectorgraphics.VectorAssetBuilder popGroup();
+ method public androidx.ui.core.vectorgraphics.VectorAssetBuilder pushGroup(String name = "", float rotate = 0.0f, float pivotX = 0.0f, float pivotY = 0.0f, float scaleX = 1.0f, float scaleY = 1.0f, float translationX = 0.0f, float translationY = 0.0f, Object? clipPathData = EmptyPath);
}
- public final class LinearGradient implements androidx.ui.core.vectorgraphics.Brush {
- ctor public LinearGradient(kotlin.Pair<androidx.ui.graphics.Color,java.lang.Float>![] colorStops, float startX, float startY, float endX, float endY, androidx.ui.painting.TileMode tileMode);
- method public void applyBrush(androidx.ui.painting.Paint p);
- method public float getEndX();
- method public float getEndY();
- method public float getStartX();
- method public float getStartY();
- method public androidx.ui.painting.TileMode getTileMode();
+ public final class VectorAssetKt {
+ ctor public VectorAssetKt();
+ method public static void DrawVector(androidx.ui.core.vectorgraphics.VectorAsset vectorImage);
}
- public final class PathBuilder {
- ctor public PathBuilder();
- method public androidx.ui.core.vectorgraphics.PathBuilder arcTo(float horizontalEllipseRadius, float verticalEllipseRadius, float theta, float largeArcFlag, float sweepFlag, float x1, float y1);
- method public androidx.ui.core.vectorgraphics.PathBuilder arcToRelative(float a, float b, float theta, float largeArcFlag, float sweepFlag, float x1, float y1);
- method public androidx.ui.core.vectorgraphics.PathBuilder close();
- method public androidx.ui.core.vectorgraphics.PathBuilder curveTo(float x1, float y1, float x2, float y2, float x3, float y3);
- method public androidx.ui.core.vectorgraphics.PathBuilder curveToRelative(float dx1, float dy1, float dx2, float dy2, float dx3, float dy3);
- method public androidx.ui.core.vectorgraphics.PathNode![] getNodes();
- method public androidx.ui.core.vectorgraphics.PathBuilder horizontalLineTo(float x);
- method public androidx.ui.core.vectorgraphics.PathBuilder horizontalLineToRelative(float x);
- method public androidx.ui.core.vectorgraphics.PathBuilder lineTo(float x, float y);
- method public androidx.ui.core.vectorgraphics.PathBuilder lineToRelative(float x, float y);
- method public androidx.ui.core.vectorgraphics.PathBuilder moveTo(float x, float y);
- method public androidx.ui.core.vectorgraphics.PathBuilder moveToRelative(float x, float y);
- method public androidx.ui.core.vectorgraphics.PathBuilder quadTo(float x1, float y1, float x2, float y2);
- method public androidx.ui.core.vectorgraphics.PathBuilder quadToRelative(float x1, float y1, float x2, float y2);
- method public androidx.ui.core.vectorgraphics.PathBuilder reflectiveCurveTo(float x1, float y1, float x2, float y2);
- method public androidx.ui.core.vectorgraphics.PathBuilder reflectiveCurveToRelative(float x1, float y1, float x2, float y2);
- method public androidx.ui.core.vectorgraphics.PathBuilder reflectiveQuadTo(float x1, float y1);
- method public androidx.ui.core.vectorgraphics.PathBuilder reflectiveQuadToRelative(float x1, float y1);
- method public androidx.ui.core.vectorgraphics.PathBuilder verticalLineTo(float y);
- method public androidx.ui.core.vectorgraphics.PathBuilder verticalLineToRelative(float y);
+ public final class VectorComposeKt {
+ ctor public VectorComposeKt();
+ method public static void DrawVector(float viewportWidth, float viewportHeight, androidx.ui.core.Px defaultWidth = Px(viewportWidth), androidx.ui.core.Px defaultHeight = Px(viewportHeight), String name = "", kotlin.jvm.functions.Function1<? super androidx.ui.vector.VectorScope,kotlin.Unit> children);
+ method public static void Group(androidx.ui.vector.VectorScope, String name = "", float rotation = 0.0f, float pivotX = 0.0f, float pivotY = 0.0f, float scaleX = 1.0f, float scaleY = 1.0f, float translationX = 0.0f, float translationY = 0.0f, Object? clipPathData = EmptyPath, kotlin.jvm.functions.Function1<? super androidx.ui.vector.VectorScope,kotlin.Unit> children);
+ method public static void Path(androidx.ui.vector.VectorScope, Object? pathData, String name = "", Object fill = EmptyBrush, float fillAlpha = 1.0f, Object stroke = EmptyBrush, float strokeAlpha = 1.0f, float strokeLineWidth = 0.0f, androidx.ui.painting.StrokeCap strokeLineCap = DefaultStrokeLineCap, androidx.ui.painting.StrokeJoin strokeLineJoin = DefaultStrokeLineJoin, float strokeLineMiter = 4.0f);
}
- public enum PathCommand {
- method public final char toKey();
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand ArcTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand Close;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand CurveTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand HorizontalLineTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand LineTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand MoveTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand QuadTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand ReflectiveCurveTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand ReflectiveQuadTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand RelativeArcTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand RelativeClose;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand RelativeCurveTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand RelativeHorizontalTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand RelativeLineTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand RelativeMoveTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand RelativeQuadTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand RelativeReflectiveCurveTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand RelativeReflectiveQuadTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand RelativeVerticalTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand VerticalLineTo;
+ public final class VectorGroup extends androidx.ui.core.vectorgraphics.VectorNode implements java.lang.Iterable<androidx.ui.core.vectorgraphics.VectorNode> kotlin.jvm.internal.markers.KMappedMarker {
+ ctor public VectorGroup(String name, float rotation, float pivotX, float pivotY, float scaleX, float scaleY, float translationX, float translationY, Object? clipPathData);
+ ctor public VectorGroup();
+ method public operator androidx.ui.core.vectorgraphics.VectorNode get(int index);
+ method public Object? getClipPathData();
+ method public String getName();
+ method public float getPivotX();
+ method public float getPivotY();
+ method public float getRotation();
+ method public float getScaleX();
+ method public float getScaleY();
+ method public int getSize();
+ method public float getTranslationX();
+ method public float getTranslationY();
+ method public java.util.Iterator<androidx.ui.core.vectorgraphics.VectorNode> iterator();
+ property public final int size;
}
- public final class PathCommandKt {
- ctor public PathCommandKt();
- method public static androidx.ui.core.vectorgraphics.PathCommand toPathCommand(char) throws java.lang.IllegalArgumentException;
+ public abstract sealed class VectorNode {
}
- public final class PathDelegate {
- ctor public PathDelegate(kotlin.jvm.functions.Function1<? super androidx.ui.core.vectorgraphics.PathBuilder,kotlin.Unit> delegate);
- method public kotlin.jvm.functions.Function1<androidx.ui.core.vectorgraphics.PathBuilder,kotlin.Unit> getDelegate();
- }
-
- public final class PathNode {
- ctor public PathNode(androidx.ui.core.vectorgraphics.PathCommand command, float[] args);
- method public androidx.ui.core.vectorgraphics.PathCommand component1();
- method public float[] component2();
- method public androidx.ui.core.vectorgraphics.PathNode copy(androidx.ui.core.vectorgraphics.PathCommand command, float[] args);
- method public float[] getArgs();
- method public androidx.ui.core.vectorgraphics.PathCommand getCommand();
- }
-
- public final class PathNodeKt {
- ctor public PathNodeKt();
- method public static operator StringBuilder plus(StringBuilder, androidx.ui.core.vectorgraphics.PathNode node);
- }
-
- public final class PathParser {
- ctor public PathParser();
- method public androidx.ui.core.vectorgraphics.PathParser addPathNodes(androidx.ui.core.vectorgraphics.PathNode![] nodes);
- method public void clear();
- method public androidx.ui.core.vectorgraphics.PathParser parsePathString(String pathData) throws java.lang.IllegalArgumentException, java.lang.NumberFormatException;
- method public androidx.ui.core.vectorgraphics.PathNode![] toNodes();
- method public androidx.ui.painting.Path toPath(androidx.ui.painting.Path target = Path());
- }
-
- public final class PathParserKt {
- ctor public PathParserKt();
- }
-
- public final class RadialGradient implements androidx.ui.core.vectorgraphics.Brush {
- ctor public RadialGradient(kotlin.Pair<androidx.ui.graphics.Color,java.lang.Float>![] colorStops, float centerX, float centerY, float radius, androidx.ui.painting.TileMode tileMode);
- method public void applyBrush(androidx.ui.painting.Paint p);
- }
-
- public final class SolidColor implements androidx.ui.core.vectorgraphics.Brush {
- ctor public SolidColor(androidx.ui.graphics.Color value);
- method public void applyBrush(androidx.ui.painting.Paint p);
- }
-
- public final class VectorKt {
- ctor public VectorKt();
- method public static androidx.ui.core.vectorgraphics.PathNode![] addPathNodes(String? pathStr);
- method public static android.view.View? adoptVectorGraphic(Object? parent, Object? child);
- method public static androidx.ui.painting.StrokeCap getDefaultStrokeLineCap();
- method public static androidx.ui.painting.StrokeJoin getDefaultStrokeLineJoin();
- method public static androidx.ui.core.vectorgraphics.PathNode![] getEmptyPath();
- method public static void group(String name = "", float rotate = 0.0f, float pivotX = 0.0f, float pivotY = 0.0f, float scaleX = 1.0f, float scaleY = 1.0f, float translateX = 0.0f, float translateY = 0.0f, Object? clipPathData = EmptyPath, kotlin.jvm.functions.Function0<kotlin.Unit> childNodes);
- method public static void path(Object? pathData, String name = "", Object fill = EmptyBrush, float fillAlpha = 1.0f, Object stroke = EmptyBrush, float strokeAlpha = 1.0f, float strokeLineWidth = 0.0f, androidx.ui.painting.StrokeCap strokeLineCap = DefaultStrokeLineCap, androidx.ui.painting.StrokeJoin strokeLineJoin = DefaultStrokeLineJoin, float strokeLineMiter = 4.0f);
- method public static void vector(String name = "", float viewportWidth, float viewportHeight, float defaultWidth = viewportWidth, float defaultHeight = viewportHeight, kotlin.jvm.functions.Function0<kotlin.Unit> children);
- field public static final float DefaultAlpha = 1.0f;
- field public static final String DefaultGroupName = "";
- field public static final String DefaultPathName = "";
- field public static final float DefaultPivotX = 0.0f;
- field public static final float DefaultPivotY = 0.0f;
- field public static final float DefaultRotate = 0.0f;
- field public static final float DefaultScaleX = 1.0f;
- field public static final float DefaultScaleY = 1.0f;
- field public static final float DefaultStrokeLineMiter = 4.0f;
- field public static final float DefaultStrokeLineWidth = 0.0f;
- field public static final float DefaultTranslateX = 0.0f;
- field public static final float DefaultTranslateY = 0.0f;
+ public final class VectorPath extends androidx.ui.core.vectorgraphics.VectorNode {
+ ctor public VectorPath(String name, Object? pathData, Object fill, float fillAlpha, Object stroke, float strokeAlpha, float strokeLineWidth, androidx.ui.painting.StrokeCap strokeLineCap, androidx.ui.painting.StrokeJoin strokeLineJoin, float strokeLineMiter);
+ method public Object getFill();
+ method public float getFillAlpha();
+ method public String getName();
+ method public Object? getPathData();
+ method public Object getStroke();
+ method public float getStrokeAlpha();
+ method public androidx.ui.painting.StrokeCap getStrokeLineCap();
+ method public androidx.ui.painting.StrokeJoin getStrokeLineJoin();
+ method public float getStrokeLineMiter();
+ method public float getStrokeLineWidth();
}
}
@@ -444,7 +417,70 @@
public final class VectorResourceKt {
ctor public VectorResourceKt();
- method public static void vectorResource(android.content.res.Resources res, int resId);
+ method public static void VectorResource(int resId);
+ method public static androidx.ui.core.vectorgraphics.VectorAsset loadVectorResource(android.content.res.Resources.Theme? theme = null, android.content.res.Resources res, int resId) throws org.xmlpull.v1.XmlPullParserException;
+ }
+
+ public final class XmlVectorParserKt {
+ ctor public XmlVectorParserKt();
+ }
+
+}
+
+package androidx.ui.semantics {
+
+ public final class SemanticsActions {
+ ctor public SemanticsActions();
+ field public static final androidx.ui.semantics.SemanticsActions.Companion! Companion;
+ }
+
+ public static final class SemanticsActions.Companion {
+ method public androidx.ui.semantics.SemanticsPropertyKey<java.util.List<androidx.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<kotlin.Unit>>>> getCustomActions();
+ method public androidx.ui.semantics.SemanticsPropertyKey<androidx.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<kotlin.Unit>>> getOnClick();
+ property public final androidx.ui.semantics.SemanticsPropertyKey<java.util.List<androidx.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<kotlin.Unit>>>> CustomActions;
+ property public final androidx.ui.semantics.SemanticsPropertyKey<androidx.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<kotlin.Unit>>> OnClick;
+ }
+
+ public final class SemanticsKt {
+ ctor public SemanticsKt();
+ method public static void Semantics(boolean container = false, boolean explicitChildNodes = false, kotlin.jvm.functions.Function1<? super androidx.ui.semantics.SemanticsPropertyReceiver,kotlin.Unit>? properties = null, kotlin.jvm.functions.Function0<kotlin.Unit> children);
+ }
+
+ public final class SemanticsProperties {
+ method public androidx.ui.semantics.SemanticsPropertyKey<java.lang.String> getAccessibilityLabel();
+ method public androidx.ui.semantics.SemanticsPropertyKey<java.lang.String> getAccessibilityValue();
+ method public androidx.ui.semantics.SemanticsPropertyKey<java.lang.Boolean> getEnabled();
+ method public androidx.ui.semantics.SemanticsPropertyKey<java.lang.Boolean> getHidden();
+ method public androidx.ui.semantics.SemanticsPropertyKey<java.lang.String> getTestTag();
+ method public androidx.ui.semantics.SemanticsPropertyKey<androidx.ui.text.style.TextDirection> getTextDirection();
+ property public final androidx.ui.semantics.SemanticsPropertyKey<java.lang.String> AccessibilityLabel;
+ property public final androidx.ui.semantics.SemanticsPropertyKey<java.lang.String> AccessibilityValue;
+ property public final androidx.ui.semantics.SemanticsPropertyKey<java.lang.Boolean> Enabled;
+ property public final androidx.ui.semantics.SemanticsPropertyKey<java.lang.Boolean> Hidden;
+ property public final androidx.ui.semantics.SemanticsPropertyKey<java.lang.String> TestTag;
+ property public final androidx.ui.semantics.SemanticsPropertyKey<androidx.ui.text.style.TextDirection> TextDirection;
+ field public static final androidx.ui.semantics.SemanticsProperties! INSTANCE;
+ }
+
+ public final class SemanticsPropertiesKt {
+ ctor public SemanticsPropertiesKt();
+ method public static String getAccessibilityLabel(androidx.ui.semantics.SemanticsPropertyReceiver);
+ method public static String getAccessibilityValue(androidx.ui.semantics.SemanticsPropertyReceiver);
+ method public static java.util.List<androidx.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<kotlin.Unit>>> getCustomActions(androidx.ui.semantics.SemanticsPropertyReceiver);
+ method public static boolean getEnabled(androidx.ui.semantics.SemanticsPropertyReceiver);
+ method public static boolean getHidden(androidx.ui.semantics.SemanticsPropertyReceiver);
+ method public static androidx.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<kotlin.Unit>> getOnClick(androidx.ui.semantics.SemanticsPropertyReceiver);
+ method public static String getTestTag(androidx.ui.semantics.SemanticsPropertyReceiver);
+ method public static androidx.ui.text.style.TextDirection getTextDirection(androidx.ui.semantics.SemanticsPropertyReceiver);
+ method public static void onClick(androidx.ui.semantics.SemanticsPropertyReceiver, String? label = null, kotlin.jvm.functions.Function0<kotlin.Unit> action);
+ method public static void setAccessibilityLabel(androidx.ui.semantics.SemanticsPropertyReceiver, String p);
+ method public static void setAccessibilityValue(androidx.ui.semantics.SemanticsPropertyReceiver, String p);
+ method public static void setCustomActions(androidx.ui.semantics.SemanticsPropertyReceiver, java.util.List<androidx.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<kotlin.Unit>>> p);
+ method public static void setEnabled(androidx.ui.semantics.SemanticsPropertyReceiver, boolean p);
+ method public static void setHidden(androidx.ui.semantics.SemanticsPropertyReceiver, boolean p);
+ method public static void setOnClick(androidx.ui.semantics.SemanticsPropertyReceiver, androidx.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<kotlin.Unit>> p);
+ method public static void setTestTag(androidx.ui.semantics.SemanticsPropertyReceiver, String p);
+ method public static void setTextDirection(androidx.ui.semantics.SemanticsPropertyReceiver, androidx.ui.text.style.TextDirection p);
}
}
diff --git a/ui/ui-framework/api/current.txt b/ui/ui-framework/api/current.txt
index 8ecec90..fbd9ec5 100644
--- a/ui/ui-framework/api/current.txt
+++ b/ui/ui-framework/api/current.txt
@@ -6,12 +6,23 @@
method public static void Clip(androidx.ui.engine.geometry.Shape shape, kotlin.jvm.functions.Function0<kotlin.Unit> children);
}
+ public final class ComplexLayoutBlockReceiver implements androidx.ui.core.DensityReceiver {
+ method public androidx.ui.core.Density getDensity();
+ method public androidx.ui.core.LayoutResult layoutResult(androidx.ui.core.IntPx width, androidx.ui.core.IntPx height, kotlin.jvm.functions.Function1<? super androidx.ui.core.PositioningBlockReceiver,kotlin.Unit> block);
+ method public androidx.ui.core.IntPx maxIntrinsicHeight(androidx.ui.core.Measurable, androidx.ui.core.IntPx w);
+ method public androidx.ui.core.IntPx maxIntrinsicWidth(androidx.ui.core.Measurable, androidx.ui.core.IntPx h);
+ method public androidx.ui.core.Placeable measure(androidx.ui.core.Measurable, androidx.ui.core.Constraints constraints);
+ method public androidx.ui.core.IntPx minIntrinsicHeight(androidx.ui.core.Measurable, androidx.ui.core.IntPx w);
+ method public androidx.ui.core.IntPx minIntrinsicWidth(androidx.ui.core.Measurable, androidx.ui.core.IntPx h);
+ property public androidx.ui.core.Density density;
+ }
+
public final class ComplexLayoutReceiver {
- method public void layout(kotlin.jvm.functions.Function3<? super androidx.ui.core.LayoutBlockReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.Constraints,kotlin.Unit> layoutBlock);
- method public void maxIntrinsicHeight(kotlin.jvm.functions.Function3<? super androidx.ui.core.IntrinsicMeasurementsReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.IntPx,androidx.ui.core.IntPx> maxIntrinsicHeightBlock);
- method public void maxIntrinsicWidth(kotlin.jvm.functions.Function3<? super androidx.ui.core.IntrinsicMeasurementsReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.IntPx,androidx.ui.core.IntPx> maxIntrinsicWidthBlock);
- method public void minIntrinsicHeight(kotlin.jvm.functions.Function3<? super androidx.ui.core.IntrinsicMeasurementsReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.IntPx,androidx.ui.core.IntPx> minIntrinsicHeightBlock);
- method public void minIntrinsicWidth(kotlin.jvm.functions.Function3<? super androidx.ui.core.IntrinsicMeasurementsReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.IntPx,androidx.ui.core.IntPx> minIntrinsicWidthBlock);
+ method public void layout(kotlin.jvm.functions.Function3<? super androidx.ui.core.ComplexLayoutBlockReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.Constraints,androidx.ui.core.LayoutResult> layoutBlock);
+ method public void maxIntrinsicHeight(kotlin.jvm.functions.Function3<? super androidx.ui.core.IntrinsicMeasurementReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.IntPx,androidx.ui.core.IntPx> maxIntrinsicHeightBlock);
+ method public void maxIntrinsicWidth(kotlin.jvm.functions.Function3<? super androidx.ui.core.IntrinsicMeasurementReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.IntPx,androidx.ui.core.IntPx> maxIntrinsicWidthBlock);
+ method public void minIntrinsicHeight(kotlin.jvm.functions.Function3<? super androidx.ui.core.IntrinsicMeasurementReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.IntPx,androidx.ui.core.IntPx> minIntrinsicHeightBlock);
+ method public void minIntrinsicWidth(kotlin.jvm.functions.Function3<? super androidx.ui.core.IntrinsicMeasurementReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.IntPx,androidx.ui.core.IntPx> minIntrinsicWidthBlock);
}
public final class DrawKt {
@@ -32,12 +43,7 @@
method public androidx.ui.text.TextStyle? getTextStyle();
}
- public final class InputFieldKt {
- ctor public InputFieldKt();
- method public static void InputField(androidx.ui.input.EditorState value, androidx.ui.core.EditorStyle editorStyle, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function1<? super androidx.ui.input.EditorState,kotlin.Unit> onValueChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.input.ImeAction,kotlin.Unit> onImeActionPerformed = {});
- }
-
- public final class IntrinsicMeasurementsReceiver implements androidx.ui.core.DensityReceiver {
+ public final class IntrinsicMeasurementReceiver implements androidx.ui.core.DensityReceiver {
method public androidx.ui.core.Density getDensity();
method public androidx.ui.core.IntPx maxIntrinsicHeight(androidx.ui.core.Measurable, androidx.ui.core.IntPx w);
method public androidx.ui.core.IntPx maxIntrinsicWidth(androidx.ui.core.Measurable, androidx.ui.core.IntPx h);
@@ -47,13 +53,10 @@
}
public final class LayoutBlockReceiver implements androidx.ui.core.DensityReceiver {
+ method public operator java.util.List<androidx.ui.core.Measurable> get(java.util.List<? extends androidx.ui.core.Measurable>, kotlin.jvm.functions.Function0<kotlin.Unit> children);
method public androidx.ui.core.Density getDensity();
- method public void layoutResult(androidx.ui.core.IntPx width, androidx.ui.core.IntPx height, kotlin.jvm.functions.Function1<? super androidx.ui.core.PositioningBlockReceiver,kotlin.Unit> block);
- method public androidx.ui.core.IntPx maxIntrinsicHeight(androidx.ui.core.Measurable, androidx.ui.core.IntPx w);
- method public androidx.ui.core.IntPx maxIntrinsicWidth(androidx.ui.core.Measurable, androidx.ui.core.IntPx h);
+ method public androidx.ui.core.LayoutResult layout(androidx.ui.core.IntPx width, androidx.ui.core.IntPx height, kotlin.jvm.functions.Function1<? super androidx.ui.core.PositioningBlockReceiver,kotlin.Unit> block);
method public androidx.ui.core.Placeable measure(androidx.ui.core.Measurable, androidx.ui.core.Constraints constraints);
- method public androidx.ui.core.IntPx minIntrinsicHeight(androidx.ui.core.Measurable, androidx.ui.core.IntPx w);
- method public androidx.ui.core.IntPx minIntrinsicWidth(androidx.ui.core.Measurable, androidx.ui.core.IntPx h);
property public androidx.ui.core.Density density;
}
@@ -72,19 +75,18 @@
public final class LayoutKt {
ctor public LayoutKt();
method public static void ComplexLayout(kotlin.jvm.functions.Function0<kotlin.Unit> children, kotlin.jvm.functions.Function1<? super androidx.ui.core.ComplexLayoutReceiver,kotlin.Unit> block);
- method public static void Layout(kotlin.jvm.functions.Function0<kotlin.Unit> children, kotlin.jvm.functions.Function3<? super androidx.ui.core.LayoutReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.Constraints,kotlin.Unit> layoutBlock);
- method public static void Layout(kotlin.jvm.functions.Function0<kotlin.Unit>![] childrenArray, kotlin.jvm.functions.Function3<? super androidx.ui.core.LayoutReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.Constraints,kotlin.Unit> layoutBlock);
+ method public static void Layout(kotlin.jvm.functions.Function0<kotlin.Unit> children, kotlin.jvm.functions.Function3<? super androidx.ui.core.LayoutBlockReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.Constraints,androidx.ui.core.LayoutResult> layoutBlock);
+ method public static void Layout(kotlin.jvm.functions.Function0<kotlin.Unit>![] childrenArray, kotlin.jvm.functions.Function3<? super androidx.ui.core.LayoutBlockReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.Constraints,androidx.ui.core.LayoutResult> layoutBlock);
method public static void OnChildPositioned(kotlin.jvm.functions.Function1<? super androidx.ui.core.LayoutCoordinates,kotlin.Unit> onPositioned, kotlin.jvm.functions.Function0<kotlin.Unit> children);
method public static void OnPositioned(kotlin.jvm.functions.Function1<? super androidx.ui.core.LayoutCoordinates,kotlin.Unit> onPositioned);
method public static void WithConstraints(kotlin.jvm.functions.Function1<? super androidx.ui.core.Constraints,kotlin.Unit> children);
}
- public final class LayoutReceiver implements androidx.ui.core.DensityReceiver {
- method public operator java.util.List<androidx.ui.core.Measurable> get(java.util.List<? extends androidx.ui.core.Measurable>, kotlin.jvm.functions.Function0<kotlin.Unit> children);
- method public androidx.ui.core.Density getDensity();
- method public void layout(androidx.ui.core.IntPx width, androidx.ui.core.IntPx height, kotlin.jvm.functions.Function1<? super androidx.ui.core.PositioningBlockReceiver,kotlin.Unit> block);
- method public androidx.ui.core.Placeable measure(androidx.ui.core.Measurable, androidx.ui.core.Constraints constraints);
- property public androidx.ui.core.Density density;
+ public final class LayoutResult {
+ field public static final androidx.ui.core.LayoutResult.Companion! Companion;
+ }
+
+ public static final class LayoutResult.Companion {
}
public interface Measurable {
@@ -92,6 +94,11 @@
property public abstract Object? parentData;
}
+ public interface OffsetMap {
+ method public int originalToTransformed(int offset);
+ method public int transformedToOriginal(int offset);
+ }
+
public final class OpacityKt {
ctor public OpacityKt();
method public static void Opacity(@FloatRange(from=0.0, to=1.0) float opacity, kotlin.jvm.functions.Function0<kotlin.Unit> children);
@@ -102,6 +109,13 @@
method public static void ParentData(Object data, kotlin.jvm.functions.Function0<kotlin.Unit> children);
}
+ public final class PasswordVisualTransformation implements androidx.ui.core.VisualTransformation {
+ ctor public PasswordVisualTransformation(char mask);
+ ctor public PasswordVisualTransformation();
+ method public androidx.ui.core.TransformedText filter(androidx.ui.text.AnnotatedString text);
+ method public char getMask();
+ }
+
public abstract class Placeable {
ctor public Placeable();
method public abstract androidx.ui.core.IntPx getHeight();
@@ -127,11 +141,6 @@
method public static void RepaintBoundary(String? name = null, kotlin.jvm.functions.Function0<kotlin.Unit> children);
}
- public final class SemanticsKt {
- ctor public SemanticsKt();
- method public static void Semantics(boolean container = false, boolean explicitChildNodes = false, Boolean? enabled = null, Boolean? checked = null, Boolean? selected = null, Boolean? button = null, Boolean? inMutuallyExclusiveGroup = null, Boolean? hidden = null, String? label = null, String? value = null, androidx.ui.text.style.TextDirection? textDirection = null, String? testTag = null, java.util.List<? extends androidx.ui.core.semantics.SemanticsAction<?>> actions = emptyList(), kotlin.jvm.functions.Function0<kotlin.Unit> children);
- }
-
public final class TestTagProviderKt {
ctor public TestTagProviderKt();
method public static void TestTag(String tag, kotlin.jvm.functions.Function0<kotlin.Unit> children);
@@ -139,6 +148,11 @@
method public static androidx.compose.Ambient<java.lang.String> getTestTagAmbient();
}
+ public final class TextFieldKt {
+ ctor public TextFieldKt();
+ method public static void TextField(androidx.ui.input.EditorModel value, androidx.ui.core.EditorStyle editorStyle, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function1<? super androidx.ui.input.EditorModel,kotlin.Unit> onValueChange = {}, kotlin.jvm.functions.Function0<kotlin.Unit> onFocus = {}, kotlin.jvm.functions.Function0<kotlin.Unit> onBlur = {}, kotlin.jvm.functions.Function1<? super androidx.ui.input.ImeAction,kotlin.Unit> onImeActionPerformed = {}, androidx.ui.core.VisualTransformation? visualTransformation = null);
+ }
+
public final class TextKt {
ctor public TextKt();
method public static void CurrentTextStyleProvider(androidx.ui.text.TextStyle value, kotlin.jvm.functions.Function0<kotlin.Unit> children);
@@ -174,12 +188,30 @@
method public androidx.ui.core.TextSpanComposition getComposer();
}
+ public final class TransformedText {
+ ctor public TransformedText(androidx.ui.text.AnnotatedString transformedText, androidx.ui.core.OffsetMap offsetMap);
+ method public androidx.ui.text.AnnotatedString component1();
+ method public androidx.ui.core.OffsetMap component2();
+ method public androidx.ui.core.TransformedText copy(androidx.ui.text.AnnotatedString transformedText, androidx.ui.core.OffsetMap offsetMap);
+ method public androidx.ui.core.OffsetMap getOffsetMap();
+ method public androidx.ui.text.AnnotatedString getTransformedText();
+ }
+
+ public interface VisualTransformation {
+ method public androidx.ui.core.TransformedText filter(androidx.ui.text.AnnotatedString text);
+ }
+
+ public final class VisualTransformationKt {
+ ctor public VisualTransformationKt();
+ }
+
public final class WrapperKt {
ctor public WrapperKt();
method public static void ComposeView(kotlin.jvm.functions.Function0<kotlin.Unit> children);
method public static void WithDensity(kotlin.jvm.functions.Function1<? super androidx.ui.core.DensityReceiver,kotlin.Unit> block);
method @CheckResult(suggest="+") public static androidx.compose.Effect<androidx.ui.core.Density> ambientDensity();
method public static androidx.compose.Ambient<android.content.Context> getContextAmbient();
+ method public static androidx.compose.Ambient<kotlin.coroutines.CoroutineContext> getCoroutineContextAmbient();
method public static androidx.compose.Ambient<androidx.ui.core.Density> getDensityAmbient();
method public static androidx.compose.CompositionContext? setContent(android.app.Activity, kotlin.jvm.functions.Function0<kotlin.Unit> content);
method public static androidx.compose.CompositionContext? setContent(android.view.ViewGroup, kotlin.jvm.functions.Function0<kotlin.Unit> content);
@@ -211,6 +243,11 @@
method public static androidx.ui.core.Duration getZoomControlsTimeout();
}
+ public final class DoubleTapGestureDetectorKt {
+ ctor public DoubleTapGestureDetectorKt();
+ method public static void DoubleTapGestureDetector(kotlin.jvm.functions.Function1<? super androidx.ui.core.PxPosition,kotlin.Unit> onDoubleTap, kotlin.jvm.functions.Function0<kotlin.Unit> children);
+ }
+
public final class DragGestureDetectorKt {
ctor public DragGestureDetectorKt();
method public static void DragGestureDetector(kotlin.jvm.functions.Function1<? super androidx.ui.core.Direction,java.lang.Boolean>? canDrag = null, androidx.ui.core.gesture.DragObserver? dragObserver = null, kotlin.jvm.functions.Function0<kotlin.Unit> children);
@@ -222,6 +259,11 @@
method public default void onStop(androidx.ui.core.PxPosition velocity);
}
+ public final class LongPressGestureDetectorKt {
+ ctor public LongPressGestureDetectorKt();
+ method public static void LongPressGestureDetector(kotlin.jvm.functions.Function1<? super androidx.ui.core.PxPosition,kotlin.Unit> onLongPress, kotlin.jvm.functions.Function0<kotlin.Unit> children);
+ }
+
public final class PressGestureDetectorKt {
ctor public PressGestureDetectorKt();
method public static void PressGestureDetector(kotlin.jvm.functions.Function1<? super androidx.ui.core.PxPosition,kotlin.Unit>? onPress = null, kotlin.jvm.functions.Function0<kotlin.Unit>? onRelease = null, kotlin.jvm.functions.Function0<kotlin.Unit>? onCancel = null, kotlin.jvm.functions.Function0<kotlin.Unit> children);
@@ -261,16 +303,16 @@
package androidx.ui.core.selection {
public final class Selection {
- ctor public Selection(androidx.ui.engine.geometry.Rect startOffset, androidx.ui.engine.geometry.Rect endOffset, androidx.ui.core.LayoutCoordinates? startLayoutCoordinates, androidx.ui.core.LayoutCoordinates? endLayoutCoordinates);
- method public androidx.ui.engine.geometry.Rect component1();
- method public androidx.ui.engine.geometry.Rect component2();
+ ctor public Selection(androidx.ui.core.PxPosition startCoordinates, androidx.ui.core.PxPosition endCoordinates, androidx.ui.core.LayoutCoordinates? startLayoutCoordinates, androidx.ui.core.LayoutCoordinates? endLayoutCoordinates);
+ method public androidx.ui.core.PxPosition component1();
+ method public androidx.ui.core.PxPosition component2();
method public androidx.ui.core.LayoutCoordinates? component3();
method public androidx.ui.core.LayoutCoordinates? component4();
- method public androidx.ui.core.selection.Selection copy(androidx.ui.engine.geometry.Rect startOffset, androidx.ui.engine.geometry.Rect endOffset, androidx.ui.core.LayoutCoordinates? startLayoutCoordinates, androidx.ui.core.LayoutCoordinates? endLayoutCoordinates);
+ method public androidx.ui.core.selection.Selection copy(androidx.ui.core.PxPosition startCoordinates, androidx.ui.core.PxPosition endCoordinates, androidx.ui.core.LayoutCoordinates? startLayoutCoordinates, androidx.ui.core.LayoutCoordinates? endLayoutCoordinates);
+ method public androidx.ui.core.PxPosition getEndCoordinates();
method public androidx.ui.core.LayoutCoordinates? getEndLayoutCoordinates();
- method public androidx.ui.engine.geometry.Rect getEndOffset();
+ method public androidx.ui.core.PxPosition getStartCoordinates();
method public androidx.ui.core.LayoutCoordinates? getStartLayoutCoordinates();
- method public androidx.ui.engine.geometry.Rect getStartOffset();
}
public final class SelectionContainerKt {
@@ -299,143 +341,74 @@
package androidx.ui.core.vectorgraphics {
- public interface Brush {
- method public void applyBrush(androidx.ui.painting.Paint p);
+ public final class VectorAsset {
+ method public androidx.ui.core.Px getDefaultHeight();
+ method public androidx.ui.core.Px getDefaultWidth();
+ method public String getName();
+ method public androidx.ui.core.vectorgraphics.VectorGroup getRoot();
+ method public float getViewportHeight();
+ method public float getViewportWidth();
}
- public final class BrushKt {
- ctor public BrushKt();
- method public static androidx.ui.core.vectorgraphics.Brush getEmptyBrush();
- method public static androidx.ui.core.vectorgraphics.Brush obtainBrush(Object? brush);
+ public final class VectorAssetBuilder {
+ ctor public VectorAssetBuilder(String name, androidx.ui.core.Px defaultWidth, androidx.ui.core.Px defaultHeight, float viewportWidth, float viewportHeight);
+ method public androidx.ui.core.vectorgraphics.VectorAssetBuilder addPath(Object? pathData, String name = "", Object fill = EmptyBrush, float fillAlpha = 1.0f, Object stroke = EmptyBrush, float strokeAlpha = 1.0f, float strokeLineWidth = 0.0f, androidx.ui.painting.StrokeCap strokeLineCap = DefaultStrokeLineCap, androidx.ui.painting.StrokeJoin strokeLineJoin = DefaultStrokeLineJoin, float strokeLineMiter = 4.0f);
+ method public androidx.ui.core.vectorgraphics.VectorAsset build();
+ method public void ensureNotConsumed();
+ method public androidx.ui.core.Px getDefaultHeight();
+ method public androidx.ui.core.Px getDefaultWidth();
+ method public String getName();
+ method public float getViewportHeight();
+ method public float getViewportWidth();
+ method public androidx.ui.core.vectorgraphics.VectorAssetBuilder popGroup();
+ method public androidx.ui.core.vectorgraphics.VectorAssetBuilder pushGroup(String name = "", float rotate = 0.0f, float pivotX = 0.0f, float pivotY = 0.0f, float scaleX = 1.0f, float scaleY = 1.0f, float translationX = 0.0f, float translationY = 0.0f, Object? clipPathData = EmptyPath);
}
- public final class LinearGradient implements androidx.ui.core.vectorgraphics.Brush {
- ctor public LinearGradient(kotlin.Pair<androidx.ui.graphics.Color,java.lang.Float>![] colorStops, float startX, float startY, float endX, float endY, androidx.ui.painting.TileMode tileMode);
- method public void applyBrush(androidx.ui.painting.Paint p);
- method public float getEndX();
- method public float getEndY();
- method public float getStartX();
- method public float getStartY();
- method public androidx.ui.painting.TileMode getTileMode();
+ public final class VectorAssetKt {
+ ctor public VectorAssetKt();
+ method public static void DrawVector(androidx.ui.core.vectorgraphics.VectorAsset vectorImage);
}
- public final class PathBuilder {
- ctor public PathBuilder();
- method public androidx.ui.core.vectorgraphics.PathBuilder arcTo(float horizontalEllipseRadius, float verticalEllipseRadius, float theta, float largeArcFlag, float sweepFlag, float x1, float y1);
- method public androidx.ui.core.vectorgraphics.PathBuilder arcToRelative(float a, float b, float theta, float largeArcFlag, float sweepFlag, float x1, float y1);
- method public androidx.ui.core.vectorgraphics.PathBuilder close();
- method public androidx.ui.core.vectorgraphics.PathBuilder curveTo(float x1, float y1, float x2, float y2, float x3, float y3);
- method public androidx.ui.core.vectorgraphics.PathBuilder curveToRelative(float dx1, float dy1, float dx2, float dy2, float dx3, float dy3);
- method public androidx.ui.core.vectorgraphics.PathNode![] getNodes();
- method public androidx.ui.core.vectorgraphics.PathBuilder horizontalLineTo(float x);
- method public androidx.ui.core.vectorgraphics.PathBuilder horizontalLineToRelative(float x);
- method public androidx.ui.core.vectorgraphics.PathBuilder lineTo(float x, float y);
- method public androidx.ui.core.vectorgraphics.PathBuilder lineToRelative(float x, float y);
- method public androidx.ui.core.vectorgraphics.PathBuilder moveTo(float x, float y);
- method public androidx.ui.core.vectorgraphics.PathBuilder moveToRelative(float x, float y);
- method public androidx.ui.core.vectorgraphics.PathBuilder quadTo(float x1, float y1, float x2, float y2);
- method public androidx.ui.core.vectorgraphics.PathBuilder quadToRelative(float x1, float y1, float x2, float y2);
- method public androidx.ui.core.vectorgraphics.PathBuilder reflectiveCurveTo(float x1, float y1, float x2, float y2);
- method public androidx.ui.core.vectorgraphics.PathBuilder reflectiveCurveToRelative(float x1, float y1, float x2, float y2);
- method public androidx.ui.core.vectorgraphics.PathBuilder reflectiveQuadTo(float x1, float y1);
- method public androidx.ui.core.vectorgraphics.PathBuilder reflectiveQuadToRelative(float x1, float y1);
- method public androidx.ui.core.vectorgraphics.PathBuilder verticalLineTo(float y);
- method public androidx.ui.core.vectorgraphics.PathBuilder verticalLineToRelative(float y);
+ public final class VectorComposeKt {
+ ctor public VectorComposeKt();
+ method public static void DrawVector(float viewportWidth, float viewportHeight, androidx.ui.core.Px defaultWidth = Px(viewportWidth), androidx.ui.core.Px defaultHeight = Px(viewportHeight), String name = "", kotlin.jvm.functions.Function1<? super androidx.ui.vector.VectorScope,kotlin.Unit> children);
+ method public static void Group(androidx.ui.vector.VectorScope, String name = "", float rotation = 0.0f, float pivotX = 0.0f, float pivotY = 0.0f, float scaleX = 1.0f, float scaleY = 1.0f, float translationX = 0.0f, float translationY = 0.0f, Object? clipPathData = EmptyPath, kotlin.jvm.functions.Function1<? super androidx.ui.vector.VectorScope,kotlin.Unit> children);
+ method public static void Path(androidx.ui.vector.VectorScope, Object? pathData, String name = "", Object fill = EmptyBrush, float fillAlpha = 1.0f, Object stroke = EmptyBrush, float strokeAlpha = 1.0f, float strokeLineWidth = 0.0f, androidx.ui.painting.StrokeCap strokeLineCap = DefaultStrokeLineCap, androidx.ui.painting.StrokeJoin strokeLineJoin = DefaultStrokeLineJoin, float strokeLineMiter = 4.0f);
}
- public enum PathCommand {
- method public final char toKey();
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand ArcTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand Close;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand CurveTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand HorizontalLineTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand LineTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand MoveTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand QuadTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand ReflectiveCurveTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand ReflectiveQuadTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand RelativeArcTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand RelativeClose;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand RelativeCurveTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand RelativeHorizontalTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand RelativeLineTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand RelativeMoveTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand RelativeQuadTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand RelativeReflectiveCurveTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand RelativeReflectiveQuadTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand RelativeVerticalTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand VerticalLineTo;
+ public final class VectorGroup extends androidx.ui.core.vectorgraphics.VectorNode implements java.lang.Iterable<androidx.ui.core.vectorgraphics.VectorNode> kotlin.jvm.internal.markers.KMappedMarker {
+ ctor public VectorGroup(String name, float rotation, float pivotX, float pivotY, float scaleX, float scaleY, float translationX, float translationY, Object? clipPathData);
+ ctor public VectorGroup();
+ method public operator androidx.ui.core.vectorgraphics.VectorNode get(int index);
+ method public Object? getClipPathData();
+ method public String getName();
+ method public float getPivotX();
+ method public float getPivotY();
+ method public float getRotation();
+ method public float getScaleX();
+ method public float getScaleY();
+ method public int getSize();
+ method public float getTranslationX();
+ method public float getTranslationY();
+ method public java.util.Iterator<androidx.ui.core.vectorgraphics.VectorNode> iterator();
+ property public final int size;
}
- public final class PathCommandKt {
- ctor public PathCommandKt();
- method public static androidx.ui.core.vectorgraphics.PathCommand toPathCommand(char) throws java.lang.IllegalArgumentException;
+ public abstract sealed class VectorNode {
}
- public final class PathDelegate {
- ctor public PathDelegate(kotlin.jvm.functions.Function1<? super androidx.ui.core.vectorgraphics.PathBuilder,kotlin.Unit> delegate);
- method public kotlin.jvm.functions.Function1<androidx.ui.core.vectorgraphics.PathBuilder,kotlin.Unit> getDelegate();
- }
-
- public final class PathNode {
- ctor public PathNode(androidx.ui.core.vectorgraphics.PathCommand command, float[] args);
- method public androidx.ui.core.vectorgraphics.PathCommand component1();
- method public float[] component2();
- method public androidx.ui.core.vectorgraphics.PathNode copy(androidx.ui.core.vectorgraphics.PathCommand command, float[] args);
- method public float[] getArgs();
- method public androidx.ui.core.vectorgraphics.PathCommand getCommand();
- }
-
- public final class PathNodeKt {
- ctor public PathNodeKt();
- method public static operator StringBuilder plus(StringBuilder, androidx.ui.core.vectorgraphics.PathNode node);
- }
-
- public final class PathParser {
- ctor public PathParser();
- method public androidx.ui.core.vectorgraphics.PathParser addPathNodes(androidx.ui.core.vectorgraphics.PathNode![] nodes);
- method public void clear();
- method public androidx.ui.core.vectorgraphics.PathParser parsePathString(String pathData) throws java.lang.IllegalArgumentException, java.lang.NumberFormatException;
- method public androidx.ui.core.vectorgraphics.PathNode![] toNodes();
- method public androidx.ui.painting.Path toPath(androidx.ui.painting.Path target = Path());
- }
-
- public final class PathParserKt {
- ctor public PathParserKt();
- }
-
- public final class RadialGradient implements androidx.ui.core.vectorgraphics.Brush {
- ctor public RadialGradient(kotlin.Pair<androidx.ui.graphics.Color,java.lang.Float>![] colorStops, float centerX, float centerY, float radius, androidx.ui.painting.TileMode tileMode);
- method public void applyBrush(androidx.ui.painting.Paint p);
- }
-
- public final class SolidColor implements androidx.ui.core.vectorgraphics.Brush {
- ctor public SolidColor(androidx.ui.graphics.Color value);
- method public void applyBrush(androidx.ui.painting.Paint p);
- }
-
- public final class VectorKt {
- ctor public VectorKt();
- method public static androidx.ui.core.vectorgraphics.PathNode![] addPathNodes(String? pathStr);
- method public static android.view.View? adoptVectorGraphic(Object? parent, Object? child);
- method public static androidx.ui.painting.StrokeCap getDefaultStrokeLineCap();
- method public static androidx.ui.painting.StrokeJoin getDefaultStrokeLineJoin();
- method public static androidx.ui.core.vectorgraphics.PathNode![] getEmptyPath();
- method public static void group(String name = "", float rotate = 0.0f, float pivotX = 0.0f, float pivotY = 0.0f, float scaleX = 1.0f, float scaleY = 1.0f, float translateX = 0.0f, float translateY = 0.0f, Object? clipPathData = EmptyPath, kotlin.jvm.functions.Function0<kotlin.Unit> childNodes);
- method public static void path(Object? pathData, String name = "", Object fill = EmptyBrush, float fillAlpha = 1.0f, Object stroke = EmptyBrush, float strokeAlpha = 1.0f, float strokeLineWidth = 0.0f, androidx.ui.painting.StrokeCap strokeLineCap = DefaultStrokeLineCap, androidx.ui.painting.StrokeJoin strokeLineJoin = DefaultStrokeLineJoin, float strokeLineMiter = 4.0f);
- method public static void vector(String name = "", float viewportWidth, float viewportHeight, float defaultWidth = viewportWidth, float defaultHeight = viewportHeight, kotlin.jvm.functions.Function0<kotlin.Unit> children);
- field public static final float DefaultAlpha = 1.0f;
- field public static final String DefaultGroupName = "";
- field public static final String DefaultPathName = "";
- field public static final float DefaultPivotX = 0.0f;
- field public static final float DefaultPivotY = 0.0f;
- field public static final float DefaultRotate = 0.0f;
- field public static final float DefaultScaleX = 1.0f;
- field public static final float DefaultScaleY = 1.0f;
- field public static final float DefaultStrokeLineMiter = 4.0f;
- field public static final float DefaultStrokeLineWidth = 0.0f;
- field public static final float DefaultTranslateX = 0.0f;
- field public static final float DefaultTranslateY = 0.0f;
+ public final class VectorPath extends androidx.ui.core.vectorgraphics.VectorNode {
+ ctor public VectorPath(String name, Object? pathData, Object fill, float fillAlpha, Object stroke, float strokeAlpha, float strokeLineWidth, androidx.ui.painting.StrokeCap strokeLineCap, androidx.ui.painting.StrokeJoin strokeLineJoin, float strokeLineMiter);
+ method public Object getFill();
+ method public float getFillAlpha();
+ method public String getName();
+ method public Object? getPathData();
+ method public Object getStroke();
+ method public float getStrokeAlpha();
+ method public androidx.ui.painting.StrokeCap getStrokeLineCap();
+ method public androidx.ui.painting.StrokeJoin getStrokeLineJoin();
+ method public float getStrokeLineMiter();
+ method public float getStrokeLineWidth();
}
}
@@ -444,7 +417,70 @@
public final class VectorResourceKt {
ctor public VectorResourceKt();
- method public static void vectorResource(android.content.res.Resources res, int resId);
+ method public static void VectorResource(int resId);
+ method public static androidx.ui.core.vectorgraphics.VectorAsset loadVectorResource(android.content.res.Resources.Theme? theme = null, android.content.res.Resources res, int resId) throws org.xmlpull.v1.XmlPullParserException;
+ }
+
+ public final class XmlVectorParserKt {
+ ctor public XmlVectorParserKt();
+ }
+
+}
+
+package androidx.ui.semantics {
+
+ public final class SemanticsActions {
+ ctor public SemanticsActions();
+ field public static final androidx.ui.semantics.SemanticsActions.Companion! Companion;
+ }
+
+ public static final class SemanticsActions.Companion {
+ method public androidx.ui.semantics.SemanticsPropertyKey<java.util.List<androidx.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<kotlin.Unit>>>> getCustomActions();
+ method public androidx.ui.semantics.SemanticsPropertyKey<androidx.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<kotlin.Unit>>> getOnClick();
+ property public final androidx.ui.semantics.SemanticsPropertyKey<java.util.List<androidx.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<kotlin.Unit>>>> CustomActions;
+ property public final androidx.ui.semantics.SemanticsPropertyKey<androidx.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<kotlin.Unit>>> OnClick;
+ }
+
+ public final class SemanticsKt {
+ ctor public SemanticsKt();
+ method public static void Semantics(boolean container = false, boolean explicitChildNodes = false, kotlin.jvm.functions.Function1<? super androidx.ui.semantics.SemanticsPropertyReceiver,kotlin.Unit>? properties = null, kotlin.jvm.functions.Function0<kotlin.Unit> children);
+ }
+
+ public final class SemanticsProperties {
+ method public androidx.ui.semantics.SemanticsPropertyKey<java.lang.String> getAccessibilityLabel();
+ method public androidx.ui.semantics.SemanticsPropertyKey<java.lang.String> getAccessibilityValue();
+ method public androidx.ui.semantics.SemanticsPropertyKey<java.lang.Boolean> getEnabled();
+ method public androidx.ui.semantics.SemanticsPropertyKey<java.lang.Boolean> getHidden();
+ method public androidx.ui.semantics.SemanticsPropertyKey<java.lang.String> getTestTag();
+ method public androidx.ui.semantics.SemanticsPropertyKey<androidx.ui.text.style.TextDirection> getTextDirection();
+ property public final androidx.ui.semantics.SemanticsPropertyKey<java.lang.String> AccessibilityLabel;
+ property public final androidx.ui.semantics.SemanticsPropertyKey<java.lang.String> AccessibilityValue;
+ property public final androidx.ui.semantics.SemanticsPropertyKey<java.lang.Boolean> Enabled;
+ property public final androidx.ui.semantics.SemanticsPropertyKey<java.lang.Boolean> Hidden;
+ property public final androidx.ui.semantics.SemanticsPropertyKey<java.lang.String> TestTag;
+ property public final androidx.ui.semantics.SemanticsPropertyKey<androidx.ui.text.style.TextDirection> TextDirection;
+ field public static final androidx.ui.semantics.SemanticsProperties! INSTANCE;
+ }
+
+ public final class SemanticsPropertiesKt {
+ ctor public SemanticsPropertiesKt();
+ method public static String getAccessibilityLabel(androidx.ui.semantics.SemanticsPropertyReceiver);
+ method public static String getAccessibilityValue(androidx.ui.semantics.SemanticsPropertyReceiver);
+ method public static java.util.List<androidx.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<kotlin.Unit>>> getCustomActions(androidx.ui.semantics.SemanticsPropertyReceiver);
+ method public static boolean getEnabled(androidx.ui.semantics.SemanticsPropertyReceiver);
+ method public static boolean getHidden(androidx.ui.semantics.SemanticsPropertyReceiver);
+ method public static androidx.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<kotlin.Unit>> getOnClick(androidx.ui.semantics.SemanticsPropertyReceiver);
+ method public static String getTestTag(androidx.ui.semantics.SemanticsPropertyReceiver);
+ method public static androidx.ui.text.style.TextDirection getTextDirection(androidx.ui.semantics.SemanticsPropertyReceiver);
+ method public static void onClick(androidx.ui.semantics.SemanticsPropertyReceiver, String? label = null, kotlin.jvm.functions.Function0<kotlin.Unit> action);
+ method public static void setAccessibilityLabel(androidx.ui.semantics.SemanticsPropertyReceiver, String p);
+ method public static void setAccessibilityValue(androidx.ui.semantics.SemanticsPropertyReceiver, String p);
+ method public static void setCustomActions(androidx.ui.semantics.SemanticsPropertyReceiver, java.util.List<androidx.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<kotlin.Unit>>> p);
+ method public static void setEnabled(androidx.ui.semantics.SemanticsPropertyReceiver, boolean p);
+ method public static void setHidden(androidx.ui.semantics.SemanticsPropertyReceiver, boolean p);
+ method public static void setOnClick(androidx.ui.semantics.SemanticsPropertyReceiver, androidx.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<kotlin.Unit>> p);
+ method public static void setTestTag(androidx.ui.semantics.SemanticsPropertyReceiver, String p);
+ method public static void setTextDirection(androidx.ui.semantics.SemanticsPropertyReceiver, androidx.ui.text.style.TextDirection p);
}
}
diff --git a/ui/ui-framework/api/restricted_1.0.0-alpha01.txt b/ui/ui-framework/api/restricted_1.0.0-alpha01.txt
index 8ecec90..fbd9ec5 100644
--- a/ui/ui-framework/api/restricted_1.0.0-alpha01.txt
+++ b/ui/ui-framework/api/restricted_1.0.0-alpha01.txt
@@ -6,12 +6,23 @@
method public static void Clip(androidx.ui.engine.geometry.Shape shape, kotlin.jvm.functions.Function0<kotlin.Unit> children);
}
+ public final class ComplexLayoutBlockReceiver implements androidx.ui.core.DensityReceiver {
+ method public androidx.ui.core.Density getDensity();
+ method public androidx.ui.core.LayoutResult layoutResult(androidx.ui.core.IntPx width, androidx.ui.core.IntPx height, kotlin.jvm.functions.Function1<? super androidx.ui.core.PositioningBlockReceiver,kotlin.Unit> block);
+ method public androidx.ui.core.IntPx maxIntrinsicHeight(androidx.ui.core.Measurable, androidx.ui.core.IntPx w);
+ method public androidx.ui.core.IntPx maxIntrinsicWidth(androidx.ui.core.Measurable, androidx.ui.core.IntPx h);
+ method public androidx.ui.core.Placeable measure(androidx.ui.core.Measurable, androidx.ui.core.Constraints constraints);
+ method public androidx.ui.core.IntPx minIntrinsicHeight(androidx.ui.core.Measurable, androidx.ui.core.IntPx w);
+ method public androidx.ui.core.IntPx minIntrinsicWidth(androidx.ui.core.Measurable, androidx.ui.core.IntPx h);
+ property public androidx.ui.core.Density density;
+ }
+
public final class ComplexLayoutReceiver {
- method public void layout(kotlin.jvm.functions.Function3<? super androidx.ui.core.LayoutBlockReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.Constraints,kotlin.Unit> layoutBlock);
- method public void maxIntrinsicHeight(kotlin.jvm.functions.Function3<? super androidx.ui.core.IntrinsicMeasurementsReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.IntPx,androidx.ui.core.IntPx> maxIntrinsicHeightBlock);
- method public void maxIntrinsicWidth(kotlin.jvm.functions.Function3<? super androidx.ui.core.IntrinsicMeasurementsReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.IntPx,androidx.ui.core.IntPx> maxIntrinsicWidthBlock);
- method public void minIntrinsicHeight(kotlin.jvm.functions.Function3<? super androidx.ui.core.IntrinsicMeasurementsReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.IntPx,androidx.ui.core.IntPx> minIntrinsicHeightBlock);
- method public void minIntrinsicWidth(kotlin.jvm.functions.Function3<? super androidx.ui.core.IntrinsicMeasurementsReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.IntPx,androidx.ui.core.IntPx> minIntrinsicWidthBlock);
+ method public void layout(kotlin.jvm.functions.Function3<? super androidx.ui.core.ComplexLayoutBlockReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.Constraints,androidx.ui.core.LayoutResult> layoutBlock);
+ method public void maxIntrinsicHeight(kotlin.jvm.functions.Function3<? super androidx.ui.core.IntrinsicMeasurementReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.IntPx,androidx.ui.core.IntPx> maxIntrinsicHeightBlock);
+ method public void maxIntrinsicWidth(kotlin.jvm.functions.Function3<? super androidx.ui.core.IntrinsicMeasurementReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.IntPx,androidx.ui.core.IntPx> maxIntrinsicWidthBlock);
+ method public void minIntrinsicHeight(kotlin.jvm.functions.Function3<? super androidx.ui.core.IntrinsicMeasurementReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.IntPx,androidx.ui.core.IntPx> minIntrinsicHeightBlock);
+ method public void minIntrinsicWidth(kotlin.jvm.functions.Function3<? super androidx.ui.core.IntrinsicMeasurementReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.IntPx,androidx.ui.core.IntPx> minIntrinsicWidthBlock);
}
public final class DrawKt {
@@ -32,12 +43,7 @@
method public androidx.ui.text.TextStyle? getTextStyle();
}
- public final class InputFieldKt {
- ctor public InputFieldKt();
- method public static void InputField(androidx.ui.input.EditorState value, androidx.ui.core.EditorStyle editorStyle, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function1<? super androidx.ui.input.EditorState,kotlin.Unit> onValueChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.input.ImeAction,kotlin.Unit> onImeActionPerformed = {});
- }
-
- public final class IntrinsicMeasurementsReceiver implements androidx.ui.core.DensityReceiver {
+ public final class IntrinsicMeasurementReceiver implements androidx.ui.core.DensityReceiver {
method public androidx.ui.core.Density getDensity();
method public androidx.ui.core.IntPx maxIntrinsicHeight(androidx.ui.core.Measurable, androidx.ui.core.IntPx w);
method public androidx.ui.core.IntPx maxIntrinsicWidth(androidx.ui.core.Measurable, androidx.ui.core.IntPx h);
@@ -47,13 +53,10 @@
}
public final class LayoutBlockReceiver implements androidx.ui.core.DensityReceiver {
+ method public operator java.util.List<androidx.ui.core.Measurable> get(java.util.List<? extends androidx.ui.core.Measurable>, kotlin.jvm.functions.Function0<kotlin.Unit> children);
method public androidx.ui.core.Density getDensity();
- method public void layoutResult(androidx.ui.core.IntPx width, androidx.ui.core.IntPx height, kotlin.jvm.functions.Function1<? super androidx.ui.core.PositioningBlockReceiver,kotlin.Unit> block);
- method public androidx.ui.core.IntPx maxIntrinsicHeight(androidx.ui.core.Measurable, androidx.ui.core.IntPx w);
- method public androidx.ui.core.IntPx maxIntrinsicWidth(androidx.ui.core.Measurable, androidx.ui.core.IntPx h);
+ method public androidx.ui.core.LayoutResult layout(androidx.ui.core.IntPx width, androidx.ui.core.IntPx height, kotlin.jvm.functions.Function1<? super androidx.ui.core.PositioningBlockReceiver,kotlin.Unit> block);
method public androidx.ui.core.Placeable measure(androidx.ui.core.Measurable, androidx.ui.core.Constraints constraints);
- method public androidx.ui.core.IntPx minIntrinsicHeight(androidx.ui.core.Measurable, androidx.ui.core.IntPx w);
- method public androidx.ui.core.IntPx minIntrinsicWidth(androidx.ui.core.Measurable, androidx.ui.core.IntPx h);
property public androidx.ui.core.Density density;
}
@@ -72,19 +75,18 @@
public final class LayoutKt {
ctor public LayoutKt();
method public static void ComplexLayout(kotlin.jvm.functions.Function0<kotlin.Unit> children, kotlin.jvm.functions.Function1<? super androidx.ui.core.ComplexLayoutReceiver,kotlin.Unit> block);
- method public static void Layout(kotlin.jvm.functions.Function0<kotlin.Unit> children, kotlin.jvm.functions.Function3<? super androidx.ui.core.LayoutReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.Constraints,kotlin.Unit> layoutBlock);
- method public static void Layout(kotlin.jvm.functions.Function0<kotlin.Unit>![] childrenArray, kotlin.jvm.functions.Function3<? super androidx.ui.core.LayoutReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.Constraints,kotlin.Unit> layoutBlock);
+ method public static void Layout(kotlin.jvm.functions.Function0<kotlin.Unit> children, kotlin.jvm.functions.Function3<? super androidx.ui.core.LayoutBlockReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.Constraints,androidx.ui.core.LayoutResult> layoutBlock);
+ method public static void Layout(kotlin.jvm.functions.Function0<kotlin.Unit>![] childrenArray, kotlin.jvm.functions.Function3<? super androidx.ui.core.LayoutBlockReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.Constraints,androidx.ui.core.LayoutResult> layoutBlock);
method public static void OnChildPositioned(kotlin.jvm.functions.Function1<? super androidx.ui.core.LayoutCoordinates,kotlin.Unit> onPositioned, kotlin.jvm.functions.Function0<kotlin.Unit> children);
method public static void OnPositioned(kotlin.jvm.functions.Function1<? super androidx.ui.core.LayoutCoordinates,kotlin.Unit> onPositioned);
method public static void WithConstraints(kotlin.jvm.functions.Function1<? super androidx.ui.core.Constraints,kotlin.Unit> children);
}
- public final class LayoutReceiver implements androidx.ui.core.DensityReceiver {
- method public operator java.util.List<androidx.ui.core.Measurable> get(java.util.List<? extends androidx.ui.core.Measurable>, kotlin.jvm.functions.Function0<kotlin.Unit> children);
- method public androidx.ui.core.Density getDensity();
- method public void layout(androidx.ui.core.IntPx width, androidx.ui.core.IntPx height, kotlin.jvm.functions.Function1<? super androidx.ui.core.PositioningBlockReceiver,kotlin.Unit> block);
- method public androidx.ui.core.Placeable measure(androidx.ui.core.Measurable, androidx.ui.core.Constraints constraints);
- property public androidx.ui.core.Density density;
+ public final class LayoutResult {
+ field public static final androidx.ui.core.LayoutResult.Companion! Companion;
+ }
+
+ public static final class LayoutResult.Companion {
}
public interface Measurable {
@@ -92,6 +94,11 @@
property public abstract Object? parentData;
}
+ public interface OffsetMap {
+ method public int originalToTransformed(int offset);
+ method public int transformedToOriginal(int offset);
+ }
+
public final class OpacityKt {
ctor public OpacityKt();
method public static void Opacity(@FloatRange(from=0.0, to=1.0) float opacity, kotlin.jvm.functions.Function0<kotlin.Unit> children);
@@ -102,6 +109,13 @@
method public static void ParentData(Object data, kotlin.jvm.functions.Function0<kotlin.Unit> children);
}
+ public final class PasswordVisualTransformation implements androidx.ui.core.VisualTransformation {
+ ctor public PasswordVisualTransformation(char mask);
+ ctor public PasswordVisualTransformation();
+ method public androidx.ui.core.TransformedText filter(androidx.ui.text.AnnotatedString text);
+ method public char getMask();
+ }
+
public abstract class Placeable {
ctor public Placeable();
method public abstract androidx.ui.core.IntPx getHeight();
@@ -127,11 +141,6 @@
method public static void RepaintBoundary(String? name = null, kotlin.jvm.functions.Function0<kotlin.Unit> children);
}
- public final class SemanticsKt {
- ctor public SemanticsKt();
- method public static void Semantics(boolean container = false, boolean explicitChildNodes = false, Boolean? enabled = null, Boolean? checked = null, Boolean? selected = null, Boolean? button = null, Boolean? inMutuallyExclusiveGroup = null, Boolean? hidden = null, String? label = null, String? value = null, androidx.ui.text.style.TextDirection? textDirection = null, String? testTag = null, java.util.List<? extends androidx.ui.core.semantics.SemanticsAction<?>> actions = emptyList(), kotlin.jvm.functions.Function0<kotlin.Unit> children);
- }
-
public final class TestTagProviderKt {
ctor public TestTagProviderKt();
method public static void TestTag(String tag, kotlin.jvm.functions.Function0<kotlin.Unit> children);
@@ -139,6 +148,11 @@
method public static androidx.compose.Ambient<java.lang.String> getTestTagAmbient();
}
+ public final class TextFieldKt {
+ ctor public TextFieldKt();
+ method public static void TextField(androidx.ui.input.EditorModel value, androidx.ui.core.EditorStyle editorStyle, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function1<? super androidx.ui.input.EditorModel,kotlin.Unit> onValueChange = {}, kotlin.jvm.functions.Function0<kotlin.Unit> onFocus = {}, kotlin.jvm.functions.Function0<kotlin.Unit> onBlur = {}, kotlin.jvm.functions.Function1<? super androidx.ui.input.ImeAction,kotlin.Unit> onImeActionPerformed = {}, androidx.ui.core.VisualTransformation? visualTransformation = null);
+ }
+
public final class TextKt {
ctor public TextKt();
method public static void CurrentTextStyleProvider(androidx.ui.text.TextStyle value, kotlin.jvm.functions.Function0<kotlin.Unit> children);
@@ -174,12 +188,30 @@
method public androidx.ui.core.TextSpanComposition getComposer();
}
+ public final class TransformedText {
+ ctor public TransformedText(androidx.ui.text.AnnotatedString transformedText, androidx.ui.core.OffsetMap offsetMap);
+ method public androidx.ui.text.AnnotatedString component1();
+ method public androidx.ui.core.OffsetMap component2();
+ method public androidx.ui.core.TransformedText copy(androidx.ui.text.AnnotatedString transformedText, androidx.ui.core.OffsetMap offsetMap);
+ method public androidx.ui.core.OffsetMap getOffsetMap();
+ method public androidx.ui.text.AnnotatedString getTransformedText();
+ }
+
+ public interface VisualTransformation {
+ method public androidx.ui.core.TransformedText filter(androidx.ui.text.AnnotatedString text);
+ }
+
+ public final class VisualTransformationKt {
+ ctor public VisualTransformationKt();
+ }
+
public final class WrapperKt {
ctor public WrapperKt();
method public static void ComposeView(kotlin.jvm.functions.Function0<kotlin.Unit> children);
method public static void WithDensity(kotlin.jvm.functions.Function1<? super androidx.ui.core.DensityReceiver,kotlin.Unit> block);
method @CheckResult(suggest="+") public static androidx.compose.Effect<androidx.ui.core.Density> ambientDensity();
method public static androidx.compose.Ambient<android.content.Context> getContextAmbient();
+ method public static androidx.compose.Ambient<kotlin.coroutines.CoroutineContext> getCoroutineContextAmbient();
method public static androidx.compose.Ambient<androidx.ui.core.Density> getDensityAmbient();
method public static androidx.compose.CompositionContext? setContent(android.app.Activity, kotlin.jvm.functions.Function0<kotlin.Unit> content);
method public static androidx.compose.CompositionContext? setContent(android.view.ViewGroup, kotlin.jvm.functions.Function0<kotlin.Unit> content);
@@ -211,6 +243,11 @@
method public static androidx.ui.core.Duration getZoomControlsTimeout();
}
+ public final class DoubleTapGestureDetectorKt {
+ ctor public DoubleTapGestureDetectorKt();
+ method public static void DoubleTapGestureDetector(kotlin.jvm.functions.Function1<? super androidx.ui.core.PxPosition,kotlin.Unit> onDoubleTap, kotlin.jvm.functions.Function0<kotlin.Unit> children);
+ }
+
public final class DragGestureDetectorKt {
ctor public DragGestureDetectorKt();
method public static void DragGestureDetector(kotlin.jvm.functions.Function1<? super androidx.ui.core.Direction,java.lang.Boolean>? canDrag = null, androidx.ui.core.gesture.DragObserver? dragObserver = null, kotlin.jvm.functions.Function0<kotlin.Unit> children);
@@ -222,6 +259,11 @@
method public default void onStop(androidx.ui.core.PxPosition velocity);
}
+ public final class LongPressGestureDetectorKt {
+ ctor public LongPressGestureDetectorKt();
+ method public static void LongPressGestureDetector(kotlin.jvm.functions.Function1<? super androidx.ui.core.PxPosition,kotlin.Unit> onLongPress, kotlin.jvm.functions.Function0<kotlin.Unit> children);
+ }
+
public final class PressGestureDetectorKt {
ctor public PressGestureDetectorKt();
method public static void PressGestureDetector(kotlin.jvm.functions.Function1<? super androidx.ui.core.PxPosition,kotlin.Unit>? onPress = null, kotlin.jvm.functions.Function0<kotlin.Unit>? onRelease = null, kotlin.jvm.functions.Function0<kotlin.Unit>? onCancel = null, kotlin.jvm.functions.Function0<kotlin.Unit> children);
@@ -261,16 +303,16 @@
package androidx.ui.core.selection {
public final class Selection {
- ctor public Selection(androidx.ui.engine.geometry.Rect startOffset, androidx.ui.engine.geometry.Rect endOffset, androidx.ui.core.LayoutCoordinates? startLayoutCoordinates, androidx.ui.core.LayoutCoordinates? endLayoutCoordinates);
- method public androidx.ui.engine.geometry.Rect component1();
- method public androidx.ui.engine.geometry.Rect component2();
+ ctor public Selection(androidx.ui.core.PxPosition startCoordinates, androidx.ui.core.PxPosition endCoordinates, androidx.ui.core.LayoutCoordinates? startLayoutCoordinates, androidx.ui.core.LayoutCoordinates? endLayoutCoordinates);
+ method public androidx.ui.core.PxPosition component1();
+ method public androidx.ui.core.PxPosition component2();
method public androidx.ui.core.LayoutCoordinates? component3();
method public androidx.ui.core.LayoutCoordinates? component4();
- method public androidx.ui.core.selection.Selection copy(androidx.ui.engine.geometry.Rect startOffset, androidx.ui.engine.geometry.Rect endOffset, androidx.ui.core.LayoutCoordinates? startLayoutCoordinates, androidx.ui.core.LayoutCoordinates? endLayoutCoordinates);
+ method public androidx.ui.core.selection.Selection copy(androidx.ui.core.PxPosition startCoordinates, androidx.ui.core.PxPosition endCoordinates, androidx.ui.core.LayoutCoordinates? startLayoutCoordinates, androidx.ui.core.LayoutCoordinates? endLayoutCoordinates);
+ method public androidx.ui.core.PxPosition getEndCoordinates();
method public androidx.ui.core.LayoutCoordinates? getEndLayoutCoordinates();
- method public androidx.ui.engine.geometry.Rect getEndOffset();
+ method public androidx.ui.core.PxPosition getStartCoordinates();
method public androidx.ui.core.LayoutCoordinates? getStartLayoutCoordinates();
- method public androidx.ui.engine.geometry.Rect getStartOffset();
}
public final class SelectionContainerKt {
@@ -299,143 +341,74 @@
package androidx.ui.core.vectorgraphics {
- public interface Brush {
- method public void applyBrush(androidx.ui.painting.Paint p);
+ public final class VectorAsset {
+ method public androidx.ui.core.Px getDefaultHeight();
+ method public androidx.ui.core.Px getDefaultWidth();
+ method public String getName();
+ method public androidx.ui.core.vectorgraphics.VectorGroup getRoot();
+ method public float getViewportHeight();
+ method public float getViewportWidth();
}
- public final class BrushKt {
- ctor public BrushKt();
- method public static androidx.ui.core.vectorgraphics.Brush getEmptyBrush();
- method public static androidx.ui.core.vectorgraphics.Brush obtainBrush(Object? brush);
+ public final class VectorAssetBuilder {
+ ctor public VectorAssetBuilder(String name, androidx.ui.core.Px defaultWidth, androidx.ui.core.Px defaultHeight, float viewportWidth, float viewportHeight);
+ method public androidx.ui.core.vectorgraphics.VectorAssetBuilder addPath(Object? pathData, String name = "", Object fill = EmptyBrush, float fillAlpha = 1.0f, Object stroke = EmptyBrush, float strokeAlpha = 1.0f, float strokeLineWidth = 0.0f, androidx.ui.painting.StrokeCap strokeLineCap = DefaultStrokeLineCap, androidx.ui.painting.StrokeJoin strokeLineJoin = DefaultStrokeLineJoin, float strokeLineMiter = 4.0f);
+ method public androidx.ui.core.vectorgraphics.VectorAsset build();
+ method public void ensureNotConsumed();
+ method public androidx.ui.core.Px getDefaultHeight();
+ method public androidx.ui.core.Px getDefaultWidth();
+ method public String getName();
+ method public float getViewportHeight();
+ method public float getViewportWidth();
+ method public androidx.ui.core.vectorgraphics.VectorAssetBuilder popGroup();
+ method public androidx.ui.core.vectorgraphics.VectorAssetBuilder pushGroup(String name = "", float rotate = 0.0f, float pivotX = 0.0f, float pivotY = 0.0f, float scaleX = 1.0f, float scaleY = 1.0f, float translationX = 0.0f, float translationY = 0.0f, Object? clipPathData = EmptyPath);
}
- public final class LinearGradient implements androidx.ui.core.vectorgraphics.Brush {
- ctor public LinearGradient(kotlin.Pair<androidx.ui.graphics.Color,java.lang.Float>![] colorStops, float startX, float startY, float endX, float endY, androidx.ui.painting.TileMode tileMode);
- method public void applyBrush(androidx.ui.painting.Paint p);
- method public float getEndX();
- method public float getEndY();
- method public float getStartX();
- method public float getStartY();
- method public androidx.ui.painting.TileMode getTileMode();
+ public final class VectorAssetKt {
+ ctor public VectorAssetKt();
+ method public static void DrawVector(androidx.ui.core.vectorgraphics.VectorAsset vectorImage);
}
- public final class PathBuilder {
- ctor public PathBuilder();
- method public androidx.ui.core.vectorgraphics.PathBuilder arcTo(float horizontalEllipseRadius, float verticalEllipseRadius, float theta, float largeArcFlag, float sweepFlag, float x1, float y1);
- method public androidx.ui.core.vectorgraphics.PathBuilder arcToRelative(float a, float b, float theta, float largeArcFlag, float sweepFlag, float x1, float y1);
- method public androidx.ui.core.vectorgraphics.PathBuilder close();
- method public androidx.ui.core.vectorgraphics.PathBuilder curveTo(float x1, float y1, float x2, float y2, float x3, float y3);
- method public androidx.ui.core.vectorgraphics.PathBuilder curveToRelative(float dx1, float dy1, float dx2, float dy2, float dx3, float dy3);
- method public androidx.ui.core.vectorgraphics.PathNode![] getNodes();
- method public androidx.ui.core.vectorgraphics.PathBuilder horizontalLineTo(float x);
- method public androidx.ui.core.vectorgraphics.PathBuilder horizontalLineToRelative(float x);
- method public androidx.ui.core.vectorgraphics.PathBuilder lineTo(float x, float y);
- method public androidx.ui.core.vectorgraphics.PathBuilder lineToRelative(float x, float y);
- method public androidx.ui.core.vectorgraphics.PathBuilder moveTo(float x, float y);
- method public androidx.ui.core.vectorgraphics.PathBuilder moveToRelative(float x, float y);
- method public androidx.ui.core.vectorgraphics.PathBuilder quadTo(float x1, float y1, float x2, float y2);
- method public androidx.ui.core.vectorgraphics.PathBuilder quadToRelative(float x1, float y1, float x2, float y2);
- method public androidx.ui.core.vectorgraphics.PathBuilder reflectiveCurveTo(float x1, float y1, float x2, float y2);
- method public androidx.ui.core.vectorgraphics.PathBuilder reflectiveCurveToRelative(float x1, float y1, float x2, float y2);
- method public androidx.ui.core.vectorgraphics.PathBuilder reflectiveQuadTo(float x1, float y1);
- method public androidx.ui.core.vectorgraphics.PathBuilder reflectiveQuadToRelative(float x1, float y1);
- method public androidx.ui.core.vectorgraphics.PathBuilder verticalLineTo(float y);
- method public androidx.ui.core.vectorgraphics.PathBuilder verticalLineToRelative(float y);
+ public final class VectorComposeKt {
+ ctor public VectorComposeKt();
+ method public static void DrawVector(float viewportWidth, float viewportHeight, androidx.ui.core.Px defaultWidth = Px(viewportWidth), androidx.ui.core.Px defaultHeight = Px(viewportHeight), String name = "", kotlin.jvm.functions.Function1<? super androidx.ui.vector.VectorScope,kotlin.Unit> children);
+ method public static void Group(androidx.ui.vector.VectorScope, String name = "", float rotation = 0.0f, float pivotX = 0.0f, float pivotY = 0.0f, float scaleX = 1.0f, float scaleY = 1.0f, float translationX = 0.0f, float translationY = 0.0f, Object? clipPathData = EmptyPath, kotlin.jvm.functions.Function1<? super androidx.ui.vector.VectorScope,kotlin.Unit> children);
+ method public static void Path(androidx.ui.vector.VectorScope, Object? pathData, String name = "", Object fill = EmptyBrush, float fillAlpha = 1.0f, Object stroke = EmptyBrush, float strokeAlpha = 1.0f, float strokeLineWidth = 0.0f, androidx.ui.painting.StrokeCap strokeLineCap = DefaultStrokeLineCap, androidx.ui.painting.StrokeJoin strokeLineJoin = DefaultStrokeLineJoin, float strokeLineMiter = 4.0f);
}
- public enum PathCommand {
- method public final char toKey();
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand ArcTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand Close;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand CurveTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand HorizontalLineTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand LineTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand MoveTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand QuadTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand ReflectiveCurveTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand ReflectiveQuadTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand RelativeArcTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand RelativeClose;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand RelativeCurveTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand RelativeHorizontalTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand RelativeLineTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand RelativeMoveTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand RelativeQuadTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand RelativeReflectiveCurveTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand RelativeReflectiveQuadTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand RelativeVerticalTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand VerticalLineTo;
+ public final class VectorGroup extends androidx.ui.core.vectorgraphics.VectorNode implements java.lang.Iterable<androidx.ui.core.vectorgraphics.VectorNode> kotlin.jvm.internal.markers.KMappedMarker {
+ ctor public VectorGroup(String name, float rotation, float pivotX, float pivotY, float scaleX, float scaleY, float translationX, float translationY, Object? clipPathData);
+ ctor public VectorGroup();
+ method public operator androidx.ui.core.vectorgraphics.VectorNode get(int index);
+ method public Object? getClipPathData();
+ method public String getName();
+ method public float getPivotX();
+ method public float getPivotY();
+ method public float getRotation();
+ method public float getScaleX();
+ method public float getScaleY();
+ method public int getSize();
+ method public float getTranslationX();
+ method public float getTranslationY();
+ method public java.util.Iterator<androidx.ui.core.vectorgraphics.VectorNode> iterator();
+ property public final int size;
}
- public final class PathCommandKt {
- ctor public PathCommandKt();
- method public static androidx.ui.core.vectorgraphics.PathCommand toPathCommand(char) throws java.lang.IllegalArgumentException;
+ public abstract sealed class VectorNode {
}
- public final class PathDelegate {
- ctor public PathDelegate(kotlin.jvm.functions.Function1<? super androidx.ui.core.vectorgraphics.PathBuilder,kotlin.Unit> delegate);
- method public kotlin.jvm.functions.Function1<androidx.ui.core.vectorgraphics.PathBuilder,kotlin.Unit> getDelegate();
- }
-
- public final class PathNode {
- ctor public PathNode(androidx.ui.core.vectorgraphics.PathCommand command, float[] args);
- method public androidx.ui.core.vectorgraphics.PathCommand component1();
- method public float[] component2();
- method public androidx.ui.core.vectorgraphics.PathNode copy(androidx.ui.core.vectorgraphics.PathCommand command, float[] args);
- method public float[] getArgs();
- method public androidx.ui.core.vectorgraphics.PathCommand getCommand();
- }
-
- public final class PathNodeKt {
- ctor public PathNodeKt();
- method public static operator StringBuilder plus(StringBuilder, androidx.ui.core.vectorgraphics.PathNode node);
- }
-
- public final class PathParser {
- ctor public PathParser();
- method public androidx.ui.core.vectorgraphics.PathParser addPathNodes(androidx.ui.core.vectorgraphics.PathNode![] nodes);
- method public void clear();
- method public androidx.ui.core.vectorgraphics.PathParser parsePathString(String pathData) throws java.lang.IllegalArgumentException, java.lang.NumberFormatException;
- method public androidx.ui.core.vectorgraphics.PathNode![] toNodes();
- method public androidx.ui.painting.Path toPath(androidx.ui.painting.Path target = Path());
- }
-
- public final class PathParserKt {
- ctor public PathParserKt();
- }
-
- public final class RadialGradient implements androidx.ui.core.vectorgraphics.Brush {
- ctor public RadialGradient(kotlin.Pair<androidx.ui.graphics.Color,java.lang.Float>![] colorStops, float centerX, float centerY, float radius, androidx.ui.painting.TileMode tileMode);
- method public void applyBrush(androidx.ui.painting.Paint p);
- }
-
- public final class SolidColor implements androidx.ui.core.vectorgraphics.Brush {
- ctor public SolidColor(androidx.ui.graphics.Color value);
- method public void applyBrush(androidx.ui.painting.Paint p);
- }
-
- public final class VectorKt {
- ctor public VectorKt();
- method public static androidx.ui.core.vectorgraphics.PathNode![] addPathNodes(String? pathStr);
- method public static android.view.View? adoptVectorGraphic(Object? parent, Object? child);
- method public static androidx.ui.painting.StrokeCap getDefaultStrokeLineCap();
- method public static androidx.ui.painting.StrokeJoin getDefaultStrokeLineJoin();
- method public static androidx.ui.core.vectorgraphics.PathNode![] getEmptyPath();
- method public static void group(String name = "", float rotate = 0.0f, float pivotX = 0.0f, float pivotY = 0.0f, float scaleX = 1.0f, float scaleY = 1.0f, float translateX = 0.0f, float translateY = 0.0f, Object? clipPathData = EmptyPath, kotlin.jvm.functions.Function0<kotlin.Unit> childNodes);
- method public static void path(Object? pathData, String name = "", Object fill = EmptyBrush, float fillAlpha = 1.0f, Object stroke = EmptyBrush, float strokeAlpha = 1.0f, float strokeLineWidth = 0.0f, androidx.ui.painting.StrokeCap strokeLineCap = DefaultStrokeLineCap, androidx.ui.painting.StrokeJoin strokeLineJoin = DefaultStrokeLineJoin, float strokeLineMiter = 4.0f);
- method public static void vector(String name = "", float viewportWidth, float viewportHeight, float defaultWidth = viewportWidth, float defaultHeight = viewportHeight, kotlin.jvm.functions.Function0<kotlin.Unit> children);
- field public static final float DefaultAlpha = 1.0f;
- field public static final String DefaultGroupName = "";
- field public static final String DefaultPathName = "";
- field public static final float DefaultPivotX = 0.0f;
- field public static final float DefaultPivotY = 0.0f;
- field public static final float DefaultRotate = 0.0f;
- field public static final float DefaultScaleX = 1.0f;
- field public static final float DefaultScaleY = 1.0f;
- field public static final float DefaultStrokeLineMiter = 4.0f;
- field public static final float DefaultStrokeLineWidth = 0.0f;
- field public static final float DefaultTranslateX = 0.0f;
- field public static final float DefaultTranslateY = 0.0f;
+ public final class VectorPath extends androidx.ui.core.vectorgraphics.VectorNode {
+ ctor public VectorPath(String name, Object? pathData, Object fill, float fillAlpha, Object stroke, float strokeAlpha, float strokeLineWidth, androidx.ui.painting.StrokeCap strokeLineCap, androidx.ui.painting.StrokeJoin strokeLineJoin, float strokeLineMiter);
+ method public Object getFill();
+ method public float getFillAlpha();
+ method public String getName();
+ method public Object? getPathData();
+ method public Object getStroke();
+ method public float getStrokeAlpha();
+ method public androidx.ui.painting.StrokeCap getStrokeLineCap();
+ method public androidx.ui.painting.StrokeJoin getStrokeLineJoin();
+ method public float getStrokeLineMiter();
+ method public float getStrokeLineWidth();
}
}
@@ -444,7 +417,70 @@
public final class VectorResourceKt {
ctor public VectorResourceKt();
- method public static void vectorResource(android.content.res.Resources res, int resId);
+ method public static void VectorResource(int resId);
+ method public static androidx.ui.core.vectorgraphics.VectorAsset loadVectorResource(android.content.res.Resources.Theme? theme = null, android.content.res.Resources res, int resId) throws org.xmlpull.v1.XmlPullParserException;
+ }
+
+ public final class XmlVectorParserKt {
+ ctor public XmlVectorParserKt();
+ }
+
+}
+
+package androidx.ui.semantics {
+
+ public final class SemanticsActions {
+ ctor public SemanticsActions();
+ field public static final androidx.ui.semantics.SemanticsActions.Companion! Companion;
+ }
+
+ public static final class SemanticsActions.Companion {
+ method public androidx.ui.semantics.SemanticsPropertyKey<java.util.List<androidx.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<kotlin.Unit>>>> getCustomActions();
+ method public androidx.ui.semantics.SemanticsPropertyKey<androidx.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<kotlin.Unit>>> getOnClick();
+ property public final androidx.ui.semantics.SemanticsPropertyKey<java.util.List<androidx.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<kotlin.Unit>>>> CustomActions;
+ property public final androidx.ui.semantics.SemanticsPropertyKey<androidx.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<kotlin.Unit>>> OnClick;
+ }
+
+ public final class SemanticsKt {
+ ctor public SemanticsKt();
+ method public static void Semantics(boolean container = false, boolean explicitChildNodes = false, kotlin.jvm.functions.Function1<? super androidx.ui.semantics.SemanticsPropertyReceiver,kotlin.Unit>? properties = null, kotlin.jvm.functions.Function0<kotlin.Unit> children);
+ }
+
+ public final class SemanticsProperties {
+ method public androidx.ui.semantics.SemanticsPropertyKey<java.lang.String> getAccessibilityLabel();
+ method public androidx.ui.semantics.SemanticsPropertyKey<java.lang.String> getAccessibilityValue();
+ method public androidx.ui.semantics.SemanticsPropertyKey<java.lang.Boolean> getEnabled();
+ method public androidx.ui.semantics.SemanticsPropertyKey<java.lang.Boolean> getHidden();
+ method public androidx.ui.semantics.SemanticsPropertyKey<java.lang.String> getTestTag();
+ method public androidx.ui.semantics.SemanticsPropertyKey<androidx.ui.text.style.TextDirection> getTextDirection();
+ property public final androidx.ui.semantics.SemanticsPropertyKey<java.lang.String> AccessibilityLabel;
+ property public final androidx.ui.semantics.SemanticsPropertyKey<java.lang.String> AccessibilityValue;
+ property public final androidx.ui.semantics.SemanticsPropertyKey<java.lang.Boolean> Enabled;
+ property public final androidx.ui.semantics.SemanticsPropertyKey<java.lang.Boolean> Hidden;
+ property public final androidx.ui.semantics.SemanticsPropertyKey<java.lang.String> TestTag;
+ property public final androidx.ui.semantics.SemanticsPropertyKey<androidx.ui.text.style.TextDirection> TextDirection;
+ field public static final androidx.ui.semantics.SemanticsProperties! INSTANCE;
+ }
+
+ public final class SemanticsPropertiesKt {
+ ctor public SemanticsPropertiesKt();
+ method public static String getAccessibilityLabel(androidx.ui.semantics.SemanticsPropertyReceiver);
+ method public static String getAccessibilityValue(androidx.ui.semantics.SemanticsPropertyReceiver);
+ method public static java.util.List<androidx.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<kotlin.Unit>>> getCustomActions(androidx.ui.semantics.SemanticsPropertyReceiver);
+ method public static boolean getEnabled(androidx.ui.semantics.SemanticsPropertyReceiver);
+ method public static boolean getHidden(androidx.ui.semantics.SemanticsPropertyReceiver);
+ method public static androidx.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<kotlin.Unit>> getOnClick(androidx.ui.semantics.SemanticsPropertyReceiver);
+ method public static String getTestTag(androidx.ui.semantics.SemanticsPropertyReceiver);
+ method public static androidx.ui.text.style.TextDirection getTextDirection(androidx.ui.semantics.SemanticsPropertyReceiver);
+ method public static void onClick(androidx.ui.semantics.SemanticsPropertyReceiver, String? label = null, kotlin.jvm.functions.Function0<kotlin.Unit> action);
+ method public static void setAccessibilityLabel(androidx.ui.semantics.SemanticsPropertyReceiver, String p);
+ method public static void setAccessibilityValue(androidx.ui.semantics.SemanticsPropertyReceiver, String p);
+ method public static void setCustomActions(androidx.ui.semantics.SemanticsPropertyReceiver, java.util.List<androidx.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<kotlin.Unit>>> p);
+ method public static void setEnabled(androidx.ui.semantics.SemanticsPropertyReceiver, boolean p);
+ method public static void setHidden(androidx.ui.semantics.SemanticsPropertyReceiver, boolean p);
+ method public static void setOnClick(androidx.ui.semantics.SemanticsPropertyReceiver, androidx.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<kotlin.Unit>> p);
+ method public static void setTestTag(androidx.ui.semantics.SemanticsPropertyReceiver, String p);
+ method public static void setTextDirection(androidx.ui.semantics.SemanticsPropertyReceiver, androidx.ui.text.style.TextDirection p);
}
}
diff --git a/ui/ui-framework/api/restricted_current.txt b/ui/ui-framework/api/restricted_current.txt
index 8ecec90..fbd9ec5 100644
--- a/ui/ui-framework/api/restricted_current.txt
+++ b/ui/ui-framework/api/restricted_current.txt
@@ -6,12 +6,23 @@
method public static void Clip(androidx.ui.engine.geometry.Shape shape, kotlin.jvm.functions.Function0<kotlin.Unit> children);
}
+ public final class ComplexLayoutBlockReceiver implements androidx.ui.core.DensityReceiver {
+ method public androidx.ui.core.Density getDensity();
+ method public androidx.ui.core.LayoutResult layoutResult(androidx.ui.core.IntPx width, androidx.ui.core.IntPx height, kotlin.jvm.functions.Function1<? super androidx.ui.core.PositioningBlockReceiver,kotlin.Unit> block);
+ method public androidx.ui.core.IntPx maxIntrinsicHeight(androidx.ui.core.Measurable, androidx.ui.core.IntPx w);
+ method public androidx.ui.core.IntPx maxIntrinsicWidth(androidx.ui.core.Measurable, androidx.ui.core.IntPx h);
+ method public androidx.ui.core.Placeable measure(androidx.ui.core.Measurable, androidx.ui.core.Constraints constraints);
+ method public androidx.ui.core.IntPx minIntrinsicHeight(androidx.ui.core.Measurable, androidx.ui.core.IntPx w);
+ method public androidx.ui.core.IntPx minIntrinsicWidth(androidx.ui.core.Measurable, androidx.ui.core.IntPx h);
+ property public androidx.ui.core.Density density;
+ }
+
public final class ComplexLayoutReceiver {
- method public void layout(kotlin.jvm.functions.Function3<? super androidx.ui.core.LayoutBlockReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.Constraints,kotlin.Unit> layoutBlock);
- method public void maxIntrinsicHeight(kotlin.jvm.functions.Function3<? super androidx.ui.core.IntrinsicMeasurementsReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.IntPx,androidx.ui.core.IntPx> maxIntrinsicHeightBlock);
- method public void maxIntrinsicWidth(kotlin.jvm.functions.Function3<? super androidx.ui.core.IntrinsicMeasurementsReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.IntPx,androidx.ui.core.IntPx> maxIntrinsicWidthBlock);
- method public void minIntrinsicHeight(kotlin.jvm.functions.Function3<? super androidx.ui.core.IntrinsicMeasurementsReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.IntPx,androidx.ui.core.IntPx> minIntrinsicHeightBlock);
- method public void minIntrinsicWidth(kotlin.jvm.functions.Function3<? super androidx.ui.core.IntrinsicMeasurementsReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.IntPx,androidx.ui.core.IntPx> minIntrinsicWidthBlock);
+ method public void layout(kotlin.jvm.functions.Function3<? super androidx.ui.core.ComplexLayoutBlockReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.Constraints,androidx.ui.core.LayoutResult> layoutBlock);
+ method public void maxIntrinsicHeight(kotlin.jvm.functions.Function3<? super androidx.ui.core.IntrinsicMeasurementReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.IntPx,androidx.ui.core.IntPx> maxIntrinsicHeightBlock);
+ method public void maxIntrinsicWidth(kotlin.jvm.functions.Function3<? super androidx.ui.core.IntrinsicMeasurementReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.IntPx,androidx.ui.core.IntPx> maxIntrinsicWidthBlock);
+ method public void minIntrinsicHeight(kotlin.jvm.functions.Function3<? super androidx.ui.core.IntrinsicMeasurementReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.IntPx,androidx.ui.core.IntPx> minIntrinsicHeightBlock);
+ method public void minIntrinsicWidth(kotlin.jvm.functions.Function3<? super androidx.ui.core.IntrinsicMeasurementReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.IntPx,androidx.ui.core.IntPx> minIntrinsicWidthBlock);
}
public final class DrawKt {
@@ -32,12 +43,7 @@
method public androidx.ui.text.TextStyle? getTextStyle();
}
- public final class InputFieldKt {
- ctor public InputFieldKt();
- method public static void InputField(androidx.ui.input.EditorState value, androidx.ui.core.EditorStyle editorStyle, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function1<? super androidx.ui.input.EditorState,kotlin.Unit> onValueChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.input.ImeAction,kotlin.Unit> onImeActionPerformed = {});
- }
-
- public final class IntrinsicMeasurementsReceiver implements androidx.ui.core.DensityReceiver {
+ public final class IntrinsicMeasurementReceiver implements androidx.ui.core.DensityReceiver {
method public androidx.ui.core.Density getDensity();
method public androidx.ui.core.IntPx maxIntrinsicHeight(androidx.ui.core.Measurable, androidx.ui.core.IntPx w);
method public androidx.ui.core.IntPx maxIntrinsicWidth(androidx.ui.core.Measurable, androidx.ui.core.IntPx h);
@@ -47,13 +53,10 @@
}
public final class LayoutBlockReceiver implements androidx.ui.core.DensityReceiver {
+ method public operator java.util.List<androidx.ui.core.Measurable> get(java.util.List<? extends androidx.ui.core.Measurable>, kotlin.jvm.functions.Function0<kotlin.Unit> children);
method public androidx.ui.core.Density getDensity();
- method public void layoutResult(androidx.ui.core.IntPx width, androidx.ui.core.IntPx height, kotlin.jvm.functions.Function1<? super androidx.ui.core.PositioningBlockReceiver,kotlin.Unit> block);
- method public androidx.ui.core.IntPx maxIntrinsicHeight(androidx.ui.core.Measurable, androidx.ui.core.IntPx w);
- method public androidx.ui.core.IntPx maxIntrinsicWidth(androidx.ui.core.Measurable, androidx.ui.core.IntPx h);
+ method public androidx.ui.core.LayoutResult layout(androidx.ui.core.IntPx width, androidx.ui.core.IntPx height, kotlin.jvm.functions.Function1<? super androidx.ui.core.PositioningBlockReceiver,kotlin.Unit> block);
method public androidx.ui.core.Placeable measure(androidx.ui.core.Measurable, androidx.ui.core.Constraints constraints);
- method public androidx.ui.core.IntPx minIntrinsicHeight(androidx.ui.core.Measurable, androidx.ui.core.IntPx w);
- method public androidx.ui.core.IntPx minIntrinsicWidth(androidx.ui.core.Measurable, androidx.ui.core.IntPx h);
property public androidx.ui.core.Density density;
}
@@ -72,19 +75,18 @@
public final class LayoutKt {
ctor public LayoutKt();
method public static void ComplexLayout(kotlin.jvm.functions.Function0<kotlin.Unit> children, kotlin.jvm.functions.Function1<? super androidx.ui.core.ComplexLayoutReceiver,kotlin.Unit> block);
- method public static void Layout(kotlin.jvm.functions.Function0<kotlin.Unit> children, kotlin.jvm.functions.Function3<? super androidx.ui.core.LayoutReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.Constraints,kotlin.Unit> layoutBlock);
- method public static void Layout(kotlin.jvm.functions.Function0<kotlin.Unit>![] childrenArray, kotlin.jvm.functions.Function3<? super androidx.ui.core.LayoutReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.Constraints,kotlin.Unit> layoutBlock);
+ method public static void Layout(kotlin.jvm.functions.Function0<kotlin.Unit> children, kotlin.jvm.functions.Function3<? super androidx.ui.core.LayoutBlockReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.Constraints,androidx.ui.core.LayoutResult> layoutBlock);
+ method public static void Layout(kotlin.jvm.functions.Function0<kotlin.Unit>![] childrenArray, kotlin.jvm.functions.Function3<? super androidx.ui.core.LayoutBlockReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.Constraints,androidx.ui.core.LayoutResult> layoutBlock);
method public static void OnChildPositioned(kotlin.jvm.functions.Function1<? super androidx.ui.core.LayoutCoordinates,kotlin.Unit> onPositioned, kotlin.jvm.functions.Function0<kotlin.Unit> children);
method public static void OnPositioned(kotlin.jvm.functions.Function1<? super androidx.ui.core.LayoutCoordinates,kotlin.Unit> onPositioned);
method public static void WithConstraints(kotlin.jvm.functions.Function1<? super androidx.ui.core.Constraints,kotlin.Unit> children);
}
- public final class LayoutReceiver implements androidx.ui.core.DensityReceiver {
- method public operator java.util.List<androidx.ui.core.Measurable> get(java.util.List<? extends androidx.ui.core.Measurable>, kotlin.jvm.functions.Function0<kotlin.Unit> children);
- method public androidx.ui.core.Density getDensity();
- method public void layout(androidx.ui.core.IntPx width, androidx.ui.core.IntPx height, kotlin.jvm.functions.Function1<? super androidx.ui.core.PositioningBlockReceiver,kotlin.Unit> block);
- method public androidx.ui.core.Placeable measure(androidx.ui.core.Measurable, androidx.ui.core.Constraints constraints);
- property public androidx.ui.core.Density density;
+ public final class LayoutResult {
+ field public static final androidx.ui.core.LayoutResult.Companion! Companion;
+ }
+
+ public static final class LayoutResult.Companion {
}
public interface Measurable {
@@ -92,6 +94,11 @@
property public abstract Object? parentData;
}
+ public interface OffsetMap {
+ method public int originalToTransformed(int offset);
+ method public int transformedToOriginal(int offset);
+ }
+
public final class OpacityKt {
ctor public OpacityKt();
method public static void Opacity(@FloatRange(from=0.0, to=1.0) float opacity, kotlin.jvm.functions.Function0<kotlin.Unit> children);
@@ -102,6 +109,13 @@
method public static void ParentData(Object data, kotlin.jvm.functions.Function0<kotlin.Unit> children);
}
+ public final class PasswordVisualTransformation implements androidx.ui.core.VisualTransformation {
+ ctor public PasswordVisualTransformation(char mask);
+ ctor public PasswordVisualTransformation();
+ method public androidx.ui.core.TransformedText filter(androidx.ui.text.AnnotatedString text);
+ method public char getMask();
+ }
+
public abstract class Placeable {
ctor public Placeable();
method public abstract androidx.ui.core.IntPx getHeight();
@@ -127,11 +141,6 @@
method public static void RepaintBoundary(String? name = null, kotlin.jvm.functions.Function0<kotlin.Unit> children);
}
- public final class SemanticsKt {
- ctor public SemanticsKt();
- method public static void Semantics(boolean container = false, boolean explicitChildNodes = false, Boolean? enabled = null, Boolean? checked = null, Boolean? selected = null, Boolean? button = null, Boolean? inMutuallyExclusiveGroup = null, Boolean? hidden = null, String? label = null, String? value = null, androidx.ui.text.style.TextDirection? textDirection = null, String? testTag = null, java.util.List<? extends androidx.ui.core.semantics.SemanticsAction<?>> actions = emptyList(), kotlin.jvm.functions.Function0<kotlin.Unit> children);
- }
-
public final class TestTagProviderKt {
ctor public TestTagProviderKt();
method public static void TestTag(String tag, kotlin.jvm.functions.Function0<kotlin.Unit> children);
@@ -139,6 +148,11 @@
method public static androidx.compose.Ambient<java.lang.String> getTestTagAmbient();
}
+ public final class TextFieldKt {
+ ctor public TextFieldKt();
+ method public static void TextField(androidx.ui.input.EditorModel value, androidx.ui.core.EditorStyle editorStyle, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function1<? super androidx.ui.input.EditorModel,kotlin.Unit> onValueChange = {}, kotlin.jvm.functions.Function0<kotlin.Unit> onFocus = {}, kotlin.jvm.functions.Function0<kotlin.Unit> onBlur = {}, kotlin.jvm.functions.Function1<? super androidx.ui.input.ImeAction,kotlin.Unit> onImeActionPerformed = {}, androidx.ui.core.VisualTransformation? visualTransformation = null);
+ }
+
public final class TextKt {
ctor public TextKt();
method public static void CurrentTextStyleProvider(androidx.ui.text.TextStyle value, kotlin.jvm.functions.Function0<kotlin.Unit> children);
@@ -174,12 +188,30 @@
method public androidx.ui.core.TextSpanComposition getComposer();
}
+ public final class TransformedText {
+ ctor public TransformedText(androidx.ui.text.AnnotatedString transformedText, androidx.ui.core.OffsetMap offsetMap);
+ method public androidx.ui.text.AnnotatedString component1();
+ method public androidx.ui.core.OffsetMap component2();
+ method public androidx.ui.core.TransformedText copy(androidx.ui.text.AnnotatedString transformedText, androidx.ui.core.OffsetMap offsetMap);
+ method public androidx.ui.core.OffsetMap getOffsetMap();
+ method public androidx.ui.text.AnnotatedString getTransformedText();
+ }
+
+ public interface VisualTransformation {
+ method public androidx.ui.core.TransformedText filter(androidx.ui.text.AnnotatedString text);
+ }
+
+ public final class VisualTransformationKt {
+ ctor public VisualTransformationKt();
+ }
+
public final class WrapperKt {
ctor public WrapperKt();
method public static void ComposeView(kotlin.jvm.functions.Function0<kotlin.Unit> children);
method public static void WithDensity(kotlin.jvm.functions.Function1<? super androidx.ui.core.DensityReceiver,kotlin.Unit> block);
method @CheckResult(suggest="+") public static androidx.compose.Effect<androidx.ui.core.Density> ambientDensity();
method public static androidx.compose.Ambient<android.content.Context> getContextAmbient();
+ method public static androidx.compose.Ambient<kotlin.coroutines.CoroutineContext> getCoroutineContextAmbient();
method public static androidx.compose.Ambient<androidx.ui.core.Density> getDensityAmbient();
method public static androidx.compose.CompositionContext? setContent(android.app.Activity, kotlin.jvm.functions.Function0<kotlin.Unit> content);
method public static androidx.compose.CompositionContext? setContent(android.view.ViewGroup, kotlin.jvm.functions.Function0<kotlin.Unit> content);
@@ -211,6 +243,11 @@
method public static androidx.ui.core.Duration getZoomControlsTimeout();
}
+ public final class DoubleTapGestureDetectorKt {
+ ctor public DoubleTapGestureDetectorKt();
+ method public static void DoubleTapGestureDetector(kotlin.jvm.functions.Function1<? super androidx.ui.core.PxPosition,kotlin.Unit> onDoubleTap, kotlin.jvm.functions.Function0<kotlin.Unit> children);
+ }
+
public final class DragGestureDetectorKt {
ctor public DragGestureDetectorKt();
method public static void DragGestureDetector(kotlin.jvm.functions.Function1<? super androidx.ui.core.Direction,java.lang.Boolean>? canDrag = null, androidx.ui.core.gesture.DragObserver? dragObserver = null, kotlin.jvm.functions.Function0<kotlin.Unit> children);
@@ -222,6 +259,11 @@
method public default void onStop(androidx.ui.core.PxPosition velocity);
}
+ public final class LongPressGestureDetectorKt {
+ ctor public LongPressGestureDetectorKt();
+ method public static void LongPressGestureDetector(kotlin.jvm.functions.Function1<? super androidx.ui.core.PxPosition,kotlin.Unit> onLongPress, kotlin.jvm.functions.Function0<kotlin.Unit> children);
+ }
+
public final class PressGestureDetectorKt {
ctor public PressGestureDetectorKt();
method public static void PressGestureDetector(kotlin.jvm.functions.Function1<? super androidx.ui.core.PxPosition,kotlin.Unit>? onPress = null, kotlin.jvm.functions.Function0<kotlin.Unit>? onRelease = null, kotlin.jvm.functions.Function0<kotlin.Unit>? onCancel = null, kotlin.jvm.functions.Function0<kotlin.Unit> children);
@@ -261,16 +303,16 @@
package androidx.ui.core.selection {
public final class Selection {
- ctor public Selection(androidx.ui.engine.geometry.Rect startOffset, androidx.ui.engine.geometry.Rect endOffset, androidx.ui.core.LayoutCoordinates? startLayoutCoordinates, androidx.ui.core.LayoutCoordinates? endLayoutCoordinates);
- method public androidx.ui.engine.geometry.Rect component1();
- method public androidx.ui.engine.geometry.Rect component2();
+ ctor public Selection(androidx.ui.core.PxPosition startCoordinates, androidx.ui.core.PxPosition endCoordinates, androidx.ui.core.LayoutCoordinates? startLayoutCoordinates, androidx.ui.core.LayoutCoordinates? endLayoutCoordinates);
+ method public androidx.ui.core.PxPosition component1();
+ method public androidx.ui.core.PxPosition component2();
method public androidx.ui.core.LayoutCoordinates? component3();
method public androidx.ui.core.LayoutCoordinates? component4();
- method public androidx.ui.core.selection.Selection copy(androidx.ui.engine.geometry.Rect startOffset, androidx.ui.engine.geometry.Rect endOffset, androidx.ui.core.LayoutCoordinates? startLayoutCoordinates, androidx.ui.core.LayoutCoordinates? endLayoutCoordinates);
+ method public androidx.ui.core.selection.Selection copy(androidx.ui.core.PxPosition startCoordinates, androidx.ui.core.PxPosition endCoordinates, androidx.ui.core.LayoutCoordinates? startLayoutCoordinates, androidx.ui.core.LayoutCoordinates? endLayoutCoordinates);
+ method public androidx.ui.core.PxPosition getEndCoordinates();
method public androidx.ui.core.LayoutCoordinates? getEndLayoutCoordinates();
- method public androidx.ui.engine.geometry.Rect getEndOffset();
+ method public androidx.ui.core.PxPosition getStartCoordinates();
method public androidx.ui.core.LayoutCoordinates? getStartLayoutCoordinates();
- method public androidx.ui.engine.geometry.Rect getStartOffset();
}
public final class SelectionContainerKt {
@@ -299,143 +341,74 @@
package androidx.ui.core.vectorgraphics {
- public interface Brush {
- method public void applyBrush(androidx.ui.painting.Paint p);
+ public final class VectorAsset {
+ method public androidx.ui.core.Px getDefaultHeight();
+ method public androidx.ui.core.Px getDefaultWidth();
+ method public String getName();
+ method public androidx.ui.core.vectorgraphics.VectorGroup getRoot();
+ method public float getViewportHeight();
+ method public float getViewportWidth();
}
- public final class BrushKt {
- ctor public BrushKt();
- method public static androidx.ui.core.vectorgraphics.Brush getEmptyBrush();
- method public static androidx.ui.core.vectorgraphics.Brush obtainBrush(Object? brush);
+ public final class VectorAssetBuilder {
+ ctor public VectorAssetBuilder(String name, androidx.ui.core.Px defaultWidth, androidx.ui.core.Px defaultHeight, float viewportWidth, float viewportHeight);
+ method public androidx.ui.core.vectorgraphics.VectorAssetBuilder addPath(Object? pathData, String name = "", Object fill = EmptyBrush, float fillAlpha = 1.0f, Object stroke = EmptyBrush, float strokeAlpha = 1.0f, float strokeLineWidth = 0.0f, androidx.ui.painting.StrokeCap strokeLineCap = DefaultStrokeLineCap, androidx.ui.painting.StrokeJoin strokeLineJoin = DefaultStrokeLineJoin, float strokeLineMiter = 4.0f);
+ method public androidx.ui.core.vectorgraphics.VectorAsset build();
+ method public void ensureNotConsumed();
+ method public androidx.ui.core.Px getDefaultHeight();
+ method public androidx.ui.core.Px getDefaultWidth();
+ method public String getName();
+ method public float getViewportHeight();
+ method public float getViewportWidth();
+ method public androidx.ui.core.vectorgraphics.VectorAssetBuilder popGroup();
+ method public androidx.ui.core.vectorgraphics.VectorAssetBuilder pushGroup(String name = "", float rotate = 0.0f, float pivotX = 0.0f, float pivotY = 0.0f, float scaleX = 1.0f, float scaleY = 1.0f, float translationX = 0.0f, float translationY = 0.0f, Object? clipPathData = EmptyPath);
}
- public final class LinearGradient implements androidx.ui.core.vectorgraphics.Brush {
- ctor public LinearGradient(kotlin.Pair<androidx.ui.graphics.Color,java.lang.Float>![] colorStops, float startX, float startY, float endX, float endY, androidx.ui.painting.TileMode tileMode);
- method public void applyBrush(androidx.ui.painting.Paint p);
- method public float getEndX();
- method public float getEndY();
- method public float getStartX();
- method public float getStartY();
- method public androidx.ui.painting.TileMode getTileMode();
+ public final class VectorAssetKt {
+ ctor public VectorAssetKt();
+ method public static void DrawVector(androidx.ui.core.vectorgraphics.VectorAsset vectorImage);
}
- public final class PathBuilder {
- ctor public PathBuilder();
- method public androidx.ui.core.vectorgraphics.PathBuilder arcTo(float horizontalEllipseRadius, float verticalEllipseRadius, float theta, float largeArcFlag, float sweepFlag, float x1, float y1);
- method public androidx.ui.core.vectorgraphics.PathBuilder arcToRelative(float a, float b, float theta, float largeArcFlag, float sweepFlag, float x1, float y1);
- method public androidx.ui.core.vectorgraphics.PathBuilder close();
- method public androidx.ui.core.vectorgraphics.PathBuilder curveTo(float x1, float y1, float x2, float y2, float x3, float y3);
- method public androidx.ui.core.vectorgraphics.PathBuilder curveToRelative(float dx1, float dy1, float dx2, float dy2, float dx3, float dy3);
- method public androidx.ui.core.vectorgraphics.PathNode![] getNodes();
- method public androidx.ui.core.vectorgraphics.PathBuilder horizontalLineTo(float x);
- method public androidx.ui.core.vectorgraphics.PathBuilder horizontalLineToRelative(float x);
- method public androidx.ui.core.vectorgraphics.PathBuilder lineTo(float x, float y);
- method public androidx.ui.core.vectorgraphics.PathBuilder lineToRelative(float x, float y);
- method public androidx.ui.core.vectorgraphics.PathBuilder moveTo(float x, float y);
- method public androidx.ui.core.vectorgraphics.PathBuilder moveToRelative(float x, float y);
- method public androidx.ui.core.vectorgraphics.PathBuilder quadTo(float x1, float y1, float x2, float y2);
- method public androidx.ui.core.vectorgraphics.PathBuilder quadToRelative(float x1, float y1, float x2, float y2);
- method public androidx.ui.core.vectorgraphics.PathBuilder reflectiveCurveTo(float x1, float y1, float x2, float y2);
- method public androidx.ui.core.vectorgraphics.PathBuilder reflectiveCurveToRelative(float x1, float y1, float x2, float y2);
- method public androidx.ui.core.vectorgraphics.PathBuilder reflectiveQuadTo(float x1, float y1);
- method public androidx.ui.core.vectorgraphics.PathBuilder reflectiveQuadToRelative(float x1, float y1);
- method public androidx.ui.core.vectorgraphics.PathBuilder verticalLineTo(float y);
- method public androidx.ui.core.vectorgraphics.PathBuilder verticalLineToRelative(float y);
+ public final class VectorComposeKt {
+ ctor public VectorComposeKt();
+ method public static void DrawVector(float viewportWidth, float viewportHeight, androidx.ui.core.Px defaultWidth = Px(viewportWidth), androidx.ui.core.Px defaultHeight = Px(viewportHeight), String name = "", kotlin.jvm.functions.Function1<? super androidx.ui.vector.VectorScope,kotlin.Unit> children);
+ method public static void Group(androidx.ui.vector.VectorScope, String name = "", float rotation = 0.0f, float pivotX = 0.0f, float pivotY = 0.0f, float scaleX = 1.0f, float scaleY = 1.0f, float translationX = 0.0f, float translationY = 0.0f, Object? clipPathData = EmptyPath, kotlin.jvm.functions.Function1<? super androidx.ui.vector.VectorScope,kotlin.Unit> children);
+ method public static void Path(androidx.ui.vector.VectorScope, Object? pathData, String name = "", Object fill = EmptyBrush, float fillAlpha = 1.0f, Object stroke = EmptyBrush, float strokeAlpha = 1.0f, float strokeLineWidth = 0.0f, androidx.ui.painting.StrokeCap strokeLineCap = DefaultStrokeLineCap, androidx.ui.painting.StrokeJoin strokeLineJoin = DefaultStrokeLineJoin, float strokeLineMiter = 4.0f);
}
- public enum PathCommand {
- method public final char toKey();
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand ArcTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand Close;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand CurveTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand HorizontalLineTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand LineTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand MoveTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand QuadTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand ReflectiveCurveTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand ReflectiveQuadTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand RelativeArcTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand RelativeClose;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand RelativeCurveTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand RelativeHorizontalTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand RelativeLineTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand RelativeMoveTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand RelativeQuadTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand RelativeReflectiveCurveTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand RelativeReflectiveQuadTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand RelativeVerticalTo;
- enum_constant public static final androidx.ui.core.vectorgraphics.PathCommand VerticalLineTo;
+ public final class VectorGroup extends androidx.ui.core.vectorgraphics.VectorNode implements java.lang.Iterable<androidx.ui.core.vectorgraphics.VectorNode> kotlin.jvm.internal.markers.KMappedMarker {
+ ctor public VectorGroup(String name, float rotation, float pivotX, float pivotY, float scaleX, float scaleY, float translationX, float translationY, Object? clipPathData);
+ ctor public VectorGroup();
+ method public operator androidx.ui.core.vectorgraphics.VectorNode get(int index);
+ method public Object? getClipPathData();
+ method public String getName();
+ method public float getPivotX();
+ method public float getPivotY();
+ method public float getRotation();
+ method public float getScaleX();
+ method public float getScaleY();
+ method public int getSize();
+ method public float getTranslationX();
+ method public float getTranslationY();
+ method public java.util.Iterator<androidx.ui.core.vectorgraphics.VectorNode> iterator();
+ property public final int size;
}
- public final class PathCommandKt {
- ctor public PathCommandKt();
- method public static androidx.ui.core.vectorgraphics.PathCommand toPathCommand(char) throws java.lang.IllegalArgumentException;
+ public abstract sealed class VectorNode {
}
- public final class PathDelegate {
- ctor public PathDelegate(kotlin.jvm.functions.Function1<? super androidx.ui.core.vectorgraphics.PathBuilder,kotlin.Unit> delegate);
- method public kotlin.jvm.functions.Function1<androidx.ui.core.vectorgraphics.PathBuilder,kotlin.Unit> getDelegate();
- }
-
- public final class PathNode {
- ctor public PathNode(androidx.ui.core.vectorgraphics.PathCommand command, float[] args);
- method public androidx.ui.core.vectorgraphics.PathCommand component1();
- method public float[] component2();
- method public androidx.ui.core.vectorgraphics.PathNode copy(androidx.ui.core.vectorgraphics.PathCommand command, float[] args);
- method public float[] getArgs();
- method public androidx.ui.core.vectorgraphics.PathCommand getCommand();
- }
-
- public final class PathNodeKt {
- ctor public PathNodeKt();
- method public static operator StringBuilder plus(StringBuilder, androidx.ui.core.vectorgraphics.PathNode node);
- }
-
- public final class PathParser {
- ctor public PathParser();
- method public androidx.ui.core.vectorgraphics.PathParser addPathNodes(androidx.ui.core.vectorgraphics.PathNode![] nodes);
- method public void clear();
- method public androidx.ui.core.vectorgraphics.PathParser parsePathString(String pathData) throws java.lang.IllegalArgumentException, java.lang.NumberFormatException;
- method public androidx.ui.core.vectorgraphics.PathNode![] toNodes();
- method public androidx.ui.painting.Path toPath(androidx.ui.painting.Path target = Path());
- }
-
- public final class PathParserKt {
- ctor public PathParserKt();
- }
-
- public final class RadialGradient implements androidx.ui.core.vectorgraphics.Brush {
- ctor public RadialGradient(kotlin.Pair<androidx.ui.graphics.Color,java.lang.Float>![] colorStops, float centerX, float centerY, float radius, androidx.ui.painting.TileMode tileMode);
- method public void applyBrush(androidx.ui.painting.Paint p);
- }
-
- public final class SolidColor implements androidx.ui.core.vectorgraphics.Brush {
- ctor public SolidColor(androidx.ui.graphics.Color value);
- method public void applyBrush(androidx.ui.painting.Paint p);
- }
-
- public final class VectorKt {
- ctor public VectorKt();
- method public static androidx.ui.core.vectorgraphics.PathNode![] addPathNodes(String? pathStr);
- method public static android.view.View? adoptVectorGraphic(Object? parent, Object? child);
- method public static androidx.ui.painting.StrokeCap getDefaultStrokeLineCap();
- method public static androidx.ui.painting.StrokeJoin getDefaultStrokeLineJoin();
- method public static androidx.ui.core.vectorgraphics.PathNode![] getEmptyPath();
- method public static void group(String name = "", float rotate = 0.0f, float pivotX = 0.0f, float pivotY = 0.0f, float scaleX = 1.0f, float scaleY = 1.0f, float translateX = 0.0f, float translateY = 0.0f, Object? clipPathData = EmptyPath, kotlin.jvm.functions.Function0<kotlin.Unit> childNodes);
- method public static void path(Object? pathData, String name = "", Object fill = EmptyBrush, float fillAlpha = 1.0f, Object stroke = EmptyBrush, float strokeAlpha = 1.0f, float strokeLineWidth = 0.0f, androidx.ui.painting.StrokeCap strokeLineCap = DefaultStrokeLineCap, androidx.ui.painting.StrokeJoin strokeLineJoin = DefaultStrokeLineJoin, float strokeLineMiter = 4.0f);
- method public static void vector(String name = "", float viewportWidth, float viewportHeight, float defaultWidth = viewportWidth, float defaultHeight = viewportHeight, kotlin.jvm.functions.Function0<kotlin.Unit> children);
- field public static final float DefaultAlpha = 1.0f;
- field public static final String DefaultGroupName = "";
- field public static final String DefaultPathName = "";
- field public static final float DefaultPivotX = 0.0f;
- field public static final float DefaultPivotY = 0.0f;
- field public static final float DefaultRotate = 0.0f;
- field public static final float DefaultScaleX = 1.0f;
- field public static final float DefaultScaleY = 1.0f;
- field public static final float DefaultStrokeLineMiter = 4.0f;
- field public static final float DefaultStrokeLineWidth = 0.0f;
- field public static final float DefaultTranslateX = 0.0f;
- field public static final float DefaultTranslateY = 0.0f;
+ public final class VectorPath extends androidx.ui.core.vectorgraphics.VectorNode {
+ ctor public VectorPath(String name, Object? pathData, Object fill, float fillAlpha, Object stroke, float strokeAlpha, float strokeLineWidth, androidx.ui.painting.StrokeCap strokeLineCap, androidx.ui.painting.StrokeJoin strokeLineJoin, float strokeLineMiter);
+ method public Object getFill();
+ method public float getFillAlpha();
+ method public String getName();
+ method public Object? getPathData();
+ method public Object getStroke();
+ method public float getStrokeAlpha();
+ method public androidx.ui.painting.StrokeCap getStrokeLineCap();
+ method public androidx.ui.painting.StrokeJoin getStrokeLineJoin();
+ method public float getStrokeLineMiter();
+ method public float getStrokeLineWidth();
}
}
@@ -444,7 +417,70 @@
public final class VectorResourceKt {
ctor public VectorResourceKt();
- method public static void vectorResource(android.content.res.Resources res, int resId);
+ method public static void VectorResource(int resId);
+ method public static androidx.ui.core.vectorgraphics.VectorAsset loadVectorResource(android.content.res.Resources.Theme? theme = null, android.content.res.Resources res, int resId) throws org.xmlpull.v1.XmlPullParserException;
+ }
+
+ public final class XmlVectorParserKt {
+ ctor public XmlVectorParserKt();
+ }
+
+}
+
+package androidx.ui.semantics {
+
+ public final class SemanticsActions {
+ ctor public SemanticsActions();
+ field public static final androidx.ui.semantics.SemanticsActions.Companion! Companion;
+ }
+
+ public static final class SemanticsActions.Companion {
+ method public androidx.ui.semantics.SemanticsPropertyKey<java.util.List<androidx.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<kotlin.Unit>>>> getCustomActions();
+ method public androidx.ui.semantics.SemanticsPropertyKey<androidx.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<kotlin.Unit>>> getOnClick();
+ property public final androidx.ui.semantics.SemanticsPropertyKey<java.util.List<androidx.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<kotlin.Unit>>>> CustomActions;
+ property public final androidx.ui.semantics.SemanticsPropertyKey<androidx.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<kotlin.Unit>>> OnClick;
+ }
+
+ public final class SemanticsKt {
+ ctor public SemanticsKt();
+ method public static void Semantics(boolean container = false, boolean explicitChildNodes = false, kotlin.jvm.functions.Function1<? super androidx.ui.semantics.SemanticsPropertyReceiver,kotlin.Unit>? properties = null, kotlin.jvm.functions.Function0<kotlin.Unit> children);
+ }
+
+ public final class SemanticsProperties {
+ method public androidx.ui.semantics.SemanticsPropertyKey<java.lang.String> getAccessibilityLabel();
+ method public androidx.ui.semantics.SemanticsPropertyKey<java.lang.String> getAccessibilityValue();
+ method public androidx.ui.semantics.SemanticsPropertyKey<java.lang.Boolean> getEnabled();
+ method public androidx.ui.semantics.SemanticsPropertyKey<java.lang.Boolean> getHidden();
+ method public androidx.ui.semantics.SemanticsPropertyKey<java.lang.String> getTestTag();
+ method public androidx.ui.semantics.SemanticsPropertyKey<androidx.ui.text.style.TextDirection> getTextDirection();
+ property public final androidx.ui.semantics.SemanticsPropertyKey<java.lang.String> AccessibilityLabel;
+ property public final androidx.ui.semantics.SemanticsPropertyKey<java.lang.String> AccessibilityValue;
+ property public final androidx.ui.semantics.SemanticsPropertyKey<java.lang.Boolean> Enabled;
+ property public final androidx.ui.semantics.SemanticsPropertyKey<java.lang.Boolean> Hidden;
+ property public final androidx.ui.semantics.SemanticsPropertyKey<java.lang.String> TestTag;
+ property public final androidx.ui.semantics.SemanticsPropertyKey<androidx.ui.text.style.TextDirection> TextDirection;
+ field public static final androidx.ui.semantics.SemanticsProperties! INSTANCE;
+ }
+
+ public final class SemanticsPropertiesKt {
+ ctor public SemanticsPropertiesKt();
+ method public static String getAccessibilityLabel(androidx.ui.semantics.SemanticsPropertyReceiver);
+ method public static String getAccessibilityValue(androidx.ui.semantics.SemanticsPropertyReceiver);
+ method public static java.util.List<androidx.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<kotlin.Unit>>> getCustomActions(androidx.ui.semantics.SemanticsPropertyReceiver);
+ method public static boolean getEnabled(androidx.ui.semantics.SemanticsPropertyReceiver);
+ method public static boolean getHidden(androidx.ui.semantics.SemanticsPropertyReceiver);
+ method public static androidx.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<kotlin.Unit>> getOnClick(androidx.ui.semantics.SemanticsPropertyReceiver);
+ method public static String getTestTag(androidx.ui.semantics.SemanticsPropertyReceiver);
+ method public static androidx.ui.text.style.TextDirection getTextDirection(androidx.ui.semantics.SemanticsPropertyReceiver);
+ method public static void onClick(androidx.ui.semantics.SemanticsPropertyReceiver, String? label = null, kotlin.jvm.functions.Function0<kotlin.Unit> action);
+ method public static void setAccessibilityLabel(androidx.ui.semantics.SemanticsPropertyReceiver, String p);
+ method public static void setAccessibilityValue(androidx.ui.semantics.SemanticsPropertyReceiver, String p);
+ method public static void setCustomActions(androidx.ui.semantics.SemanticsPropertyReceiver, java.util.List<androidx.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<kotlin.Unit>>> p);
+ method public static void setEnabled(androidx.ui.semantics.SemanticsPropertyReceiver, boolean p);
+ method public static void setHidden(androidx.ui.semantics.SemanticsPropertyReceiver, boolean p);
+ method public static void setOnClick(androidx.ui.semantics.SemanticsPropertyReceiver, androidx.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<kotlin.Unit>> p);
+ method public static void setTestTag(androidx.ui.semantics.SemanticsPropertyReceiver, String p);
+ method public static void setTextDirection(androidx.ui.semantics.SemanticsPropertyReceiver, androidx.ui.text.style.TextDirection p);
}
}
diff --git a/ui/ui-framework/build.gradle b/ui/ui-framework/build.gradle
index 6d07e4c..5d162f6 100644
--- a/ui/ui-framework/build.gradle
+++ b/ui/ui-framework/build.gradle
@@ -30,7 +30,7 @@
dependencies {
kotlinPlugin project(path: ":compose:compose-compiler", configuration: "embeddablePlugin")
-
+ implementation(KOTLIN_COMPOSE_COROUTINES)
implementation(KOTLIN_COMPOSE_STDLIB)
// TODO: Non-Kotlin dependency, move to Android-specific code
@@ -40,6 +40,7 @@
implementation project(":ui:ui-core")
implementation project(":ui:ui-platform")
implementation project(":ui:ui-text")
+ implementation project(":ui:ui-vector")
testImplementation(ANDROIDX_TEST_RULES)
testImplementation(ANDROIDX_TEST_RUNNER)
diff --git a/ui/ui-framework/integration-tests/framework-demos/build.gradle b/ui/ui-framework/integration-tests/framework-demos/build.gradle
index efa06f8..5e42071 100644
--- a/ui/ui-framework/integration-tests/framework-demos/build.gradle
+++ b/ui/ui-framework/integration-tests/framework-demos/build.gradle
@@ -26,6 +26,7 @@
implementation project(':ui:ui-animation')
implementation project(':ui:ui-framework')
implementation project(':ui:ui-material')
+ implementation project(path: ':ui:ui-vector')
}
android {
diff --git a/ui/ui-framework/integration-tests/framework-demos/src/main/AndroidManifest.xml b/ui/ui-framework/integration-tests/framework-demos/src/main/AndroidManifest.xml
index ed79eaf..77971ae 100644
--- a/ui/ui-framework/integration-tests/framework-demos/src/main/AndroidManifest.xml
+++ b/ui/ui-framework/integration-tests/framework-demos/src/main/AndroidManifest.xml
@@ -45,6 +45,22 @@
<category android:name="androidx.ui.demos.SAMPLE_CODE" />
</intent-filter>
</activity>
+ <activity android:name=".gestures.LongPressGestureDetectorDemo"
+ android:configChanges="orientation|screenSize"
+ android:label="Gestures/Single GestureDetectors/LongPressGestureDetectorDemo">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="androidx.ui.demos.SAMPLE_CODE" />
+ </intent-filter>
+ </activity>
+ <activity android:name=".gestures.DoubleTapGestureDetectorDemo"
+ android:configChanges="orientation|screenSize"
+ android:label="Gestures/Single GestureDetectors/DoubleTapGestureDetectorDemo">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="androidx.ui.demos.SAMPLE_CODE" />
+ </intent-filter>
+ </activity>
<activity android:name=".gestures.NestedScrollingDemo"
android:configChanges="orientation|screenSize"
android:label="Gestures/Complex Demos/Nested Scrolling">
diff --git a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/MultipleCollect.kt b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/MultipleCollect.kt
index 443fcd6..2d01e25 100644
--- a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/MultipleCollect.kt
+++ b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/MultipleCollect.kt
@@ -25,13 +25,13 @@
import androidx.ui.core.coerceIn
import androidx.ui.core.ipx
import androidx.ui.core.toRect
-import androidx.ui.core.vectorgraphics.Brush
-import androidx.ui.core.vectorgraphics.SolidColor
import androidx.ui.graphics.Color
import androidx.ui.painting.Paint
import androidx.compose.Children
import androidx.compose.Composable
import androidx.compose.composer
+import androidx.ui.graphics.vectorgraphics.Brush
+import androidx.ui.graphics.vectorgraphics.SolidColor
@Composable
fun ColoredRect(brush: Brush, width: Dp? = null, height: Dp? = null) {
@@ -63,7 +63,7 @@
fun HeaderFooterLayout(
header: @Composable() () -> Unit,
footer: @Composable() () -> Unit,
- @Children content: @Composable() () -> Unit
+ content: @Composable() () -> Unit
) {
@Suppress("USELESS_CAST")
Layout(
diff --git a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/SemanticsL1.kt b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/SemanticsL1.kt
index 74904e6..a24fa7b 100644
--- a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/SemanticsL1.kt
+++ b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/SemanticsL1.kt
@@ -144,7 +144,7 @@
onPress: SemanticAction<PxPosition> = SemanticAction(defaultParam = PxPosition.Origin) { },
onRelease: SemanticAction<Unit> = SemanticAction(defaultParam = Unit) { },
onCancel: SemanticAction<Unit> = SemanticAction(defaultParam = Unit) { },
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
PressGestureDetector(
onPress = { onPress.action(ActionParam(ActionCaller.PointerInput, it)) },
@@ -165,7 +165,7 @@
fun Semantics(
@Suppress("UNUSED_PARAMETER") properties: Set<SemanticProperty<out Any>> = setOf(),
actions: Set<SemanticAction<out Any?>> = setOf(),
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
Column {
MaterialTheme {
@@ -275,7 +275,7 @@
* children or make them visible.
*/
@Composable
-private fun Collapsable(@Children children: @Composable() () -> Unit) {
+private fun Collapsable(children: @Composable() () -> Unit) {
val collapsedState = +state { CollapseMode.Collapsed }
diff --git a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/SemanticsL2.kt b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/SemanticsL2.kt
index a1aa21f..031632f 100644
--- a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/SemanticsL2.kt
+++ b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/SemanticsL2.kt
@@ -28,7 +28,7 @@
label: String = "",
visibility: Visibility = Visibility.Undefined,
actions: Set<SemanticAction<out Any?>> = setOf(),
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
val propertySet = mutableSetOf<SemanticProperty<out Any>>()
@@ -62,7 +62,7 @@
defaultParam: T,
types: Set<ActionType> = setOf(),
action: (ActionParam<T>) -> Unit,
- @Children block: @Composable() (SemanticAction<T>) -> Unit
+ block: @Composable() (SemanticAction<T>) -> Unit
) {
val semanticAction = SemanticAction<T>(phrase, defaultParam, types, action)
block.invoke(semanticAction)
diff --git a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/SemanticsL3.kt b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/SemanticsL3.kt
index 9b5eed4..565ab99 100644
--- a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/SemanticsL3.kt
+++ b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/SemanticsL3.kt
@@ -29,7 +29,7 @@
@Composable
fun ClickInteraction(
click: SemanticActionBuilder<Unit>.() -> Unit,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
val clickAction = SemanticActionBuilder(phrase = "Click", defaultParam = Unit)
.apply(click)
diff --git a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/VectorGraphicsActivity.kt b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/VectorGraphicsActivity.kt
index e6288ff..37f6735 100644
--- a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/VectorGraphicsActivity.kt
+++ b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/VectorGraphicsActivity.kt
@@ -17,68 +17,90 @@
package androidx.ui.framework.demos
import android.app.Activity
-import android.graphics.Color
+import android.graphics.Shader
import android.os.Bundle
-import android.widget.LinearLayout
-import androidx.ui.core.vectorgraphics.adoptVectorGraphic
-import androidx.ui.core.vectorgraphics.compat.vectorResource
-import androidx.ui.core.vectorgraphics.group
-import androidx.ui.core.vectorgraphics.path
-import androidx.ui.core.vectorgraphics.vector
-import androidx.ui.core.vectorgraphics.PathBuilder
-import androidx.ui.core.vectorgraphics.PathDelegate
+import androidx.compose.Children
import androidx.compose.Composable
import androidx.compose.composer
-import androidx.compose.registerAdapter
-import androidx.compose.setViewContent
+import androidx.ui.core.IntPx
+import androidx.ui.core.Layout
+import androidx.ui.core.Px
+import androidx.ui.core.dp
+import androidx.ui.core.px
+import androidx.ui.core.round
+import androidx.ui.core.vectorgraphics.DrawVector
+import androidx.ui.core.vectorgraphics.Group
+import androidx.ui.core.vectorgraphics.Path
+import androidx.ui.graphics.Color
+import androidx.ui.graphics.vectorgraphics.HorizontalGradient
+import androidx.ui.graphics.vectorgraphics.PathBuilder
+import androidx.ui.graphics.vectorgraphics.PathDelegate
+import androidx.ui.graphics.vectorgraphics.RadialGradient
+import androidx.ui.graphics.vectorgraphics.VerticalGradient
+import androidx.ui.layout.Center
+import androidx.ui.layout.Column
+import androidx.ui.layout.Container
+import androidx.ui.painting.TileMode
+import androidx.ui.core.setContent
+import androidx.ui.core.vectorgraphics.compat.VectorResource
+import androidx.ui.vector.VectorScope
class VectorGraphicsActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- val res = getResources()
- setViewContent {
- composer.registerAdapter { parent, child ->
- adoptVectorGraphic(parent, child)
- }
+ setContent {
+ Column {
+ Container(width = 480.dp, height = 480.dp) {
+ VectorResource(resId = R.drawable.ic_crane)
+ }
- LinearLayout(orientation = LinearLayout.VERTICAL) {
- vectorResource(
- res = res,
- resId = androidx.ui.framework.demos.R.drawable.ic_crane
- )
- vectorShape()
+ Center {
+ val width = 300.px
+ val height = 300.px
+ FixedLayout(width.round(), height.round()) {
+ vectorShape(width, height)
+ }
+ }
}
}
}
@Composable
- fun vectorShape() {
- val viewportWidth = 300.0f
- val viewportHeight = 300.0f
- vector(
+ fun FixedLayout(width: IntPx, height: IntPx, @Children child: @Composable() () -> Unit) {
+ Layout(children = { child() },
+ layoutBlock = { _, _ ->
+ layout(width, height) {}
+ })
+ }
+
+ @Composable
+ fun vectorShape(width: Px, height: Px) {
+ val viewportWidth = width.value
+ val viewportHeight = height.value
+ DrawVector(
name = "vectorShape",
- defaultWidth = 300.0f,
- defaultHeight = 300.0f,
+ defaultWidth = width,
+ defaultHeight = height,
viewportWidth = viewportWidth,
viewportHeight = viewportHeight
) {
- group(
+ Group(
scaleX = 0.75f,
scaleY = 0.75f,
- rotate = 45.0f,
+ rotation = 45.0f,
pivotX = (viewportWidth / 2),
pivotY = (viewportHeight / 2)
) {
backgroundPath(vectorWidth = viewportWidth, vectorHeight = viewportHeight)
stripePath(vectorWidth = viewportWidth, vectorHeight = viewportHeight)
- group(
- translateX = 50.0f,
- translateY = 50.0f,
+ Group(
+ translationX = 50.0f,
+ translationY = 50.0f,
pivotX = (viewportWidth / 2),
pivotY = (viewportHeight / 2),
- rotate = 25.0f
+ rotation = 25.0f
) {
val pathData = PathDelegate {
moveTo(viewportWidth / 2 - 100, viewportHeight / 2 - 100)
@@ -87,14 +109,24 @@
horizontalLineToRelative(-200.0f)
close()
}
- path(fill = Color.MAGENTA, pathData = pathData)
+ Path(
+ fill = HorizontalGradient(
+ Color.Red,
+ Color.Blue,
+ startX = Px.Zero,
+ endX = Px(viewportWidth / 2 + 100)
+ ),
+ pathData = pathData
+ )
}
+ triangle()
+ triangleWithOffsets()
}
}
}
@Composable
- fun backgroundPath(vectorWidth: Float, vectorHeight: Float) {
+ fun VectorScope.backgroundPath(vectorWidth: Float, vectorHeight: Float) {
val background = PathDelegate {
horizontalLineTo(vectorWidth)
verticalLineTo(vectorHeight)
@@ -102,16 +134,68 @@
close()
}
- path(fill = Color.CYAN, pathData = background)
+ Path(
+ fill = VerticalGradient(
+ 0.0f to Color.Aqua,
+ 0.3f to Color.Lime,
+ 1.0f to Color.Fuchsia,
+ startY = Px.Zero,
+ endY = Px(vectorHeight)
+ ),
+ pathData = background
+ )
}
@Composable
- fun stripePath(vectorWidth: Float, vectorHeight: Float) {
+ fun VectorScope.triangle() {
+ val length = 150.0f
+ Path(
+ fill = RadialGradient(
+ Color.Navy,
+ Color.Olive,
+ Color.Teal,
+ centerX = length / 2.0f,
+ centerY = length / 2.0f,
+ radius = length / 2.0f,
+ tileMode = TileMode.repeated
+ ),
+ pathData = PathDelegate {
+ verticalLineTo(length)
+ horizontalLineTo(length)
+ close()
+ }
+ )
+ }
+
+ @Composable
+ fun VectorScope.triangleWithOffsets() {
+
+ val side1 = 150.0f
+ val side2 = 150.0f
+ Path(
+ fill = RadialGradient(
+ 0.0f to Color.Maroon,
+ 0.3f to Color.Cyan,
+ 0.8f to Color.Yellow,
+ centerX = side1 / 2.0f,
+ centerY = side2 / 2.0f,
+ radius = side1 / 2.0f
+ ),
+ pathData = PathDelegate {
+ horizontalLineToRelative(side1)
+ verticalLineToRelative(side2)
+ close()
+ }
+ )
+ }
+
+ @Composable
+ fun VectorScope.stripePath(vectorWidth: Float, vectorHeight: Float) {
val stripeDelegate = PathDelegate {
stripe(vectorWidth, vectorHeight, 10)
}
- path(stroke = Color.BLUE, pathData = stripeDelegate)
+ Path(stroke = Color.Blue, pathData = stripeDelegate)
}
private fun PathBuilder.stripe(vectorWidth: Float, vectorHeight: Float, numLines: Int) {
diff --git a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/gestures/Colors.kt b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/gestures/Colors.kt
new file mode 100644
index 0000000..eaea537
--- /dev/null
+++ b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/gestures/Colors.kt
@@ -0,0 +1,104 @@
+/*
+ * 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.ui.framework.demos.gestures
+
+import androidx.ui.graphics.Color
+import kotlin.random.Random
+
+val Red = Color(0xFFf44336.toInt())
+val Pink = Color(0xFFe91e63.toInt())
+val Purple = Color(0xFF9c27b0.toInt())
+val DeepPurple = Color(0xFF673ab7.toInt())
+val Indigo = Color(0xFF3f51b5.toInt())
+val Blue = Color(0xFF2196f3.toInt())
+val LightBlue = Color(0xFF03a9f4.toInt())
+val Cyan = Color(0xFF00bcd4.toInt())
+val Teal = Color(0xFF009688.toInt())
+val Green = Color(0xFF4caf50.toInt())
+val LightGreen = Color(0xFF8bc34a.toInt())
+val Lime = Color(0xFFcddc39.toInt())
+val Yellow = Color(0xFFffeb3b.toInt())
+val Amber = Color(0xFFffc107.toInt())
+val Orange = Color(0xFFff9800.toInt())
+val DeepOrange = Color(0xFFff5722.toInt())
+val Brown = Color(0xFF795548.toInt())
+val Grey = Color(0xFF9e9e9e.toInt())
+val BlueGrey = Color(0xFF607d8b.toInt())
+
+val Colors = listOf(
+ Red,
+ Pink,
+ Purple,
+ DeepPurple,
+ Indigo,
+ Blue,
+ LightBlue,
+ Cyan,
+ Teal,
+ Green,
+ LightGreen,
+ Lime,
+ Yellow,
+ Amber,
+ Orange,
+ DeepOrange,
+ Brown,
+ Grey,
+ BlueGrey
+)
+
+fun Color.anotherRandomColor() = Colors.random(this)
+
+fun Color.next() = Colors.inOrder(this, true)
+fun Color.prev() = Colors.inOrder(this, false)
+
+fun List<Color>.random(exclude: Color?): Color {
+ val excludeIndex = indexOf(exclude)
+
+ val max = size - if (excludeIndex >= 0) 1 else 0
+
+ val random = Random.nextInt(max).run {
+ if (excludeIndex >= 0 && this >= excludeIndex) {
+ this + 1
+ } else {
+ this
+ }
+ }
+
+ return this[random]
+}
+
+fun List<Color>.inOrder(current: Color?, forward: Boolean): Color {
+ val currentIndex = indexOf(current)
+
+ val next =
+ if (forward) {
+ if (currentIndex == -1) {
+ 0
+ } else {
+ (currentIndex + 1) % size
+ }
+ } else {
+ if (currentIndex == -1) {
+ size - 1
+ } else {
+ (currentIndex - 1 + size) % size
+ }
+ }
+
+ return this[next]
+}
\ No newline at end of file
diff --git a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/gestures/DoubleTapGestureDetectorDemo.kt b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/gestures/DoubleTapGestureDetectorDemo.kt
new file mode 100644
index 0000000..7a972c5
--- /dev/null
+++ b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/gestures/DoubleTapGestureDetectorDemo.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.ui.framework.demos.gestures
+
+import android.app.Activity
+import android.os.Bundle
+import androidx.compose.state
+import androidx.compose.unaryPlus
+import androidx.ui.core.px
+import androidx.ui.core.setContent
+import androidx.compose.composer
+import androidx.ui.core.PxPosition
+import androidx.ui.core.dp
+import androidx.ui.core.gesture.DoubleTapGestureDetector
+
+/**
+ * Simple demo that shows off DragGestureDetector.
+ */
+class DoubleTapGestureDetectorDemo : Activity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContent {
+ val color = +state { Colors.random() }
+
+ val onDoubleTap: (PxPosition) -> Unit = {
+ color.value = color.value.anotherRandomColor()
+ }
+
+ DoubleTapGestureDetector(onDoubleTap = onDoubleTap) {
+ MatchParent {
+ DrawBox(
+ 0.px,
+ 0.px,
+ 96.dp,
+ 96.dp,
+ color.value
+ )
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/gestures/DragGestureDetectorDemo.kt b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/gestures/DragGestureDetectorDemo.kt
index af0161a..f7dabe6 100644
--- a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/gestures/DragGestureDetectorDemo.kt
+++ b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/gestures/DragGestureDetectorDemo.kt
@@ -23,10 +23,11 @@
import androidx.ui.core.PxPosition
import androidx.ui.core.gesture.DragObserver
import androidx.ui.core.px
+import androidx.ui.core.setContent
import androidx.ui.graphics.Color
import androidx.compose.composer
+import androidx.ui.core.dp
import androidx.ui.core.gesture.DragGestureDetector
-import androidx.ui.core.setContent
/**
* Simple demo that shows off DragGestureDetector.
@@ -51,8 +52,8 @@
DrawBox(
xOffset.value,
yOffset.value,
- 200.px,
- 200.px,
+ 96.dp,
+ 96.dp,
Color(0xFF9e9e9e.toInt())
)
}
diff --git a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/gestures/LongPressGestureDetectorDemo.kt b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/gestures/LongPressGestureDetectorDemo.kt
new file mode 100644
index 0000000..0cd4f12
--- /dev/null
+++ b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/gestures/LongPressGestureDetectorDemo.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.ui.framework.demos.gestures
+
+import android.app.Activity
+import android.os.Bundle
+import androidx.compose.state
+import androidx.compose.unaryPlus
+import androidx.ui.core.PxPosition
+import androidx.ui.core.px
+import androidx.ui.core.setContent
+import androidx.compose.composer
+import androidx.ui.core.dp
+import androidx.ui.core.gesture.LongPressGestureDetector
+
+/**
+ * Simple demo that shows off DragGestureDetector.
+ */
+class LongPressGestureDetectorDemo : Activity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContent {
+ val color = +state { Colors.random() }
+
+ val onLongPress = { _: PxPosition ->
+ color.value = color.value.anotherRandomColor()
+ }
+
+ LongPressGestureDetector(onLongPress = onLongPress) {
+ MatchParent {
+ DrawBox(
+ 0.px,
+ 0.px,
+ 96.dp,
+ 96.dp,
+ color.value
+ )
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/gestures/NestedScrollingDemo.kt b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/gestures/NestedScrollingDemo.kt
index 57886b2..ffadb06 100644
--- a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/gestures/NestedScrollingDemo.kt
+++ b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/gestures/NestedScrollingDemo.kt
@@ -24,24 +24,27 @@
import androidx.compose.unaryPlus
import androidx.ui.core.Direction
import androidx.ui.core.Dp
-import androidx.ui.core.Draw
import androidx.ui.core.IntPx
import androidx.ui.core.Layout
import androidx.ui.core.PxPosition
import androidx.ui.core.coerceIn
-import androidx.ui.core.dp
import androidx.ui.core.gesture.DragGestureDetector
import androidx.ui.core.gesture.DragObserver
import androidx.ui.core.gesture.PressIndicatorGestureDetector
import androidx.ui.core.ipx
import androidx.ui.core.px
-import androidx.ui.core.round
-import androidx.ui.core.toRect
+import androidx.ui.core.setContent
import androidx.ui.engine.geometry.Rect
import androidx.ui.graphics.Color
import androidx.ui.painting.Paint
+import androidx.ui.core.gesture.LongPressGestureDetector
import androidx.compose.composer
-import androidx.ui.core.setContent
+import androidx.ui.core.Draw
+import androidx.ui.core.dp
+import androidx.ui.core.gesture.DoubleTapGestureDetector
+import androidx.ui.core.gesture.PressReleasedGestureDetector
+import androidx.ui.core.round
+import androidx.ui.core.toRect
/**
* Demo app created to study some complex interactions of multiple DragGestureDetectors.
@@ -50,11 +53,11 @@
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
- // Outer composable that scrolls
+ // Outer composable that scrollsAll mea
Draggable {
RepeatingList(repititions = 3) {
SimpleContainer(
- width = -1.dp,
+ width = (-1).dp,
height = 398.dp,
padding = 72.dp
) {
@@ -78,7 +81,7 @@
* A very simple ScrollView like implementation that allows for vertical scrolling.
*/
@Composable
-private fun Draggable(@Children children: @Composable() () -> Unit) {
+private fun Draggable(children: @Composable() () -> Unit) {
val offset = +state { 0.px }
val maxOffset = +state { 0.px }
@@ -138,39 +141,62 @@
height: Dp
) {
- val pressed = +state { false }
+ val pressedColor = Color(0x1f000000)
+ val defaultColor = Color(0xFFFFFFFF.toInt())
- val onStart: (PxPosition) -> Unit = {
- pressed.value = true
+ val color = +state { defaultColor }
+ val showPressed = +state { false }
+
+ val onPress: (PxPosition) -> Unit = {
+ showPressed.value = true
}
- val onStop = {
- pressed.value = false
+ val onRelease = {
+ showPressed.value = false
}
- val resolvedColor =
- if (pressed.value) {
- Color(0x1f000000)
- } else {
- Color(0xFFFFFFFF.toInt())
- }
+ val onTap = {
+ color.value = color.value.next()
+ }
+
+ val onDoubleTap: (PxPosition) -> Unit = {
+ color.value = color.value.prev().prev()
+ }
+
+ val onLongPress = { _: PxPosition ->
+ color.value = defaultColor
+ showPressed.value = false
+ }
val children = @Composable {
Draw { canvas, parentSize ->
- val backgroundPaint = Paint().apply { this.color = resolvedColor }
+ val backgroundPaint = Paint().apply { this.color = color.value }
canvas.drawRect(
Rect(0f, 0f, parentSize.width.value, parentSize.height.value),
backgroundPaint
)
+ if (showPressed.value) {
+ backgroundPaint.color = pressedColor
+ canvas.drawRect(
+ Rect(0f, 0f, parentSize.width.value, parentSize.height.value),
+ backgroundPaint
+ )
+ }
}
}
- PressIndicatorGestureDetector(onStart, onStop, onStop) {
- Layout(children) { _, constraints ->
- layout(
- constraints.maxWidth,
- height.toIntPx().coerceIn(constraints.minHeight, constraints.maxHeight)
- ) {}
+ PressIndicatorGestureDetector(onPress, onRelease, onRelease) {
+ PressReleasedGestureDetector(onTap, false) {
+ DoubleTapGestureDetector(onDoubleTap) {
+ LongPressGestureDetector(onLongPress) {
+ Layout(children) { _, constraints ->
+ layout(
+ constraints.maxWidth,
+ height.toIntPx().coerceIn(constraints.minHeight, constraints.maxHeight)
+ ) {}
+ }
+ }
+ }
}
}
}
@@ -195,7 +221,7 @@
* A simple composable that arranges it's children as vertical list of items.
*/
@Composable
-private fun Column(@Children children: @Composable() () -> Unit) {
+private fun Column(children: @Composable() () -> Unit) {
Layout(children) { measurables, constraints ->
var height = 0.ipx
val placeables = measurables.map {
diff --git a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/gestures/SimpleComposables.kt b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/gestures/SimpleComposables.kt
index 8e67db6..dc07bc9 100644
--- a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/gestures/SimpleComposables.kt
+++ b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/gestures/SimpleComposables.kt
@@ -35,7 +35,7 @@
* A simple layout composable that matches the size of it's parent layout.
*/
@Composable
-internal fun MatchParent(@Children children: @Composable() () -> Unit) {
+internal fun MatchParent(children: @Composable() () -> Unit) {
Layout({
children()
}, { _, constraints ->
@@ -44,7 +44,7 @@
}
@Composable
-internal fun Center(@Children children: @Composable() () -> Unit) {
+internal fun Center(children: @Composable() () -> Unit) {
Layout(children) { measurables, constraints ->
val placeable = measurables.first().measure(constraints)
layout(constraints.maxWidth, constraints.maxHeight) {
@@ -59,7 +59,7 @@
* A simple composable that pads items by [padding].
*/
@Composable
-private fun Padding(padding: Dp?, @Children children: @Composable() () -> Unit) {
+private fun Padding(padding: Dp?, children: @Composable() () -> Unit) {
Layout(children) { measurables, constraints ->
val paddingPx = padding?.toIntPx() ?: 0.ipx
val doublePadding = paddingPx * 2
@@ -90,7 +90,7 @@
* A simple composable that draws a border around it's children.
*/
@Composable
-private fun Border(color: Color, width: Dp, @Children children: @Composable() () -> Unit) {
+private fun Border(color: Color, width: Dp, children: @Composable() () -> Unit) {
Layout(
children = {
children()
@@ -140,7 +140,7 @@
width: Dp,
height: Dp,
padding: Dp,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
val borderWidth: Dp = 2.dp
@@ -175,8 +175,8 @@
internal fun DrawBox(
xOffset: Px,
yOffset: Px,
- width: Px,
- height: Px,
+ width: Dp,
+ height: Dp,
color: Color
) {
val paint = +memo { Paint() }
@@ -184,8 +184,10 @@
paint.color = color
val centerX = parentSize.width.value / 2 + xOffset.value
val centerY = parentSize.height.value / 2 + yOffset.value
- val widthValue = if (width.value < 0) parentSize.width.value else width.value
- val heightValue = if (height.value < 0) parentSize.height.value else height.value
+ val widthPx = width.toPx()
+ val heightPx = height.toPx()
+ val widthValue = if (widthPx.value < 0) parentSize.width.value else widthPx.value
+ val heightValue = if (heightPx.value < 0) parentSize.height.value else heightPx.value
canvas.drawRect(
androidx.ui.engine.geometry.Rect(
centerX - widthValue / 2,
diff --git a/ui/ui-framework/src/androidTest/java/androidx/ui/core/test/AndroidLayoutDrawTest.kt b/ui/ui-framework/src/androidTest/java/androidx/ui/core/test/AndroidLayoutDrawTest.kt
index 2318041..c8866dc 100644
--- a/ui/ui-framework/src/androidTest/java/androidx/ui/core/test/AndroidLayoutDrawTest.kt
+++ b/ui/ui-framework/src/androidTest/java/androidx/ui/core/test/AndroidLayoutDrawTest.kt
@@ -426,14 +426,17 @@
val header = @Composable {
Layout(layoutBlock = { _, constraints ->
assertEquals(childConstraints[0], constraints)
+ layout(0.ipx, 0.ipx) {}
}, children = {})
}
val footer = @Composable {
Layout(layoutBlock = { _, constraints ->
assertEquals(childConstraints[1], constraints)
+ layout(0.ipx, 0.ipx) {}
}, children = {})
Layout(layoutBlock = { _, constraints ->
assertEquals(childConstraints[2], constraints)
+ layout(0.ipx, 0.ipx) {}
}, children = {})
}
@Suppress("USELESS_CAST")
@@ -447,6 +450,7 @@
assertEquals(footerChildrenCount, measurables[footer].size)
assertSame(measurables[1], measurables[footer][0])
assertSame(measurables[2], measurables[footer][1])
+ layout(0.ipx, 0.ipx) {}
}
}
}
@@ -458,18 +462,19 @@
activity.setContent {
val header = @Composable {
ParentData(data = 0) {
- Layout(layoutBlock = { _, _ -> }, children = {})
+ Layout(layoutBlock = { _, _ -> layout(0.ipx, 0.ipx, {}) }, children = {})
}
}
val footer = @Composable {
ParentData(data = 1) {
- Layout(layoutBlock = { _, _ -> }, children = {})
+ Layout(layoutBlock = { _, _ -> layout(0.ipx, 0.ipx, {}) }, children = {})
}
}
Layout(childrenArray = arrayOf(header, footer)) { measurables, _ ->
assertEquals(0, measurables[0].parentData)
assertEquals(1, measurables[1].parentData)
+ layout(0.ipx, 0.ipx, {})
}
}
}
@@ -553,7 +558,7 @@
fun FixedSizeRow(
width: IntPx,
height: IntPx,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
Layout(children = children, layoutBlock = { measurables, constraints ->
val resolvedWidth = width.coerceIn(constraints.minWidth, constraints.maxWidth)
@@ -936,7 +941,7 @@
}
@Composable
-fun AtLeastSize(size: IntPx, @Children children: @Composable() () -> Unit) {
+fun AtLeastSize(size: IntPx, children: @Composable() () -> Unit) {
Layout(
layoutBlock = { measurables, constraints ->
val newConstraints = Constraints(
@@ -964,7 +969,7 @@
}
@Composable
-fun Align(@Children children: @Composable() () -> Unit) {
+fun Align(children: @Composable() () -> Unit) {
Layout(
layoutBlock = { measurables, constraints ->
val newConstraints = Constraints(
@@ -992,7 +997,7 @@
}
@Composable
-fun Padding(size: IntPx, @Children children: @Composable() () -> Unit) {
+fun Padding(size: IntPx, children: @Composable() () -> Unit) {
Layout(
layoutBlock = { measurables, constraints ->
val totalDiff = size * 2
@@ -1024,7 +1029,7 @@
fun TwoMeasureLayout(
size: IntPx,
latch: CountDownLatch,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
Layout(children = children) { measurables, _ ->
val testConstraints = Constraints()
@@ -1042,11 +1047,12 @@
// expected
latch.countDown()
}
+ layout(0.ipx, 0.ipx, {})
}
}
@Composable
-fun Position(size: IntPx, offset: OffsetModel, @Children children: @Composable() () -> Unit) {
+fun Position(size: IntPx, offset: OffsetModel, children: @Composable() () -> Unit) {
Layout(children) { measurables, constraints ->
val placeables = measurables.map { m ->
m.measure(constraints)
diff --git a/ui/ui-framework/src/androidTest/java/androidx/ui/core/test/TextLayoutTest.kt b/ui/ui-framework/src/androidTest/java/androidx/ui/core/test/TextLayoutTest.kt
index e31200b..ccfa929 100644
--- a/ui/ui-framework/src/androidTest/java/androidx/ui/core/test/TextLayoutTest.kt
+++ b/ui/ui-framework/src/androidTest/java/androidx/ui/core/test/TextLayoutTest.kt
@@ -137,7 +137,7 @@
var threw = false
try {
textMeasurable.minIntrinsicWidth(0.ipx)
- } catch(e: UnsupportedOperationException) {
+ } catch (e: UnsupportedOperationException) {
threw = true
}
assertTrue(threw)
@@ -153,6 +153,8 @@
assertEquals(textHeight, textMeasurable.maxIntrinsicHeight(IntPx.Infinity))
intrinsicsLatch.countDown()
+
+ layoutResult(0.ipx, 0.ipx) {}
}
minIntrinsicWidth { _, _ -> 0.ipx }
minIntrinsicHeight { _, _ -> 0.ipx }
@@ -163,7 +165,7 @@
assertTrue(intrinsicsLatch.await(1, TimeUnit.SECONDS))
}
- private fun show(@Children composable: @Composable() () -> Unit) {
+ private fun show(composable: @Composable() () -> Unit) {
val runnable: Runnable = object : Runnable {
override fun run() {
activity.setContent {
diff --git a/ui/ui-framework/src/androidTest/java/androidx/ui/core/vectorgraphics/compat/XmlVectorParserTest.kt b/ui/ui-framework/src/androidTest/java/androidx/ui/core/vectorgraphics/compat/XmlVectorParserTest.kt
new file mode 100644
index 0000000..2fcd9a2
--- /dev/null
+++ b/ui/ui-framework/src/androidTest/java/androidx/ui/core/vectorgraphics/compat/XmlVectorParserTest.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.ui.core.vectorgraphics.compat
+
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.ui.core.Px
+
+import androidx.ui.core.vectorgraphics.VectorPath
+import androidx.ui.framework.test.R
+import androidx.ui.graphics.vectorgraphics.PathCommand
+import androidx.ui.graphics.vectorgraphics.PathNode
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class XmlVectorParserTest {
+
+ @Test
+ fun testParseXml() {
+ val res = InstrumentationRegistry.getInstrumentation().targetContext.resources
+ val asset = loadVectorResource(null, res, R.drawable.test_compose_vector)
+ val density = res.displayMetrics.density
+ val expectedSize = Px(density*24)
+ assertEquals(expectedSize, asset.defaultWidth)
+ assertEquals(expectedSize, asset.defaultHeight)
+ assertEquals(24.0f, asset.viewportWidth)
+ assertEquals(24.0f, asset.viewportHeight)
+ assertEquals(1, asset.root.size)
+
+ val node = asset.root.iterator().next() as VectorPath
+ assertEquals(0xFFFF0000.toInt(), node.fill as Int)
+
+ @Suppress("UNCHECKED_CAST")
+ val path = node.pathData as Array<PathNode>
+ assertEquals(3, path.size)
+ assertEquals(PathCommand.MoveTo, path.get(0).command)
+ assertEquals(20.0f, path.get(0).args[0])
+ assertEquals(10.0f, path.get(0).args[1])
+
+ assertEquals(PathCommand.RelativeLineTo, path.get(1).command)
+ assertEquals(6, path.get(1).args.size)
+ assertEquals(10.0f, path.get(1).args[0])
+ assertEquals(0.0f, path.get(1).args[1])
+ assertEquals(0.0f, path.get(1).args[2])
+ assertEquals(10.0f, path.get(1).args[3])
+ assertEquals(-10.0f, path.get(1).args[4])
+ assertEquals(0.0f, path.get(1).args[5])
+
+ assertEquals(PathCommand.RelativeClose, path.get(2).command)
+ assertEquals(0, path.get(2).args.size)
+ }
+}
\ No newline at end of file
diff --git a/ui/ui-framework/src/androidTest/res/drawable/test_compose_vector.xml b/ui/ui-framework/src/androidTest/res/drawable/test_compose_vector.xml
new file mode 100644
index 0000000..05beeb3
--- /dev/null
+++ b/ui/ui-framework/src/androidTest/res/drawable/test_compose_vector.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FFFF0000"
+ android:pathData="M20,10, l10,0 0,10 -10, 0z"/>
+</vector>
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/Clip.kt b/ui/ui-framework/src/main/java/androidx/ui/core/Clip.kt
index be6e0ff..294325e 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/Clip.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/Clip.kt
@@ -27,7 +27,7 @@
* @param shape the [Shape] used for clipping.
*/
@Composable
-fun Clip(shape: Shape, @Children children: @Composable() () -> Unit) {
+fun Clip(shape: Shape, children: @Composable() () -> Unit) {
<RepaintBoundaryNode name=null shape=shape clipToShape=true>
children()
</RepaintBoundaryNode>
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/Layout.kt b/ui/ui-framework/src/main/java/androidx/ui/core/Layout.kt
index 4686cc3..f328eff 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/Layout.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/Layout.kt
@@ -27,14 +27,28 @@
import androidx.compose.trace
import androidx.compose.unaryPlus
-internal typealias LayoutBlock = LayoutBlockReceiver.(List<Measurable>, Constraints) -> Unit
-internal typealias IntrinsicMeasurementBlock = IntrinsicMeasurementsReceiver
- .(List<Measurable>, IntPx) -> IntPx
-internal val LayoutBlockStub: LayoutBlock = { _, _ -> }
+internal typealias LayoutBlock =
+ LayoutBlockReceiver.(List<Measurable>, Constraints) -> LayoutResult
+internal typealias ComplexLayoutBlock =
+ ComplexLayoutBlockReceiver.(List<Measurable>, Constraints) -> LayoutResult
+internal typealias IntrinsicMeasurementBlock =
+ IntrinsicMeasurementReceiver.(List<Measurable>, IntPx) -> IntPx
+
+internal val ComplexLayoutBlockStub: ComplexLayoutBlock = { _, _ -> LayoutResult.instance }
internal val IntrinsicMeasurementBlockStub: IntrinsicMeasurementBlock = { _, _ -> 0.ipx }
+/**
+ * Object returned from [LayoutBlockReceiver.layout] and [ComplexLayoutBlockReceiver.layoutResult]
+ * to deliver proof the method was called in [LayoutBlock] and [ComplexLayoutBlock], respectively.
+ */
+class LayoutResult private constructor() {
+ companion object {
+ internal val instance = LayoutResult()
+ }
+}
+
internal class ComplexLayoutState(
- internal var layoutBlock: LayoutBlock = LayoutBlockStub,
+ internal var layoutBlock: ComplexLayoutBlock = ComplexLayoutBlockStub,
internal var minIntrinsicWidthBlock: IntrinsicMeasurementBlock = IntrinsicMeasurementBlockStub,
internal var maxIntrinsicWidthBlock: IntrinsicMeasurementBlock = IntrinsicMeasurementBlockStub,
internal var minIntrinsicHeightBlock: IntrinsicMeasurementBlock = IntrinsicMeasurementBlockStub,
@@ -57,9 +71,9 @@
internal var block: ComplexLayoutReceiver.() -> Unit = {}
internal var positioningBlock: PositioningBlockReceiver.() -> Unit = {}
- internal val layoutBlockReceiver = LayoutBlockReceiver(this)
+ internal val layoutBlockReceiver = ComplexLayoutBlockReceiver(this)
internal val intrinsicMeasurementsReceiver =
- IntrinsicMeasurementsReceiver(this)
+ IntrinsicMeasurementReceiver(this)
internal val positioningBlockReceiver = PositioningBlockReceiver()
internal val layoutNodeRef = Ref<LayoutNode>()
@@ -203,7 +217,7 @@
* Receiver scope for the [ComplexLayout] lambda.
*/
class ComplexLayoutReceiver internal constructor(internal val layoutState: ComplexLayoutState) {
- fun layout(layoutBlock: LayoutBlock) {
+ fun layout(layoutBlock: ComplexLayoutBlock) {
layoutState.layoutBlock = layoutBlock
}
fun minIntrinsicWidth(minIntrinsicWidthBlock: IntrinsicMeasurementBlock) {
@@ -220,7 +234,7 @@
}
internal fun runBlock(block: ComplexLayoutReceiver.() -> Unit) {
- layoutState.layoutBlock = LayoutBlockStub
+ layoutState.layoutBlock = ComplexLayoutBlockStub
layoutState.minIntrinsicWidthBlock = IntrinsicMeasurementBlockStub
layoutState.maxIntrinsicWidthBlock = IntrinsicMeasurementBlockStub
layoutState.minIntrinsicHeightBlock = IntrinsicMeasurementBlockStub
@@ -231,7 +245,7 @@
val noLambdaMessage = { subject: String ->
{ "No $subject lambda provided in ComplexLayout" }
}
- require(layoutState.layoutBlock != LayoutBlockStub, noLambdaMessage("layout"))
+ require(layoutState.layoutBlock != ComplexLayoutBlockStub, noLambdaMessage("layout"))
require(
layoutState.minIntrinsicWidthBlock != IntrinsicMeasurementBlockStub,
noLambdaMessage("minIntrinsicWidth")
@@ -283,7 +297,7 @@
/**
* Receiver scope for [ComplexLayout]'s intrinsic measurements lambdas.
*/
-class IntrinsicMeasurementsReceiver internal constructor(
+class IntrinsicMeasurementReceiver internal constructor(
internal val layoutState: ComplexLayoutState
) : DensityReceiver {
override val density: Density
@@ -301,7 +315,7 @@
/**
* Receiver scope for [ComplexLayout]'s layout lambda.
*/
-class LayoutBlockReceiver internal constructor(
+class ComplexLayoutBlockReceiver internal constructor(
internal val layoutState: ComplexLayoutState
) : DensityReceiver {
override val density: Density
@@ -315,9 +329,10 @@
width: IntPx,
height: IntPx,
block: PositioningBlockReceiver.() -> Unit
- ) {
+ ): LayoutResult {
layoutState.resize(width, height)
layoutState.positioningBlock = block
+ return LayoutResult.instance
}
fun Measurable.minIntrinsicWidth(h: IntPx) =
(this as ComplexLayoutState).minIntrinsicWidth(h)
@@ -344,19 +359,18 @@
@Composable
fun Layout(
children: @Composable() () -> Unit,
- @Children(composable = false) layoutBlock: LayoutReceiver
- .(measurables: List<Measurable>, constraints: Constraints) -> Unit
+ @Children(composable = false) layoutBlock: LayoutBlock
) {
trace("UI:Layout") {
ComplexLayout(children = children, block = {
- val layoutReceiver = LayoutReceiver(
+ val layoutReceiver = LayoutBlockReceiver(
layoutState,
LayoutMeasure, /* measure lambda */
{ _, _, _ -> }
)
layout { measurables, constraints ->
layoutReceiver.complexMeasure = LayoutMeasure
- layoutReceiver.complexLayoutResult = this::layoutResult
+ layoutReceiver.complexLayoutResult = { w, h, block -> layoutResult(w, h, block) }
layoutReceiver.layoutBlock(measurables, constraints)
}
@@ -368,7 +382,7 @@
}
layoutReceiver.complexLayoutResult = { width, _, _ -> intrinsicWidth = width }
val constraints = Constraints(maxHeight = h)
- layoutBlock(layoutReceiver, measurables, constraints)
+ layoutReceiver.layoutBlock(measurables, constraints)
intrinsicWidth
}
@@ -380,7 +394,7 @@
}
layoutReceiver.complexLayoutResult = { width, _, _ -> intrinsicWidth = width }
val constraints = Constraints(maxHeight = h)
- layoutBlock(layoutReceiver, measurables, constraints)
+ layoutReceiver.layoutBlock(measurables, constraints)
intrinsicWidth
}
@@ -392,7 +406,7 @@
}
layoutReceiver.complexLayoutResult = { _, height, _ -> intrinsicHeight = height }
val constraints = Constraints(maxWidth = w)
- layoutBlock(layoutReceiver, measurables, constraints)
+ layoutReceiver.layoutBlock(measurables, constraints)
intrinsicHeight
}
@@ -404,7 +418,7 @@
}
layoutReceiver.complexLayoutResult = { _, height, _ -> intrinsicHeight = height }
val constraints = Constraints(maxWidth = w)
- layoutBlock(layoutReceiver, measurables, constraints)
+ layoutReceiver.layoutBlock(measurables, constraints)
intrinsicHeight
}
})
@@ -437,8 +451,7 @@
@Composable
fun Layout(
childrenArray: Array<@Composable() () -> Unit>,
- @Children(composable = false) layoutBlock: LayoutReceiver
- .(measurables: List<Measurable>, constraints: Constraints) -> Unit
+ @Children(composable = false) layoutBlock: LayoutBlock
) {
val ChildrenEndMarker = @Composable { children: @Composable() () -> Unit ->
ParentData(data = ChildrenEndParentData(children)) {
@@ -457,10 +470,10 @@
}
/**
- * Receiver scope for the lambda of [Layout].
+ * Receiver scope for [Layout]'s layout lambda.
* Used to mask away intrinsics inside [Layout].
*/
-class LayoutReceiver internal constructor(
+class LayoutBlockReceiver internal constructor(
internal val layoutState: ComplexLayoutState,
internal var complexMeasure: (Measurable, Constraints) -> Placeable,
internal var complexLayoutResult: (IntPx, IntPx, PositioningBlockReceiver.() -> Unit) -> Unit
@@ -487,8 +500,13 @@
* calls to [Placeable.place], defining the positions of the children relative to the current
* layout.
*/
- fun layout(width: IntPx, height: IntPx, block: PositioningBlockReceiver.() -> Unit) {
+ fun layout(
+ width: IntPx,
+ height: IntPx,
+ block: PositioningBlockReceiver.() -> Unit
+ ): LayoutResult {
complexLayoutResult(width, height, block)
+ return LayoutResult.instance
}
}
@@ -516,7 +534,7 @@
* Please note that using this widget might be a performance hit, so please use with care.
*/
@Composable
-fun WithConstraints(@Children children: @Composable() (Constraints) -> Unit) {
+fun WithConstraints(children: @Composable() (Constraints) -> Unit) {
val ref = +compositionReference()
val context = +ambient(ContextAmbient)
@@ -592,7 +610,7 @@
@Composable
fun OnChildPositioned(
onPositioned: (coordinates: LayoutCoordinates) -> Unit,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
<DataNode key=OnChildPositionedKey value=onPositioned>
<children/>
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/Opacity.kt b/ui/ui-framework/src/main/java/androidx/ui/core/Opacity.kt
index 7399e1a..bc1881a 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/Opacity.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/Opacity.kt
@@ -33,7 +33,7 @@
@Composable
fun Opacity(
@FloatRange(from = 0.0, to = 1.0) opacity: Float,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
<RepaintBoundaryNode name=null opacity=opacity>
children()
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/ParentData.kt b/ui/ui-framework/src/main/java/androidx/ui/core/ParentData.kt
index 8ae7c49..081b3daf 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/ParentData.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/ParentData.kt
@@ -53,7 +53,7 @@
* expanded, which are inflexible and which are flexible.
*/
@Composable
-fun ParentData(data: Any, @Children children: @Composable() () -> Unit) {
+fun ParentData(data: Any, children: @Composable() () -> Unit) {
<DataNode key=ParentDataKey value=data>
<children/>
</DataNode>
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/PointerInputWrapper.kt b/ui/ui-framework/src/main/java/androidx/ui/core/PointerInputWrapper.kt
index ec66af4..d6e0e77 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/PointerInputWrapper.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/PointerInputWrapper.kt
@@ -24,7 +24,7 @@
@Composable
fun PointerInputWrapper(
pointerInputHandler: PointerInputHandler = { event, _ -> event },
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
// Hide the internals of PointerInputNode
<PointerInputNode pointerInputHandler>
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/RepaintBoundary.kt b/ui/ui-framework/src/main/java/androidx/ui/core/RepaintBoundary.kt
index 757e740..cbf1760 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/RepaintBoundary.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/RepaintBoundary.kt
@@ -31,7 +31,7 @@
* @param children The contained children.
*/
@Composable
-fun RepaintBoundary(name: String? = null, @Children children: @Composable() () -> Unit) {
+fun RepaintBoundary(name: String? = null, children: @Composable() () -> Unit) {
<RepaintBoundaryNode name=name>
<children/>
</RepaintBoundaryNode>
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/Semantics.kt b/ui/ui-framework/src/main/java/androidx/ui/core/Semantics.kt
deleted file mode 100644
index baa4ff3..0000000
--- a/ui/ui-framework/src/main/java/androidx/ui/core/Semantics.kt
+++ /dev/null
@@ -1,155 +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.ui.core
-
-import androidx.ui.core.semantics.SemanticsAction
-import androidx.ui.text.style.TextDirection
-import androidx.compose.Children
-import androidx.compose.Composable
-import androidx.compose.ambient
-import androidx.compose.composer
-import androidx.compose.unaryPlus
-
-// TODO(ryanmentley): This is the wrong package, move it as a standalone CL
-
-@Composable
-@Suppress("PLUGIN_ERROR")
-fun Semantics(
- /**
- * If 'container' is true, this component will introduce a new
- * node in the semantics tree. Otherwise, the semantics will be
- * merged with the semantics of any ancestors.
- *
- * Whether descendants of this component can add their semantic information
- * to the [SemanticsNode] introduced by this configuration is controlled by
- * [explicitChildNodes].
- */
- container: Boolean = false,
- /**
- * Whether descendants of this component are allowed to add semantic
- * information to the [SemanticsNode] annotated by this widget.
- *
- * When set to false descendants are allowed to annotate [SemanticNode]s of
- * their parent with the semantic information they want to contribute to the
- * semantic tree.
- * When set to true the only way for descendants to contribute semantic
- * information to the semantic tree is to introduce new explicit
- * [SemanticNode]s to the tree.
- *
- * This setting is often used in combination with [isSemanticBoundary] to
- * create semantic boundaries that are either writable or not for children.
- */
- explicitChildNodes: Boolean = false,
- /**
- * Whether the component represented by this configuration is currently enabled.
- *
- * A disabled object does not respond to user interactions. Only objects that
- * usually respond to user interactions, but which currently do not (like a
- * disabled button) should be marked as disabled.
- *
- * The corresponding getter on [SemanticsConfiguration] will return null if the component
- * doesn't support the concept of being enabled/disabled.
- */
- enabled: Boolean? = null,
- /**
- * If this node has Boolean state that can be controlled by the user, whether
- * that state is checked or unchecked, corresponding to true and false,
- * respectively.
- *
- * Do not set this field if the component doesn't have checked/unchecked state that can be
- * controlled by the user.
- *
- * The corresponding getter on [SemanticsConfiguration] returns null if the component does not
- * have checked/unchecked state.
- */
- checked: Boolean? = null,
- /** Whether the component represented by this configuration is selected (true) or not (false). */
- selected: Boolean? = null,
- /** Whether the component represented by this configuration is a button (true) or not (false). */
- button: Boolean? = null,
-// header: Boolean? = null,
-// textField: Boolean? = null,
-// focused: Boolean? = null,
- /**
- * Whether this component corresponds to UI that allows the user to
- * pick one of several mutually exclusive options.
- *
- * For example, a [Radio] button is in a mutually exclusive group because
- * only one radio button in that group can be marked as [isChecked].
- */
- inMutuallyExclusiveGroup: Boolean? = null,
-// obscured: Boolean? = null,
-// scopesRoute: Boolean? = null,
-// namesRoute: Boolean? = null,
- hidden: Boolean? = null,
- /**
- * A textual description of the component
- *
- * On iOS this is used for the `accessibilityLabel` property defined in the
- * `UIAccessibility` Protocol. On Android it is concatenated together with
- * [value] and [hint] in the following order: [value], [label], [hint].
- * The concatenated value is then used as the `Text` description.
- *
- * The reading direction is given by [textDirection].
- */
- label: String? = null,
- /**
- * A textual description for the current value of the owning component.
- *
- * On iOS this is used for the `accessibilityValue` property defined in the
- * `UIAccessibility` Protocol. On Android it is concatenated together with
- * [label] and [hint] in the following order: [value], [label], [hint].
- * The concatenated value is then used as the `Text` description.
- *
- * The reading direction is given by [textDirection].
- */
- value: String? = null,
-// hint: String? = null,
- /**
- * The reading direction for the text in [label], [value], and [hint]
- */
- textDirection: TextDirection? = null,
- testTag: String? = null,
- actions: List<SemanticsAction<*>> = emptyList(),
- @Children children: @Composable() () -> Unit
-) {
- val providedTestTag = +ambient(TestTagAmbient)
- <SemanticsComponentNode
- container
- explicitChildNodes
- enabled
- checked
- selected
- button
- header=null
- textField=null
- focused=null
- inMutuallyExclusiveGroup
- obscured=null
- scopesRoute=null
- namesRoute=null
- hidden
- label
- value
- hint=null
- textDirection
- testTag=(testTag ?: providedTestTag)
- actions>
- TestTag(tag=DefaultTestTag) {
- children()
- }
- </SemanticsComponentNode>
-}
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/TestTagProvider.kt b/ui/ui-framework/src/main/java/androidx/ui/core/TestTagProvider.kt
index e69bf03..a75a022 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/TestTagProvider.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/TestTagProvider.kt
@@ -27,6 +27,6 @@
// Implementation with ambients now for only one semantics inside.
// replace with mergeable semantics later
@Composable
-fun TestTag(tag: String, @Children children: @Composable() () -> Unit) {
+fun TestTag(tag: String, children: @Composable() () -> Unit) {
TestTagAmbient.Provider(value = tag, children = children)
}
\ No newline at end of file
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/Text.kt b/ui/ui-framework/src/main/java/androidx/ui/core/Text.kt
index 524f483..eaf59ee 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/Text.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/Text.kt
@@ -17,7 +17,6 @@
import android.annotation.SuppressLint
import androidx.compose.Ambient
-import androidx.compose.Children
import androidx.compose.Composable
import androidx.compose.ambient
import androidx.compose.composer
@@ -31,16 +30,18 @@
import androidx.ui.core.selection.Selection
import androidx.ui.core.selection.SelectionMode
import androidx.ui.core.selection.SelectionRegistrarAmbient
-import androidx.ui.engine.geometry.Offset
import androidx.ui.graphics.Color
import androidx.ui.text.AnnotatedString
import androidx.ui.text.ParagraphStyle
import androidx.ui.core.selection.TextSelectionHandler
import androidx.ui.core.selection.TextSelectionProcessor
+import androidx.ui.semantics.Semantics
+import androidx.ui.semantics.accessibilityLabel
import androidx.ui.text.TextSelection
import androidx.ui.text.TextPainter
import androidx.ui.text.TextSpan
import androidx.ui.text.TextStyle
+import androidx.ui.text.style.TextAlign
import androidx.ui.text.toAnnotatedString
import androidx.ui.text.style.TextOverflow
@@ -77,7 +78,7 @@
* Whether the text should break at soft line breaks.
* If false, the glyphs in the text will be positioned as if there was unlimited horizontal
* space.
- * If [softWrap] is false, [overflow] and [textAlign] may have unexpected effects.
+ * If [softWrap] is false, [overflow] and [TextAlign] may have unexpected effects.
*/
softWrap: Boolean = DefaultSoftWrap,
/** How visual overflow should be handled. */
@@ -98,7 +99,7 @@
/**
* Composable TextSpan attached after [text].
*/
- @Children child: @Composable TextSpanScope.() -> Unit
+ child: @Composable TextSpanScope.() -> Unit
) {
val rootTextSpan = +memo(text) { TextSpan(text = text) }
val ref = +compositionReference()
@@ -170,7 +171,7 @@
* Whether the text should break at soft line breaks.
* If false, the glyphs in the text will be positioned as if there was unlimited horizontal
* space.
- * If [softWrap] is false, [overflow] and [textAlign] may have unexpected effects.
+ * If [softWrap] is false, [overflow] and [TextAlign] may have unexpected effects.
*/
softWrap: Boolean = DefaultSoftWrap,
/** How visual overflow should be handled. */
@@ -199,7 +200,11 @@
val density = +ambientDensity()
val resourceLoader = +ambient(FontLoaderAmbient)
- Semantics(label = text.text) {
+ Semantics(
+ properties = {
+ accessibilityLabel = text.text
+ }
+ ) {
val textPainter = +memo(
text,
mergedStyle,
@@ -228,9 +233,10 @@
Draw { canvas, _ ->
internalSelection.value?.let {
textPainter.paintBackground(
- it.start, it.end, selectionColor, canvas, Offset.zero)
+ it.start, it.end, selectionColor, canvas
+ )
}
- textPainter.paint(canvas, Offset.zero)
+ textPainter.paint(canvas)
}
}
ComplexLayout(children) {
@@ -286,13 +292,13 @@
onSelectionChange = { internalSelection.value = it },
textPainter = textPainter
)
-
if (!textSelectionProcessor.isSelected) return null
- // TODO(qqd): Determine a set of coordinates around a character that we need.
+ // TODO(qqd): Determine a set of coordinates around a character that we
+ // need.
return Selection(
- startOffset = textSelectionProcessor.startOffset,
- endOffset = textSelectionProcessor.endOffset,
+ startCoordinates = textSelectionProcessor.startCoordinates,
+ endCoordinates = textSelectionProcessor.endCoordinates,
startLayoutCoordinates =
if (textSelectionProcessor.containsWholeSelectionStart) {
layoutCoordinates.value!!
@@ -323,7 +329,7 @@
* styled explicitly.
*/
@Composable
-fun CurrentTextStyleProvider(value: TextStyle, @Children children: @Composable() () -> Unit) {
+fun CurrentTextStyleProvider(value: TextStyle, children: @Composable() () -> Unit) {
val style = +ambient(CurrentTextStyleAmbient)
val mergedStyle = style.merge(value)
CurrentTextStyleAmbient.Provider(value = mergedStyle) {
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/InputField.kt b/ui/ui-framework/src/main/java/androidx/ui/core/TextField.kt
similarity index 79%
rename from ui/ui-framework/src/main/java/androidx/ui/core/InputField.kt
rename to ui/ui-framework/src/main/java/androidx/ui/core/TextField.kt
index f834987..656a8a4 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/InputField.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/TextField.kt
@@ -28,10 +28,9 @@
import androidx.ui.core.input.FocusManager
import androidx.ui.graphics.Color
import androidx.ui.input.EditProcessor
-import androidx.ui.input.EditorState
+import androidx.ui.input.EditorModel
import androidx.ui.input.ImeAction
import androidx.ui.input.KeyboardType
-import androidx.ui.text.AnnotatedString
import androidx.ui.text.TextPainter
import androidx.ui.text.TextStyle
@@ -45,35 +44,35 @@
/**
* The composition background color
*
- * @see EditorState.composition
+ * @see EditorModel.composition
*/
val compositionColor: Color = Color(alpha = 0xFF, red = 0xB0, green = 0xE0, blue = 0xE6),
/**
* The selection background color
*
- * @see EditorState.selection
+ * @see EditorModel.selection
*/
// TODO(nona): share with Text.DEFAULT_SELECTION_COLOR
val selectionColor: Color = Color(alpha = 0x66, red = 0x33, green = 0xB5, blue = 0xE5)
)
/**
- * A default implementation of InputField
+ * A default implementation of TextField
*
- * To make InputField work with platoform input service, you must keep the editor state and update
+ * To make TextField work with platoform input service, you must keep the editor state and update
* in [onValueChagne] callback.
*
* Example:
- * var state = +state { EditorState() }
- * InputField(
+ * var state = +state { EditorModel() }
+ * TextField(
* value = state.value,
* onValueChange = { state.value = it })
*/
@Composable
-fun InputField(
+fun TextField(
/** Initial editor state value */
- value: EditorState,
+ value: EditorModel,
/** The editor style */
editorStyle: EditorStyle,
@@ -97,10 +96,21 @@
imeAction: ImeAction = ImeAction.Unspecified,
/** Called when the InputMethodService update the editor state */
- onValueChange: (EditorState) -> Unit = {},
+ onValueChange: (EditorModel) -> Unit = {},
+
+ /** Called when the input field gains focus. */
+ onFocus: () -> Unit = {},
+
+ /** Called when the input field loses focus. */
+ onBlur: () -> Unit = {},
/** Called when the InputMethod requested an IME action */
- onImeActionPerformed: (ImeAction) -> Unit = {} // TODO(nona): Define argument type
+ onImeActionPerformed: (ImeAction) -> Unit = {},
+
+ /**
+ * Optional visual filter for changing visual output of input field.
+ */
+ visualTransformation: VisualTransformation? = null
) {
// Ambients
val style = +ambient(CurrentTextStyleAmbient)
@@ -111,10 +121,13 @@
// Memos
val processor = +memo { EditProcessor() }
val mergedStyle = style.merge(editorStyle.textStyle)
- val textPainter = +memo(value, mergedStyle, density, resourceLoader) {
+ val (visualText, offsetMap) = +memo(value, visualTransformation) {
+ TextFieldDelegate.applyVisualFilter(value, visualTransformation)
+ }
+ val textPainter = +memo(visualText, mergedStyle, density, resourceLoader) {
// TODO(nona): Add parameter for text direction, softwrap, etc.
TextPainter(
- text = AnnotatedString(text = value.text),
+ text = visualText,
style = mergedStyle,
density = density,
resourceLoader = resourceLoader
@@ -127,10 +140,10 @@
processor.onNewState(value, textInputService)
TextInputEventObserver(
- onPress = { InputFieldDelegate.onPress(textInputService) },
+ onPress = { },
onFocus = {
hasFocus.value = true
- InputFieldDelegate.onFocus(
+ TextFieldDelegate.onFocus(
textInputService,
value,
processor,
@@ -140,25 +153,37 @@
onImeActionPerformed)
coords.value?.let { coords ->
textInputService?.let { textInputService ->
- InputFieldDelegate.notifyFocusedRect(
+ TextFieldDelegate.notifyFocusedRect(
value,
textPainter,
coords,
textInputService,
- hasFocus.value
+ hasFocus.value,
+ offsetMap
)
}
}
+ onFocus()
},
onBlur = {
hasFocus.value = false
- InputFieldDelegate.onBlur(
+ TextFieldDelegate.onBlur(
textInputService,
processor,
onValueChange)
+ onBlur()
},
- onDragAt = { InputFieldDelegate.onDragAt(it) },
- onRelease = { InputFieldDelegate.onRelease(it, textPainter, processor, onValueChange) }
+ onDragAt = { TextFieldDelegate.onDragAt(it) },
+ onRelease = {
+ TextFieldDelegate.onRelease(
+ it,
+ textPainter,
+ processor,
+ offsetMap,
+ onValueChange,
+ textInputService,
+ hasFocus.value)
+ }
) {
Layout(
children = @Composable {
@@ -167,24 +192,26 @@
// TODO(nona): notify focused rect in onPreDraw equivalent callback for
// supporting multiline text.
coords.value = it
- InputFieldDelegate.notifyFocusedRect(
+ TextFieldDelegate.notifyFocusedRect(
value,
textPainter,
it,
textInputService,
- hasFocus.value
+ hasFocus.value,
+ offsetMap
)
}
}
- Draw { canvas, _ -> InputFieldDelegate.draw(
+ Draw { canvas, _ -> TextFieldDelegate.draw(
canvas,
value,
+ offsetMap,
textPainter,
hasFocus.value,
editorStyle) }
},
layoutBlock = { _, constraints ->
- InputFieldDelegate.layout(textPainter, constraints).let {
+ TextFieldDelegate.layout(textPainter, constraints).let {
layout(it.first, it.second) {}
}
}
@@ -202,7 +229,7 @@
onRelease: (PxPosition) -> Unit,
onFocus: () -> Unit,
onBlur: () -> Unit,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
val focused = +state { false }
val focusManager = +ambient(FocusManagerAmbient)
@@ -275,7 +302,7 @@
onPress: (PxPosition) -> Unit,
onDragAt: (PxPosition) -> Unit,
onRelease: (PxPosition) -> Unit,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
val tracker = +state { DragEventTracker() }
PressGestureDetector(
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/InputFieldDelegate.kt b/ui/ui-framework/src/main/java/androidx/ui/core/TextFieldDelegate.kt
similarity index 75%
rename from ui/ui-framework/src/main/java/androidx/ui/core/InputFieldDelegate.kt
rename to ui/ui-framework/src/main/java/androidx/ui/core/TextFieldDelegate.kt
index 29316a2..9d66efa 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/InputFieldDelegate.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/TextFieldDelegate.kt
@@ -17,11 +17,10 @@
package androidx.ui.core
import android.util.Log
-import androidx.ui.engine.geometry.Offset
import androidx.ui.engine.geometry.Rect
import androidx.ui.input.EditOperation
import androidx.ui.input.EditProcessor
-import androidx.ui.input.EditorState
+import androidx.ui.input.EditorModel
import androidx.ui.input.FinishComposingTextEditOp
import androidx.ui.input.ImeAction
import androidx.ui.input.KeyboardType
@@ -31,7 +30,7 @@
import androidx.ui.text.AnnotatedString
import androidx.ui.text.TextPainter
-internal class InputFieldDelegate {
+internal class TextFieldDelegate {
companion object {
/**
* Process text layout with given constraint.
@@ -73,6 +72,7 @@
*
* @param canvas The target canvas.
* @param value The editor state
+ * @param offsetMap The offset map
* @param textPainter The text painter
* @param hasFocus true if this widget is focused, otherwise false
* @param editorStyle The editor style.
@@ -80,34 +80,34 @@
@JvmStatic
fun draw(
canvas: Canvas,
- value: EditorState,
+ value: EditorModel,
+ offsetMap: OffsetMap,
textPainter: TextPainter,
hasFocus: Boolean,
editorStyle: EditorStyle
) {
value.composition?.let {
textPainter.paintBackground(
- it.start,
- it.end,
+ offsetMap.originalToTransformed(it.start),
+ offsetMap.originalToTransformed(it.end),
editorStyle.compositionColor,
- canvas,
- Offset.zero
+ canvas
)
}
if (value.selection.collapsed) {
if (hasFocus) {
- textPainter.paintCursor(value.selection.start, canvas)
+ textPainter.paintCursor(
+ offsetMap.originalToTransformed(value.selection.start), canvas)
}
} else {
textPainter.paintBackground(
- value.selection.start,
- value.selection.end,
+ offsetMap.originalToTransformed(value.selection.start),
+ offsetMap.originalToTransformed(value.selection.end),
editorStyle.selectionColor,
- canvas,
- Offset.zero
+ canvas
)
}
- textPainter.paint(canvas, Offset.zero)
+ textPainter.paint(canvas)
}
/**
@@ -117,20 +117,21 @@
*/
@JvmStatic
fun notifyFocusedRect(
- value: EditorState,
+ value: EditorModel,
textPainter: TextPainter,
layoutCoordinates: LayoutCoordinates,
textInputService: TextInputService,
- hasFocus: Boolean
+ hasFocus: Boolean,
+ offsetMap: OffsetMap
) {
if (!hasFocus) {
return
}
val bbox = if (value.selection.end < value.text.length) {
- textPainter.getBoundingBox(value.selection.end)
+ textPainter.getBoundingBox(offsetMap.originalToTransformed(value.selection.end))
} else if (value.selection.end != 0) {
- textPainter.getBoundingBox(value.selection.end - 1)
+ textPainter.getBoundingBox(offsetMap.originalToTransformed(value.selection.end) - 1)
} else {
Rect(0f, 0f, 1.0f, textPainter.preferredLineHeight)
}
@@ -157,22 +158,12 @@
internal fun onEditCommand(
ops: List<EditOperation>,
editProcessor: EditProcessor,
- onValueChange: (EditorState) -> Unit
+ onValueChange: (EditorModel) -> Unit
) {
onValueChange(editProcessor.onEditCommands(ops))
}
/**
- * Called when onPress event is fired.
- *
- * @param textInputService The text input service
- */
- @JvmStatic
- fun onPress(textInputService: TextInputService?) {
- textInputService?.showSoftwareKeyboard()
- }
-
- /**
* Called when onDrag event is fired.
*
* @param position The event position in widget coordinate.
@@ -180,7 +171,7 @@
@JvmStatic
fun onDragAt(position: PxPosition) {
// TODO(nona): Implement this function
- Log.d("InputFieldDelegate", "onDrag: $position")
+ Log.d("TextFieldDelegate", "onDrag: $position")
}
/**
@@ -189,17 +180,30 @@
* @param position The event position in widget coordinate.
* @param textPainter The text painter
* @param editProcessor The edit processor
+ * @param offsetMap The offset map
* @param onValueChange The callback called when the new editor state arrives.
+ * @param textInputService The text input service
+ * @param hasFocus True if the widget has input focus, otherwise false.
*/
@JvmStatic
fun onRelease(
position: PxPosition,
textPainter: TextPainter,
editProcessor: EditProcessor,
- onValueChange: (EditorState) -> Unit
+ offsetMap: OffsetMap,
+ onValueChange: (EditorModel) -> Unit,
+ textInputService: TextInputService?,
+ hasFocus: Boolean
) {
- val offset = textPainter.getPositionForOffset(position.toOffset())
- onEditCommand(listOf(SetSelectionEditOp(offset, offset)), editProcessor, onValueChange)
+ textInputService?.showSoftwareKeyboard()
+ if (hasFocus) {
+ val offset = offsetMap.transformedToOriginal(
+ textPainter.getOffsetForPosition(position))
+ onEditCommand(
+ listOf(SetSelectionEditOp(offset, offset)),
+ editProcessor,
+ onValueChange)
+ }
}
/**
@@ -210,20 +214,20 @@
* @param editProcessor The edit processor
* @param keyboardType The keyboard type
* @param onValueChange The callback called when the new editor state arrives.
- * @param onEditorActionPerformed The callback called when the editor action arrives.
+ * @param onImeActionPerformed The callback called when the editor action arrives.
*/
@JvmStatic
fun onFocus(
textInputService: TextInputService?,
- value: EditorState,
+ value: EditorModel,
editProcessor: EditProcessor,
keyboardType: KeyboardType,
imeAction: ImeAction,
- onValueChange: (EditorState) -> Unit,
+ onValueChange: (EditorModel) -> Unit,
onImeActionPerformed: (ImeAction) -> Unit
) {
textInputService?.startInput(
- initState = value,
+ initModel = value,
keyboardType = keyboardType,
imeAction = imeAction,
onEditCommand = { onEditCommand(it, editProcessor, onValueChange) },
@@ -241,10 +245,26 @@
fun onBlur(
textInputService: TextInputService?,
editProcessor: EditProcessor,
- onValueChange: (EditorState) -> Unit
+ onValueChange: (EditorModel) -> Unit
) {
onEditCommand(listOf(FinishComposingTextEditOp()), editProcessor, onValueChange)
textInputService?.stopInput()
}
+
+ /**
+ * Helper function of applying visual transformation method to the EditorModel.
+ *
+ * @param value An editor state
+ * @param visualTransformation A visual transformation
+ */
+ @JvmStatic
+ fun applyVisualFilter(
+ value: EditorModel,
+ visualTransformation: VisualTransformation?
+ ): TransformedText {
+ val annotatedString = AnnotatedString(value.text)
+ return visualTransformation?.filter(annotatedString)
+ ?: TransformedText(annotatedString, identityOffsetMap)
+ }
}
}
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/TextSpanCompose.kt b/ui/ui-framework/src/main/java/androidx/ui/core/TextSpanCompose.kt
index 1af1687..8d8a56f 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/TextSpanCompose.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/TextSpanCompose.kt
@@ -256,7 +256,7 @@
fun TextSpanScope.Span(
text: String? = null,
style: TextStyle? = null,
- @Children child: @Composable TextSpanScope.() -> Unit
+ child: @Composable TextSpanScope.() -> Unit
) {
TextSpan(text = text, style = style) {
child()
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/VisualTransformation.kt b/ui/ui-framework/src/main/java/androidx/ui/core/VisualTransformation.kt
new file mode 100644
index 0000000..23ff8a6
--- /dev/null
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/VisualTransformation.kt
@@ -0,0 +1,142 @@
+/*
+ * 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.ui.core
+
+import androidx.ui.text.AnnotatedString
+
+/**
+ * The map interface used for bidirectional offset mapping from original to transformed text.
+ */
+interface OffsetMap {
+ /**
+ * Convert offset in original text into the offset in transformed text.
+ *
+ * This function must be a monotonically non-decreasing function. In other words, if a cursor
+ * advances in the original text, the cursor in the transformed text must advance or stay there.
+ *
+ * @param offset offset in original text.
+ * @return offset in transformed text
+ * @see VisualTransformation
+ */
+ fun originalToTransformed(offset: Int): Int
+
+ /**
+ * Convert offset in transformed text into the offset in original text.
+ *
+ * This function must be a monotonically non-decreasing function. In other words, if a cursor
+ * advances in the transformed text, the cusrsor in the original text must advance or stay
+ * there.
+ *
+ * @param offset offset in transformed text
+ * @return offset in original text
+ * @see VisualTransformation
+ */
+ fun transformedToOriginal(offset: Int): Int
+}
+
+/**
+ * The transformed text with offset offset mapping
+ */
+data class TransformedText(
+ /**
+ * The transformed text
+ */
+ val transformedText: AnnotatedString,
+
+ /**
+ * The map used for bidirectional offset mapping from original to transformed text.
+ */
+ val offsetMap: OffsetMap
+)
+
+/**
+ * Interface used for changing visual output of the input field.
+ *
+ * This interface can be used for changing visual output of the text in the input field.
+ * For example, you can mask characters in password filed with asterisk with
+ * PasswordVisualTransformation.
+ */
+interface VisualTransformation {
+ /**
+ * Change the visual output of given text.
+ *
+ * Note that the returned text length can be different length from the given text. The widget
+ * will call the offset translator for converting offsets for various reasons, cursor drawing
+ * position, text selection by gesture, etc.
+ *
+ * Example: Credit Card Visual Output (inserting hyphens each 4 digits)
+ * original text : 1234567890123456
+ * transformed text: 1234-5678-9012-3456
+ *
+ * Then, the offset translator should ignore the hyphen characters, so conversion from
+ * original offset to transformed text works like
+ * - The 4th char of the original text is 5th char in the transformed text.
+ * - The 13th char of the original text is 15th char in the transformed text.
+ * Similarly, the reverse conversion works like
+ * - The 5th char of the transformed text is 4th char in the original text.
+ * - The 12th char of the transformed text is 10th char in the original text.
+ *
+ * The reference implementation would be like as follows:
+ * <pre>
+ * val creditCardOffsetTranslator = object : OffsetMap {
+ * override fun originalToTransformed(originalOffset: Int): Int {
+ * if (originalOffset <= 3) return originalOffset
+ * if (originalOffset <= 7) return originalOffset + 1
+ * if (originalOffset <= 11) return originalOffset + 2
+ * if (originalOffset <= 16) return originalOffset + 3
+ * return 19
+ * }
+ *
+ * override fun transformedToOriginal(transformedOffset: Int): Int {
+ * if (transformedOffset <= 4) return transformedOffset
+ * if (transformedOffset <= 9) return transformedOffset - 1
+ * if (transformedOffset <= 14) return transformedOffset - 2
+ * if (transformedOffset <= 19) return transformedOffset - 3
+ * return 16
+ * }
+ * }
+ * </pre>
+ *
+ * TODO(nona): Add paragraph direction argument for determining offset conversion.
+ *
+ * @param text The original text
+ * @return the pair of filtered text and offset translator.
+ */
+ fun filter(text: AnnotatedString): TransformedText
+}
+
+/**
+ * The Visual Filter can be used for password Input Field.
+ *
+ * Note that this visual filter only works for ASCII characters.
+ *
+ * @param mask The mask character used instead of original text.
+ */
+class PasswordVisualTransformation(val mask: Char = '\u2022') : VisualTransformation {
+ override fun filter(text: AnnotatedString): TransformedText {
+ return TransformedText(AnnotatedString(Character.toString(mask).repeat(text.text.length)),
+ identityOffsetMap)
+ }
+}
+
+/**
+ * The offset map used for identity mapping.
+ */
+internal val identityOffsetMap = object : OffsetMap {
+ override fun originalToTransformed(offset: Int): Int = offset
+ override fun transformedToOriginal(offset: Int): Int = offset
+}
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/Wrapper.kt b/ui/ui-framework/src/main/java/androidx/ui/core/Wrapper.kt
index 272f1d7..ed51529 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/Wrapper.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/Wrapper.kt
@@ -37,6 +37,8 @@
import androidx.compose.unaryPlus
import androidx.ui.core.text.AndroidFontResourceLoader
import androidx.ui.text.font.Font
+import kotlinx.coroutines.Dispatchers
+import kotlin.coroutines.CoroutineContext
/**
* Composes a view containing ui composables into a view composition.
@@ -45,7 +47,7 @@
* [Activity.setContent] or [ViewGroup.setContent] extensions.
*/
@Composable
-fun ComposeView(@Children children: @Composable() () -> Unit) {
+fun ComposeView(children: @Composable() () -> Unit) {
val rootRef = +memo { Ref<AndroidCraneView>() }
<AndroidCraneView ref=rootRef>
@@ -80,8 +82,11 @@
val rootLayoutNode = rootRef.value?.root ?: error("Failed to create root platform view")
val context = rootRef.value?.context ?: composer.composer.context
+ // If this value is inlined where it is used, an error that includes 'Precise Reference:
+ // kotlinx.coroutines.Dispatchers' not instance of 'Precise Reference: androidx.compose.Ambient'.
+ val coroutineContext = Dispatchers.Main
cc = Compose.composeInto(container = rootLayoutNode, context = context, parent = reference) {
- WrapWithAmbients(rootRef.value!!, context) {
+ WrapWithAmbients(rootRef.value!!, context, coroutineContext) {
children()
}
}
@@ -95,15 +100,18 @@
* @param content Composable that will be the content of the activity.
*/
fun Activity.setContent(
- @Children content: @Composable() () -> Unit
+ content: @Composable() () -> Unit
): CompositionContext? {
val craneView = window.decorView
.findViewById<ViewGroup>(android.R.id.content)
.getChildAt(0) as? AndroidCraneView
?: AndroidCraneView(this).also { setContentView(it) }
+ // If this value is inlined where it is used, an error that includes 'Precise Reference:
+ // kotlinx.coroutines.Dispatchers' not instance of 'Precise Reference: androidx.compose.Ambient'.
+ val coroutineContext = Dispatchers.Main
return Compose.composeInto(craneView.root, this) {
- WrapWithAmbients(craneView, this) {
+ WrapWithAmbients(craneView, this, coroutineContext) {
content()
}
}
@@ -115,14 +123,17 @@
* @param content Composable that will be the content of the view.
*/
fun ViewGroup.setContent(
- @Children content: @Composable() () -> Unit
+ content: @Composable() () -> Unit
): CompositionContext? {
val craneView =
if (childCount > 0) { getChildAt(0) as? AndroidCraneView } else { removeAllViews(); null }
?: AndroidCraneView(context).also { addView(it) }
+ // If this value is inlined where it is used, an error that includes 'Precise Reference:
+ // kotlinx.coroutines.Dispatchers' not instance of 'Precise Reference: androidx.compose.Ambient'.
+ val coroutineContext = Dispatchers.Main
return Compose.composeInto(craneView.root, context) {
- WrapWithAmbients(craneView, context) {
+ WrapWithAmbients(craneView, context, coroutineContext) {
content()
}
}
@@ -132,12 +143,14 @@
private fun WrapWithAmbients(
craneView: AndroidCraneView,
context: Context,
- @Children content: @Composable() () -> Unit
+ coroutineContext: CoroutineContext,
+ content: @Composable() () -> Unit
) {
// TODO(nona): Tie the focus manger lifecycle to Window, otherwise FocusManager won't work
// with nested AndroidCraneView case
val focusManager = +memo { FocusManager() }
ContextAmbient.Provider(value = context) {
+ CoroutineContextAmbient.Provider(value = coroutineContext) {
DensityAmbient.Provider(value = Density(context)) {
FocusManagerAmbient.Provider(value = focusManager) {
TextInputServiceAmbient.Provider(value = craneView.textInputService) {
@@ -147,6 +160,7 @@
}
}
}
+ }
}
}
@@ -154,6 +168,8 @@
val DensityAmbient = Ambient.of<Density>()
+val CoroutineContextAmbient = Ambient.of<CoroutineContext>()
+
internal val FocusManagerAmbient = Ambient.of<FocusManager>()
internal val TextInputServiceAmbient = Ambient.of<TextInputService?>()
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/DoubleTapGestureDetector.kt b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/DoubleTapGestureDetector.kt
new file mode 100644
index 0000000..c887644
--- /dev/null
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/DoubleTapGestureDetector.kt
@@ -0,0 +1,112 @@
+/*
+ * 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.ui.core.gesture
+
+import androidx.ui.core.PointerEventPass
+import androidx.ui.core.PointerInputChange
+import androidx.ui.core.changedToDown
+import androidx.compose.Children
+import androidx.compose.Composable
+import androidx.compose.ambient
+import androidx.compose.memo
+import androidx.compose.unaryPlus
+import androidx.ui.core.changedToUp
+import androidx.compose.composer
+import androidx.ui.core.CoroutineContextAmbient
+import androidx.ui.core.PointerInputWrapper
+import androidx.ui.core.PxPosition
+import androidx.ui.core.anyPositionChangeConsumed
+import androidx.ui.core.changedToUpIgnoreConsumed
+import androidx.ui.core.consumeDownChange
+import androidx.ui.temputils.delay
+import kotlinx.coroutines.Job
+import kotlin.coroutines.CoroutineContext
+
+// TODO(b/138605697): This bug tracks the note below: DoubleTapGestureDetector should use the
+// eventual api that will allow it to temporary block tap.
+// TODO(b/138754591): The behavior of this gesture detector needs to be finalized.
+/**
+ * Responds to pointers going up, down within a small duration, and then up again.
+ *
+ * Note: This is a temporary implementation to unblock dependents. Once the underlying API that
+ * allows double tap to temporarily block tap from firing is complete, this gesture detector will
+ * not block tap when the first "up" occurs. It will however block the 2nd up from causing tap to
+ * fire.
+ *
+ * Also, given that this gesture detector is so temporary, opting to not write substantial tests.
+ */
+@Composable
+fun DoubleTapGestureDetector(
+ onDoubleTap: (PxPosition) -> Unit,
+ children: @Composable() () -> Unit
+) {
+ val recognizer =
+ +memo { DoubleTapGestureRecognizer(onDoubleTap, +ambient(CoroutineContextAmbient)) }
+ PointerInputWrapper(pointerInputHandler = recognizer.pointerInputHandler) {
+ children()
+ }
+}
+
+internal class DoubleTapGestureRecognizer(
+ val onDoubleTap: (PxPosition) -> Unit,
+ coroutineContext: CoroutineContext
+) {
+
+ private enum class State {
+ Idle, Down, Up, SecondDown, Cancelled
+ }
+
+ var doubleTapTimeout = DoubleTapTimeout
+ private var state = State.Idle
+ private var job: Job? = null
+
+ val pointerInputHandler =
+ { changes: List<PointerInputChange>, pass: PointerEventPass ->
+
+ var changesToReturn = changes
+
+ if (pass == PointerEventPass.PostUp) {
+ if (state == State.Idle && changesToReturn.all { it.changedToDown() }) {
+ state = State.Down
+ } else if (state == State.Down && changesToReturn.all { it.changedToUp() }) {
+ state = State.Up
+ job = delay(doubleTapTimeout, coroutineContext) {
+ state = State.Idle
+ }
+ } else if (state == State.Up && changesToReturn.all { it.changedToDown() }) {
+ job?.cancel()
+ state = State.SecondDown
+ } else if (state == State.SecondDown && changesToReturn.all { it.changedToUp() }) {
+ changesToReturn = changesToReturn.map { it.consumeDownChange() }
+ state = State.Idle
+ onDoubleTap.invoke(changes[0].previous.position!!)
+ } else if (state == State.Cancelled &&
+ changesToReturn.all { it.changedToUpIgnoreConsumed() }
+ ) {
+ state = State.Idle
+ }
+ }
+
+ if (pass == PointerEventPass.PostDown &&
+ changesToReturn.any { it.anyPositionChangeConsumed() }
+ ) {
+ state = State.Cancelled
+ }
+
+ changesToReturn
+ }
+}
\ No newline at end of file
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/DragGestureDetector.kt b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/DragGestureDetector.kt
index 95cc8eb..0692d57 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/DragGestureDetector.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/DragGestureDetector.kt
@@ -100,7 +100,7 @@
fun DragGestureDetector(
canDrag: ((Direction) -> Boolean)? = null,
dragObserver: DragObserver? = null,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
val recognizer = +memo { DragGestureRecognizer() }
recognizer.canDrag = canDrag
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/LongPressGestureDetector.kt b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/LongPressGestureDetector.kt
new file mode 100644
index 0000000..d269572
--- /dev/null
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/LongPressGestureDetector.kt
@@ -0,0 +1,133 @@
+/*
+ * 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.ui.core.gesture
+
+import androidx.ui.core.PointerEventPass
+import androidx.ui.core.PointerInputChange
+import androidx.ui.core.anyPositionChangeConsumed
+import androidx.ui.core.changedToDown
+import androidx.compose.Children
+import androidx.compose.Composable
+import androidx.compose.ambient
+import androidx.compose.memo
+import androidx.compose.unaryPlus
+import androidx.ui.core.PxPosition
+import androidx.ui.core.changedToUp
+import androidx.ui.core.changedToUpIgnoreConsumed
+import androidx.ui.core.consumeDownChange
+import androidx.compose.composer
+import androidx.ui.core.CoroutineContextAmbient
+import androidx.ui.core.PointerInputWrapper
+import androidx.ui.temputils.delay
+import kotlinx.coroutines.Job
+import kotlin.coroutines.CoroutineContext
+
+// TODO(b/137569202): This bug tracks the note below regarding the need to eventually improve LongPressGestureDetector.
+/**
+ * Responds to a pointer being "down" for an extended amount of time.
+ *
+ * Note: this is likely a temporary, naive, and flawed approach. It is not necessarily guaranteed to interoperate well
+ * with forthcoming behavior related to disambiguation between multi-tap (double tap, triple tap) and tap.
+ */
+@Composable
+fun LongPressGestureDetector(
+ onLongPress: (PxPosition) -> Unit,
+ children: @Composable() () -> Unit
+) {
+ val recognizer =
+ +memo { LongPressGestureRecognizer(onLongPress, +ambient(CoroutineContextAmbient)) }
+ PointerInputWrapper(pointerInputHandler = recognizer.pointerInputHandler) {
+ children()
+ }
+}
+
+internal class LongPressGestureRecognizer(
+ val onLongPress: (PxPosition) -> Unit,
+ coroutineContext: CoroutineContext
+) {
+
+ private enum class State {
+ Idle, Primed, Fired
+ }
+
+ private var state = State.Idle
+ private val pointerPositions = linkedMapOf<Int, PxPosition>()
+ var longPressTimeout = LongPressTimeout
+ var job: Job? = null
+
+ val pointerInputHandler =
+ { changes: List<PointerInputChange>, pass: PointerEventPass ->
+
+ var changesToReturn = changes
+
+ if (pass == PointerEventPass.InitialDown && state == State.Fired) {
+ // If we are in the Fired state, we dispatched the long press event and pointers are still down so we
+ // should consume any up events to prevent other gesture detectors from responding to up.
+ changesToReturn = changesToReturn.map {
+ if (it.changedToUp()) {
+ it.consumeDownChange()
+ } else {
+ it
+ }
+ }
+ }
+
+ if (pass == PointerEventPass.PostUp) {
+ if (state == State.Idle && changes.all { it.changedToDown() }) {
+ // If we have not yet started and all of the changes changed to down, we are
+ // starting.
+ job = delay(longPressTimeout, coroutineContext) {
+ onLongPress.invoke(pointerPositions.asIterable().first().value)
+ state = State.Fired
+ }
+ pointerPositions.clear()
+ state = State.Primed
+ } else if (state != State.Idle && changes.all { it.changedToUpIgnoreConsumed() }) {
+ // If we have started and all of the changes changed to up, we are stopping.
+ reset()
+ }
+
+ if (state == State.Primed) {
+ // If we are primed, for all down pointers, keep track of their current positions, and for all
+ // other pointers, remove their tracked information.
+ changes.forEach {
+ if (it.current.down) {
+ pointerPositions[it.id] = it.current.position!!
+ } else {
+ pointerPositions.remove(it.id)
+ }
+ }
+ }
+ }
+
+ if (pass == PointerEventPass.PostDown &&
+ state == State.Primed &&
+ changes.any { it.anyPositionChangeConsumed() }
+ ) {
+ // If we are currently primed and any pointers had consumed movement, we should no longer fire the long
+ // press event so reset.
+ reset()
+ }
+
+ changesToReturn
+ }
+
+ private fun reset() {
+ job?.cancel()
+ state = State.Idle
+ }
+}
\ No newline at end of file
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/PressGestureDetector.kt b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/PressGestureDetector.kt
index 6d8beb2..e91a1e34 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/PressGestureDetector.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/PressGestureDetector.kt
@@ -26,7 +26,7 @@
onPress: ((PxPosition) -> Unit)? = null,
onRelease: (() -> Unit)? = null,
onCancel: (() -> Unit)? = null,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
PressIndicatorGestureDetector(onStart = onPress, onCancel = onCancel) {
PressReleasedGestureDetector(onRelease = onRelease, consumeDownOnStart = false) {
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/PressIndicatorGestureDetector.kt b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/PressIndicatorGestureDetector.kt
index 7705035..423b917 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/PressIndicatorGestureDetector.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/PressIndicatorGestureDetector.kt
@@ -53,7 +53,7 @@
onStart: ((PxPosition) -> Unit)? = null,
onStop: (() -> Unit)? = null,
onCancel: (() -> Unit)? = null,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
val recognizer = +memo { PressIndicatorGestureRecognizer() }
recognizer.onStart = onStart
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/PressReleasedGestureDetector.kt b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/PressReleasedGestureDetector.kt
index a524b3f..99a0daa 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/PressReleasedGestureDetector.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/PressReleasedGestureDetector.kt
@@ -52,7 +52,7 @@
fun PressReleasedGestureDetector(
onRelease: (() -> Unit)? = null,
consumeDownOnStart: Boolean = true,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
val recognizer = +memo { PressReleaseGestureRecognizer() }
recognizer.onRelease = onRelease
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/selection/Selection.kt b/ui/ui-framework/src/main/java/androidx/ui/core/selection/Selection.kt
index f4c753a..6e2dd35 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/selection/Selection.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/selection/Selection.kt
@@ -17,24 +17,30 @@
package androidx.ui.core.selection
import androidx.ui.core.LayoutCoordinates
-import androidx.ui.engine.geometry.Rect
+import androidx.ui.core.PxPosition
/**
* Data class of Selection.
*/
data class Selection(
/**
- * A box around the character at the start offset as Rect. This box' height is the line height,
- * and the width is the advance. Note: It is temporary to use Rect.
+ * The coordinates of the graphical position for selection start character offset.
+ *
+ * This graphical position is the point at the left bottom corner for LTR
+ * character, or right bottom corner for RTL character.
+ *
+ * This coordinates is in child widget coordinates system.
*/
- // TODO(qqd): After solving the problem of getting the coordinates of a character, figure out
- // what should the startOffset and endOffset should be.
- val startOffset: Rect,
+ val startCoordinates: PxPosition,
/**
- * A box around the character at the end offset as Rect. This box' height is the line height,
- * and the width is the advance. Note: It is temporary to use Rect.
+ * The coordinates of the graphical position for selection end character offset.
+ *
+ * This graphical position is the point at the left bottom corner for LTR
+ * character, or right bottom corner for RTL character.
+ *
+ * This coordinates is in child widget coordinates system.
*/
- val endOffset: Rect,
+ val endCoordinates: PxPosition,
/**
* The layout coordinates of the child which contains the start of the selection. If the child
* does not contain the start of the selection, this should be null.
@@ -51,13 +57,13 @@
var currentSelection = this.copy()
if (other.startLayoutCoordinates != null) {
currentSelection = currentSelection.copy(
- startOffset = other.startOffset,
+ startCoordinates = other.startCoordinates,
startLayoutCoordinates = other.startLayoutCoordinates
)
}
if (other.endLayoutCoordinates != null) {
currentSelection = currentSelection.copy(
- endOffset = other.endOffset,
+ endCoordinates = other.endCoordinates,
endLayoutCoordinates = other.endLayoutCoordinates
)
}
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/selection/SelectionContainer.kt b/ui/ui-framework/src/main/java/androidx/ui/core/selection/SelectionContainer.kt
index 54f078c..6dfb42c 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/selection/SelectionContainer.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/selection/SelectionContainer.kt
@@ -25,11 +25,9 @@
import androidx.ui.core.IntPx
import androidx.ui.core.Layout
import androidx.ui.core.OnPositioned
-import androidx.ui.core.PxPosition
import androidx.ui.core.gesture.DragGestureDetector
import androidx.ui.core.gesture.PressIndicatorGestureDetector
import androidx.ui.core.ipx
-import androidx.ui.core.px
import androidx.ui.core.round
/**
@@ -46,7 +44,7 @@
onSelectionChange: (Selection?) -> Unit,
/** Selection mode. The default mode is Vertical. */
mode: SelectionMode = SelectionMode.Vertical,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
val manager = +memo { SelectionManager() }
// TODO (qqd): After selection widget is fully implemented, evaluate if the following 2 items
@@ -129,17 +127,11 @@
) {
val startOffset = manager.containerLayoutCoordinates.childToLocal(
selection.startLayoutCoordinates,
- PxPosition(
- selection.startOffset.left.px,
- selection.startOffset.bottom.px
- )
+ selection.startCoordinates
)
val endOffset = manager.containerLayoutCoordinates.childToLocal(
selection.endLayoutCoordinates,
- PxPosition(
- selection.endOffset.right.px,
- selection.endOffset.bottom.px
- )
+ selection.endCoordinates
)
start.place(startOffset.x - HANDLE_WIDTH, startOffset.y)
end.place(endOffset.x, endOffset.y)
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/selection/SelectionManager.kt b/ui/ui-framework/src/main/java/androidx/ui/core/selection/SelectionManager.kt
index 99e2fa3..d7edee4 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/selection/SelectionManager.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/selection/SelectionManager.kt
@@ -21,7 +21,6 @@
import androidx.ui.core.PxPosition
import androidx.ui.core.gesture.DragObserver
import androidx.ui.core.px
-import androidx.ui.engine.geometry.Rect
internal class SelectionManager : SelectionRegistrar {
/**
@@ -99,11 +98,15 @@
onSelectionChange(result)
}
- // Get the coordinates of a character. Currently, it's the middle point of the left edge of the
- // bounding box of the character. This is a temporary solution.
- // TODO(qqd): Read how Android solve this problem.
- fun getCoordinatesForCharacter(box: Rect): PxPosition {
- return PxPosition(box.left.px, box.top.px + (box.bottom.px - box.top.px) / 2)
+ /**
+ * Adjust coordinates for given text offset.
+ *
+ * Currently [android.text.Layout.getLineBottom] returns y coordinates of the next
+ * line's top offset, which is not included in current line's hit area. To be able to
+ * hit current line, move up this y coordinates by 1 pixel.
+ */
+ fun getAdjustedCoordinates(p: PxPosition): PxPosition {
+ return PxPosition(p.x, p.y - 1.px)
}
fun handleDragObserver(dragStartHandle: Boolean): DragObserver {
@@ -121,11 +124,11 @@
// The position of the character where the drag gesture should begin. This is in
// the widget coordinates.
val beginCoordinates =
- getCoordinatesForCharacter(
+ getAdjustedCoordinates(
if (dragStartHandle) {
- selection!!.startOffset
+ selection!!.startCoordinates
} else {
- selection!!.endOffset
+ selection!!.endCoordinates
}
)
// Convert the position where drag gesture begins from widget coordinates to
@@ -150,7 +153,7 @@
} else {
containerLayoutCoordinates.childToLocal(
selection!!.startLayoutCoordinates!!,
- getCoordinatesForCharacter(selection!!.startOffset)
+ getAdjustedCoordinates(selection!!.startCoordinates)
)
}
@@ -158,7 +161,7 @@
if (dragStartHandle) {
containerLayoutCoordinates.childToLocal(
selection!!.endLayoutCoordinates!!,
- getCoordinatesForCharacter(selection!!.endOffset)
+ getAdjustedCoordinates(selection!!.endCoordinates)
)
} else {
dragBeginPosition + dragTotalDistance
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/selection/TextSelectionProcessor.kt b/ui/ui-framework/src/main/java/androidx/ui/core/selection/TextSelectionProcessor.kt
index 23604f0..23419ed 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/selection/TextSelectionProcessor.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/selection/TextSelectionProcessor.kt
@@ -18,8 +18,6 @@
import androidx.ui.core.PxPosition
import androidx.ui.core.px
-import androidx.ui.engine.geometry.Offset
-import androidx.ui.engine.geometry.Rect
import androidx.ui.text.TextSelection
import androidx.ui.text.TextPainter
import kotlin.math.max
@@ -39,19 +37,24 @@
/** The TextPainter object from Text widget. */
val textPainter: TextPainter
) {
- // TODO(qqd): Determine a set of coordinates around a character that we need.
/**
- * The bounding box of the character at the start offset as Rect. The bounding box includes the
- * top, bottom, left, and right of the character. Note: It is temporary to use Rect.
+ * The coordinates of the graphical position for selection start character offset.
+ *
+ * This graphical position is the point at the left bottom corner for LTR
+ * character, or right bottom corner for RTL character.
+ *
+ * This coordinates is in child widget coordinates system.
*/
- // TODO(qqd): After solving the problem of getting the coordinates of a character, figure out
- // what should the startOffset and endOffset should be.
- internal var startOffset = Rect.zero
+ internal var startCoordinates: PxPosition = PxPosition.Origin
/**
- * The bounding box of the character at the end offset as Rect. The bounding box includes the
- * top, bottom, left, and right of the character. Note: It is temporary to use Rect.
+ * The coordinates of the graphical position for selection end character offset.
+ *
+ * This graphical position is the point at the left bottom corner for LTR
+ * character, or right bottom corner for RTL character.
+ *
+ * This coordinates is in child widget coordinates system.
*/
- internal var endOffset = Rect.zero
+ internal var endCoordinates: PxPosition = PxPosition.Origin
/**
* A flag to check if the text widget contains the whole selection's start.
*/
@@ -94,22 +97,12 @@
val wordBoundary = textPainter.getWordBoundary(textSelectionStart)
textSelectionStart = wordBoundary.start
textSelectionEnd = wordBoundary.end
- } else {
- // Currently the implementation of selection is inclusive-inclusive which is a temporary
- // workaround, but inclusive-exclusive in Android. Thus before calling drawing selection
- // background, make the selection matches Android behaviour.
- textSelectionEnd = textSelectionEnd + 1
}
onSelectionChange(TextSelection(textSelectionStart, textSelectionEnd))
- // Currently the implementation of selection is inclusive-inclusive which is a temporary
- // workaround, but inclusive-exclusive in Android. Thus make the selection end matches Crane
- // behaviour.
- textSelectionEnd = textSelectionEnd - 1
-
- startOffset = textPainter.getBoundingBox(textSelectionStart)
- endOffset = textPainter.getBoundingBox(textSelectionEnd)
+ startCoordinates = getSelectionHandleCoordinates(textSelectionStart)
+ endCoordinates = getSelectionHandleCoordinates(textSelectionEnd)
this.containsWholeSelectionStart = containsWholeSelectionStart
this.containsWholeSelectionEnd = containsWholeSelectionEnd
@@ -125,10 +118,10 @@
position: PxPosition,
isStart: Boolean
): Pair<Int, Boolean> {
- // The text position of the border of selection. The default value is set to the beginning
- // of the text widget for the start border, and the very last position of the text widget
- // for the end border. If the widget contains the whole selection's border, this value will
- // be reset.
+ // The character offset of the border of selection. The default value is set to the
+ // beginning of the text widget for the start border, and the very last character offset
+ // of the text widget for the end border. If the widget contains the whole selection's
+ // border, this value will be reset.
var selectionBorder = if (isStart) 0 else max(length - 1, 0)
// Flag to check if the widget contains the whole selection's border.
var containsWholeSelectionBorder = false
@@ -138,21 +131,29 @@
val left = 0.px
val right = textPainter.width.px
// If the current text widget contains the whole selection's border, then find the exact
- // text position of the border, and the flag checking if the widget contains the whole
+ // character offset of the border, and the flag checking if the widget contains the whole
// selection's border will be set to true.
if (position.x >= left &&
position.x < right &&
position.y >= top &&
position.y < bottom
) {
- val offset = Offset(position.x.value, position.y.value)
- // Constrain the position of the selection border to be within the text range of the
- // current widget.
- val constrainedSelectionBorderPosition =
- textPainter.getPositionForOffset(offset).coerceIn(0, length - 1)
- selectionBorder = constrainedSelectionBorderPosition
+ // Constrain the character offset of the selection border to be within the text range
+ // of the current widget.
+ val constrainedSelectionBorderOffset =
+ textPainter.getOffsetForPosition(position).coerceIn(0, length - 1)
+ selectionBorder = constrainedSelectionBorderOffset
containsWholeSelectionBorder = true
}
return Pair(selectionBorder, containsWholeSelectionBorder)
}
+
+ private fun getSelectionHandleCoordinates(offset: Int): PxPosition {
+ val left = textPainter.getPrimaryHorizontal(offset)
+
+ val line = textPainter.getLineForOffset(offset)
+ val bottom = textPainter.getLineBottom(line)
+
+ return PxPosition(left.px, bottom.px)
+ }
}
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/vectorgraphics/Brush.kt b/ui/ui-framework/src/main/java/androidx/ui/core/vectorgraphics/Brush.kt
deleted file mode 100644
index b8fd184..0000000
--- a/ui/ui-framework/src/main/java/androidx/ui/core/vectorgraphics/Brush.kt
+++ /dev/null
@@ -1,105 +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.ui.core.vectorgraphics
-
-import androidx.ui.engine.geometry.Offset
-import androidx.ui.graphics.Color
-import androidx.ui.painting.Gradient
-import androidx.ui.painting.Paint
-import androidx.ui.painting.TileMode
-import androidx.ui.vectormath64.Matrix4
-
-val EmptyBrush = object : Brush {
- override fun applyBrush(p: Paint) {
- // NO-OP
- }
-}
-
-interface Brush {
- fun applyBrush(p: Paint)
-}
-
-/* inline */ class SolidColor(private val value: Color) : Brush {
- override fun applyBrush(p: Paint) {
- p.color = value
- }
-}
-
-typealias ColorStop = Pair<Color, Float>
-
-fun obtainBrush(brush: Any?): Brush {
- return when (brush) {
- is Int -> SolidColor(Color(brush))
- is Color -> SolidColor(brush)
- is Brush -> brush
- null -> EmptyBrush
- else -> throw IllegalArgumentException(brush.javaClass.simpleName +
- "Brush must be either a Color long, LinearGradient or RadialGradient")
- }
-}
-
-// TODO (njawad) replace with inline color class
-class LinearGradient(
- vararg colorStops: ColorStop,
- val startX: Float,
- val startY: Float,
- val endX: Float,
- val endY: Float,
- val tileMode: TileMode = TileMode.clamp
-) : Brush {
-
- private val colors: List<Color>
- private val stops: List<Float>
-
- init {
- colors = List(colorStops.size) { i -> colorStops[i].first }
- stops = List(colorStops.size) { i -> colorStops[i].second }
- }
-
- override fun applyBrush(p: Paint) {
- p.shader = Gradient.linear(
- Offset(startX, startY),
- Offset(endX, endY),
- colors,
- stops,
- tileMode)
- }
-}
-
-class RadialGradient(
- vararg colorStops: ColorStop,
- private val centerX: Float,
- private val centerY: Float,
- private val radius: Float,
- private val tileMode: TileMode = TileMode.clamp
-) : Brush {
-
- private val colors: List<Color>
- private val stops: List<Float>
-
- init {
- colors = List(colorStops.size) { it -> colorStops[it].first }
- stops = List(colorStops.size) { it -> colorStops[it].second }
- }
-
- override fun applyBrush(p: Paint) {
- p.shader = Gradient.radial(
- Offset(centerX, centerY),
- radius, colors, stops, tileMode, Matrix4(),
- null, 0.0f)
- }
-}
\ No newline at end of file
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/vectorgraphics/VectorAsset.kt b/ui/ui-framework/src/main/java/androidx/ui/core/vectorgraphics/VectorAsset.kt
new file mode 100644
index 0000000..80071b1
--- /dev/null
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/vectorgraphics/VectorAsset.kt
@@ -0,0 +1,439 @@
+/*
+ * 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.ui.core.vectorgraphics
+
+import androidx.compose.Composable
+import androidx.compose.composer
+import androidx.ui.core.Px
+import androidx.ui.graphics.vectorgraphics.BrushType
+import androidx.ui.graphics.vectorgraphics.DefaultAlpha
+import androidx.ui.graphics.vectorgraphics.DefaultGroupName
+import androidx.ui.graphics.vectorgraphics.DefaultPathName
+import androidx.ui.graphics.vectorgraphics.DefaultPivotX
+import androidx.ui.graphics.vectorgraphics.DefaultPivotY
+import androidx.ui.graphics.vectorgraphics.DefaultRotation
+import androidx.ui.graphics.vectorgraphics.DefaultScaleX
+import androidx.ui.graphics.vectorgraphics.DefaultScaleY
+import androidx.ui.graphics.vectorgraphics.DefaultStrokeLineCap
+import androidx.ui.graphics.vectorgraphics.DefaultStrokeLineJoin
+import androidx.ui.graphics.vectorgraphics.DefaultStrokeLineMiter
+import androidx.ui.graphics.vectorgraphics.DefaultStrokeLineWidth
+import androidx.ui.graphics.vectorgraphics.DefaultTranslationX
+import androidx.ui.graphics.vectorgraphics.DefaultTranslationY
+import androidx.ui.graphics.vectorgraphics.EmptyBrush
+import androidx.ui.graphics.vectorgraphics.EmptyPath
+import androidx.ui.graphics.vectorgraphics.PathData
+import androidx.ui.painting.StrokeCap
+import androidx.ui.painting.StrokeJoin
+import androidx.ui.vector.VectorScope
+import java.util.Stack
+import java.util.function.Consumer
+
+/**
+ * Builder used to construct a Vector graphic tree.
+ * This is useful for caching the result of expensive operations used to construct
+ * a vector graphic for compose.
+ * For example, the vector graphic could be serialized and downloaded from a server and represented
+ * internally in a VectorAsset before it is composed through [DrawVector]
+ * The generated VectorAsset is recommended to be memoized across composition calls to avoid
+ * doing redundant work
+ **/
+class VectorAssetBuilder(
+
+ /**
+ * Name of the vector asset
+ */
+ val name: String = DefaultGroupName,
+
+ /**
+ * Intrinsic width of the Vector in pixels
+ */
+ val defaultWidth: Px,
+
+ /**
+ * Intrinsic height of the Vector in pixels
+ */
+ val defaultHeight: Px,
+
+ /**
+ * Used to define the width of the viewport space. Viewport is basically the virtual canvas
+ * where the paths are drawn on.
+ */
+ val viewportWidth: Float,
+
+ /**
+ * Used to define the height of the viewport space. Viewport is basically the virtual canvas
+ * where the paths are drawn on.
+ */
+ val viewportHeight: Float
+) {
+
+ private val nodes = Stack<VectorGroup>()
+
+ private var root = VectorGroup()
+ private var isConsumed = false
+
+ private var currentGroup: VectorGroup = root
+ private set
+ get() = nodes.peek()
+
+ init {
+ nodes.add(root)
+ }
+
+ /**
+ * Create a new group and push it to the front of the stack of VectorAsset nodes
+ * @return This VectorAssetBuilder instance as a convenience for chaining calls
+ */
+ fun pushGroup(
+ name: String = DefaultGroupName,
+ rotate: Float = DefaultRotation,
+ pivotX: Float = DefaultPivotX,
+ pivotY: Float = DefaultPivotY,
+ scaleX: Float = DefaultScaleX,
+ scaleY: Float = DefaultScaleY,
+ translationX: Float = DefaultTranslationX,
+ translationY: Float = DefaultTranslationY,
+ clipPathData: PathData = EmptyPath
+ ): VectorAssetBuilder {
+ ensureNotConsumed()
+ val group = VectorGroup(
+ name,
+ rotate,
+ pivotX,
+ pivotY,
+ scaleX,
+ scaleY,
+ translationX,
+ translationY,
+ clipPathData
+ )
+ nodes.add(group)
+ currentGroup.addNode(group)
+ return this
+ }
+
+ /**
+ * Pops the topmost VectorGroup from this VectorAssetBuilder. This is used to indicate
+ * that no additional VectorAsset nodes will be added to the current VectorGroup
+ * @return This VectorAssetBuilder instance as a convenience for chaining calls
+ */
+ fun popGroup(): VectorAssetBuilder {
+ ensureNotConsumed()
+ nodes.pop()
+ return this
+ }
+
+ /**
+ * Add a path to the VectorAsset graphic. This represents a leaf node in the VectorAsset graphics
+ * tree structure
+ * @return This VectorAssetBuilder instance as a convenience for chaining calls
+ */
+ fun addPath(
+ pathData: PathData,
+ name: String = DefaultPathName,
+ fill: BrushType = EmptyBrush,
+ fillAlpha: Float = DefaultAlpha,
+ stroke: BrushType = EmptyBrush,
+ strokeAlpha: Float = DefaultAlpha,
+ strokeLineWidth: Float = DefaultStrokeLineWidth,
+ strokeLineCap: StrokeCap = DefaultStrokeLineCap,
+ strokeLineJoin: StrokeJoin = DefaultStrokeLineJoin,
+ strokeLineMiter: Float = DefaultStrokeLineMiter
+ ): VectorAssetBuilder {
+ ensureNotConsumed()
+ currentGroup.addNode(
+ VectorPath(
+ name,
+ pathData,
+ fill,
+ fillAlpha,
+ stroke,
+ strokeAlpha,
+ strokeLineWidth,
+ strokeLineCap,
+ strokeLineJoin,
+ strokeLineMiter
+ )
+ )
+ return this
+ }
+
+ /**
+ * Construct a VectorAsset. This concludes the creation process of a VectorAsset graphic
+ * This builder cannot be re-used to create additional VectorAsset instances
+ * @return Thew newly created VectorAsset instance
+ */
+ fun build(): VectorAsset {
+ ensureNotConsumed()
+ val vectorImage = VectorAsset(
+ name,
+ defaultWidth,
+ defaultHeight,
+ viewportWidth,
+ viewportHeight,
+ root
+ )
+
+ // reset state in case this builder is used again
+ nodes.clear()
+ root = VectorGroup()
+ nodes.add(root)
+
+ isConsumed = true
+
+ return vectorImage
+ }
+
+ /**
+ * Throws IllegalStateException if the VectorAssetBuilder is already been consumed
+ */
+ fun ensureNotConsumed() {
+ if (isConsumed) {
+ throw IllegalStateException("VectorAssetBuilder is single use, create " +
+ "a new instance to create a new VectorAsset")
+ }
+ }
+}
+
+sealed class VectorNode
+
+/**
+ * Vector graphics object that is generated as a result of [VectorAssetBuilder]]
+ * It can be composed and rendered by passing it as an argument to [DrawVector]
+ */
+class VectorAsset internal constructor(
+
+ /**
+ * Name of the Vector asset
+ */
+ val name: String,
+
+ /**
+ * Intrinsic width of the vector asset in pixels
+ */
+ val defaultWidth: Px,
+
+ /**
+ * Intrinsic height of the vector asset in pixels
+ */
+ val defaultHeight: Px,
+
+ /**
+ * Used to define the width of the viewport space. Viewport is basically the virtual canvas
+ * where the paths are drawn on.
+ */
+ val viewportWidth: Float,
+
+ /**
+ * Used to define the height of the viewport space. Viewport is basically the virtual canvas
+ * where the paths are drawn on.
+ */
+ val viewportHeight: Float,
+
+ /**
+ * Root group of the vector asset that contains all the child groups and paths
+ */
+ val root: VectorGroup
+)
+
+/**
+ * Defines a group of paths or subgroups, plus transformation information.
+ * The transformations are defined in the same coordinates as the viewport.
+ * The transformations are applied in the order of scale, rotate then translate.
+ */
+class VectorGroup(
+ /**
+ * Name of the corresponding group
+ */
+ val name: String = DefaultGroupName,
+
+ /**
+ * Rotation of the group in degrees
+ */
+ val rotation: Float = DefaultRotation,
+
+ /**
+ * X coordinate of the pivot point to rotate or scale the group
+ */
+ val pivotX: Float = DefaultPivotX,
+
+ /**
+ * Y coordinate of the pivot point to rotate or scale the group
+ */
+ val pivotY: Float = DefaultPivotY,
+
+ /**
+ * Scale factor in the X-axis to apply to the group
+ */
+ val scaleX: Float = DefaultScaleX,
+
+ /**
+ * Scale factor in the Y-axis to apply to the group
+ */
+ val scaleY: Float = DefaultScaleY,
+
+ /**
+ * Translation in virtual pixels to apply along the x-axis
+ */
+ val translationX: Float = DefaultTranslationX,
+
+ /**
+ * Translation in virtual pixels to apply along the y-axis
+ */
+ val translationY: Float = DefaultTranslationY,
+
+ /**
+ * Path information used to clip the content within the group
+ */
+ val clipPathData: PathData = EmptyPath
+
+) : VectorNode(), Iterable<VectorNode> {
+
+ private val children = ArrayList<VectorNode>()
+
+ internal fun addNode(node: VectorNode) {
+ children.add(node)
+ }
+
+ val size: Int
+ get() = children.size
+
+ operator fun get(index: Int): VectorNode {
+ return children[index]
+ }
+
+ override fun iterator(): Iterator<VectorNode> {
+ return object: Iterator<VectorNode> {
+
+ val it = children.iterator()
+
+ override fun hasNext(): Boolean = it.hasNext()
+
+ override fun next(): VectorNode = it.next()
+ }
+ }
+}
+
+/**
+ * Leaf node of a Vector graphics tree. This specifies a path shape and parameters
+ * to color and style the the shape itself
+ */
+class VectorPath(
+ /**
+ * Name of the corresponding path
+ */
+ val name: String = DefaultPathName,
+
+ /**
+ * Path information to render the shape of the path
+ */
+ val pathData: PathData,
+
+ /**
+ * Specifies the color or gradient used to fill the path
+ */
+ val fill: BrushType = EmptyBrush,
+
+ /**
+ * Opacity to fill the path
+ */
+ val fillAlpha: Float = DefaultAlpha,
+
+ /**
+ * Specifies the color or gradient used to fill the stroke
+ */
+ val stroke: BrushType = EmptyBrush,
+
+ /**
+ * Opacity to stroke the path
+ */
+ val strokeAlpha: Float = DefaultAlpha,
+
+ /**
+ * Width of the line to stroke the path
+ */
+ val strokeLineWidth: Float = DefaultStrokeLineWidth,
+
+ /**
+ * Specifies the linecap for a stroked path, either butt, round, or square. The default is butt.
+ */
+ val strokeLineCap: StrokeCap = DefaultStrokeLineCap,
+
+ /**
+ * Specifies the linejoin for a stroked path, either miter, round or bevel. The default is miter
+ */
+ val strokeLineJoin: StrokeJoin = DefaultStrokeLineJoin,
+
+ /**
+ * Specifies the miter limit for a stroked path, the default is 4
+ */
+ val strokeLineMiter: Float = DefaultStrokeLineMiter
+) : VectorNode()
+
+/**
+ * Composes a vector graphic into the composition tree based on the specification
+ * provided by given [VectorAsset]
+ */
+@Composable
+fun DrawVector(vectorImage: VectorAsset) {
+ DrawVector(
+ name = vectorImage.name,
+ viewportWidth = vectorImage.viewportWidth,
+ viewportHeight = vectorImage.viewportHeight,
+ defaultWidth = vectorImage.defaultWidth,
+ defaultHeight = vectorImage.defaultHeight) {
+ RenderVectorGroup(group = vectorImage.root)
+ }
+}
+
+/**
+ * Recursive method for creating the vector graphic composition by traversing
+ * the tree structure
+ */
+@Composable
+private fun VectorScope.RenderVectorGroup(group: VectorGroup) {
+ for (vectorNode in group) {
+ if (vectorNode is VectorPath) {
+ Path(
+ pathData = vectorNode.pathData,
+ name = vectorNode.name,
+ fill = vectorNode.fill,
+ fillAlpha = vectorNode.fillAlpha,
+ stroke = vectorNode.stroke,
+ strokeAlpha = vectorNode.strokeAlpha,
+ strokeLineWidth = vectorNode.strokeLineWidth,
+ strokeLineCap = vectorNode.strokeLineCap,
+ strokeLineJoin = vectorNode.strokeLineJoin,
+ strokeLineMiter = vectorNode.strokeLineMiter
+ )
+ } else if (vectorNode is VectorGroup) {
+ Group(
+ name = vectorNode.name,
+ rotation = vectorNode.rotation,
+ scaleX = vectorNode.scaleX,
+ scaleY = vectorNode.scaleY,
+ translationX = vectorNode.translationX,
+ translationY = vectorNode.translationY,
+ pivotX = vectorNode.pivotX,
+ pivotY = vectorNode.pivotY,
+ clipPathData = vectorNode.clipPathData) {
+ RenderVectorGroup(group = vectorNode)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/vectorgraphics/VectorCompose.kt b/ui/ui-framework/src/main/java/androidx/ui/core/vectorgraphics/VectorCompose.kt
new file mode 100644
index 0000000..4e36f04
--- /dev/null
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/vectorgraphics/VectorCompose.kt
@@ -0,0 +1,153 @@
+/*
+ * 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.ui.core.vectorgraphics
+
+import androidx.compose.Children
+import androidx.compose.Composable
+import androidx.compose.composer
+import androidx.compose.compositionReference
+import androidx.compose.memo
+import androidx.compose.onDispose
+import androidx.compose.onPreCommit
+import androidx.compose.unaryPlus
+
+import androidx.ui.core.Draw
+import androidx.ui.core.Px
+import androidx.ui.graphics.vectorgraphics.Brush
+import androidx.ui.graphics.vectorgraphics.BrushType
+import androidx.ui.graphics.vectorgraphics.DefaultAlpha
+import androidx.ui.graphics.vectorgraphics.DefaultGroupName
+import androidx.ui.graphics.vectorgraphics.DefaultPathName
+import androidx.ui.graphics.vectorgraphics.DefaultPivotX
+import androidx.ui.graphics.vectorgraphics.DefaultPivotY
+import androidx.ui.graphics.vectorgraphics.DefaultRotation
+import androidx.ui.graphics.vectorgraphics.DefaultScaleX
+import androidx.ui.graphics.vectorgraphics.DefaultScaleY
+import androidx.ui.graphics.vectorgraphics.DefaultStrokeLineCap
+import androidx.ui.graphics.vectorgraphics.DefaultStrokeLineJoin
+import androidx.ui.graphics.vectorgraphics.DefaultStrokeLineMiter
+import androidx.ui.graphics.vectorgraphics.DefaultStrokeLineWidth
+import androidx.ui.graphics.vectorgraphics.DefaultTranslationX
+import androidx.ui.graphics.vectorgraphics.DefaultTranslationY
+import androidx.ui.graphics.vectorgraphics.EmptyBrush
+import androidx.ui.graphics.vectorgraphics.EmptyPath
+import androidx.ui.graphics.vectorgraphics.PathData
+import androidx.ui.graphics.vectorgraphics.VectorComponent
+import androidx.ui.graphics.vectorgraphics.GroupComponent
+import androidx.ui.graphics.vectorgraphics.PathComponent
+import androidx.ui.graphics.vectorgraphics.createPath
+import androidx.ui.graphics.vectorgraphics.obtainBrush
+import androidx.ui.painting.StrokeCap
+import androidx.ui.painting.StrokeJoin
+import androidx.ui.vector.VectorScope
+import androidx.ui.vector.composeVector
+import androidx.ui.vector.disposeVector
+import java.util.Vector
+
+
+@Composable
+fun DrawVector(
+ viewportWidth: Float,
+ viewportHeight: Float,
+ defaultWidth: Px = Px(viewportWidth),
+ defaultHeight: Px = Px(viewportHeight),
+ name: String = "",
+ @Children children: @Composable() VectorScope.() -> Unit
+) {
+ val vector = +memo(name, viewportWidth, viewportHeight) {
+ VectorComponent(
+ name,
+ viewportWidth,
+ viewportHeight,
+ defaultWidth,
+ defaultHeight
+ )
+ }
+
+ val ref = +compositionReference()
+ composeVector(vector, ref, children)
+ +onPreCommit(vector) {
+ onDispose { disposeVector(vector, ref) }
+ }
+
+ Draw { canvas, _ ->
+ vector.draw(canvas)
+ }
+}
+
+@Composable
+fun VectorScope.Group(
+ name: String = DefaultGroupName,
+ rotation: Float = DefaultRotation,
+ pivotX: Float = DefaultPivotX,
+ pivotY: Float = DefaultPivotY,
+ scaleX: Float = DefaultScaleX,
+ scaleY: Float = DefaultScaleY,
+ translationX: Float = DefaultTranslationX,
+ translationY: Float = DefaultTranslationY,
+ clipPathData: PathData = EmptyPath,
+ @Children children: @Composable() VectorScope.() -> Unit
+) {
+
+ val clipPathNodes = +memo(clipPathData) {
+ createPath(clipPathData)
+ }
+ <GroupComponent
+ name = name
+ rotation = rotation
+ pivotX = pivotX
+ pivotY = pivotY
+ scaleX = scaleX
+ scaleY = scaleY
+ translationX = translationX
+ translationY = translationY
+ clipPathNodes = clipPathNodes
+ >
+ children()
+ </GroupComponent>
+}
+
+@Composable
+fun VectorScope.Path(
+ pathData: PathData,
+ name: String = DefaultPathName,
+ fill: BrushType = EmptyBrush,
+ fillAlpha: Float = DefaultAlpha,
+ stroke: BrushType = EmptyBrush,
+ strokeAlpha: Float = DefaultAlpha,
+ strokeLineWidth: Float = DefaultStrokeLineWidth,
+ strokeLineCap: StrokeCap = DefaultStrokeLineCap,
+ strokeLineJoin: StrokeJoin = DefaultStrokeLineJoin,
+ strokeLineMiter: Float = DefaultStrokeLineMiter
+) {
+ val pathNodes = createPath(pathData)
+ val fillBrush: Brush = obtainBrush(fill)
+ val strokeBrush: Brush = obtainBrush(stroke)
+
+ <PathComponent
+ name
+ pathNodes
+ fill = fillBrush
+ fillAlpha
+ stroke = strokeBrush
+ strokeAlpha
+ strokeLineWidth
+ strokeLineJoin
+ strokeLineCap
+ strokeLineMiter
+ />
+}
\ No newline at end of file
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/vectorgraphics/compat/VectorResource.kt b/ui/ui-framework/src/main/java/androidx/ui/core/vectorgraphics/compat/VectorResource.kt
index c129e97..a886ad0 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/vectorgraphics/compat/VectorResource.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/vectorgraphics/compat/VectorResource.kt
@@ -16,395 +16,41 @@
package androidx.ui.core.vectorgraphics.compat
+import androidx.compose.composer
import android.annotation.SuppressLint
import android.content.res.Resources
-import android.content.res.TypedArray
-import android.util.AttributeSet
-import android.util.Log
import android.util.Xml
-import androidx.core.content.res.TypedArrayUtils
-import androidx.ui.core.vectorgraphics.addPathNodes
-import androidx.ui.core.vectorgraphics.group
-import androidx.ui.core.vectorgraphics.path
-import androidx.ui.core.vectorgraphics.vector
-import androidx.ui.painting.StrokeCap
-import androidx.ui.painting.StrokeJoin
-import androidx.ui.core.vectorgraphics.DefaultPivotX
-import androidx.ui.core.vectorgraphics.DefaultPivotY
-import androidx.ui.core.vectorgraphics.DefaultRotate
-import androidx.ui.core.vectorgraphics.DefaultScaleX
-import androidx.ui.core.vectorgraphics.DefaultScaleY
-import androidx.ui.core.vectorgraphics.DefaultTranslateX
-import androidx.ui.core.vectorgraphics.DefaultTranslateY
-import androidx.ui.core.vectorgraphics.EmptyBrush
-import androidx.ui.core.vectorgraphics.EmptyPath
-import androidx.ui.core.vectorgraphics.PathNode
-import androidx.compose.Children
import androidx.compose.Composable
-import androidx.compose.composer
-import org.xmlpull.v1.XmlPullParser
+import androidx.compose.ambient
+import androidx.compose.memo
+import androidx.compose.unaryPlus
+import androidx.ui.core.ContextAmbient
+import androidx.ui.core.vectorgraphics.DrawVector
+import androidx.ui.core.vectorgraphics.VectorAsset
import org.xmlpull.v1.XmlPullParserException
-import java.io.IOException
-
-private val LINECAP_BUTT = 0
-private val LINECAP_ROUND = 1
-private val LINECAP_SQUARE = 2
-
-private val LINEJOIN_MITER = 0
-private val LINEJOIN_ROUND = 1
-private val LINEJOIN_BEVEL = 2
-
-private val FILL_TYPE_WINDING = 0
-
-private val SHAPE_CLIP_PATH = "clip-VPath"
-private val SHAPE_GROUP = "group"
-private val SHAPE_PATH = "path"
-
-private val LOGTAG = "VectorGraphicCreator"
-
-private fun getStrokeLineCap(id: Int, defValue: StrokeCap = StrokeCap.butt): StrokeCap =
- when (id) {
- LINECAP_BUTT -> StrokeCap.butt
- LINECAP_ROUND -> StrokeCap.round
- LINECAP_SQUARE -> StrokeCap.square
- else -> defValue
- }
-
-private fun getStrokeLineJoin(id: Int, defValue: StrokeJoin = StrokeJoin.miter): StrokeJoin =
- when (id) {
- LINEJOIN_MITER -> StrokeJoin.miter
- LINEJOIN_ROUND -> StrokeJoin.round
- LINEJOIN_BEVEL -> StrokeJoin.bevel
- else -> defValue
- }
@Composable
+fun VectorResource(resId: Int) {
+ val context = +ambient(ContextAmbient)
+ val res = context.resources
+ val theme = context.theme
+ val vectorImage = +memo(resId) {
+ loadVectorResource(theme, res, resId)
+ }
+ DrawVector(vectorImage = vectorImage)
+}
+
+@Throws(XmlPullParserException::class)
@SuppressWarnings("RestrictedApi")
-private fun inflateGroup(
- a: TypedArray,
- parser: XmlPullParser,
- @Suppress("UNUSED_PARAMETER") theme: Resources.Theme?,
- @Children childNodes: @Composable() () -> Unit
-) {
- // Account for any configuration changes.
- // mChangingConfigurations |= Utils.getChangingConfigurations(a);
+fun loadVectorResource(theme: Resources.Theme? = null, res: Resources, resId: Int): VectorAsset {
- // Extract the theme attributes, if any.
- // mThemeAttrs = null // TODO TINT THEME Not supported yet a.extractThemeAttrs();
-
- // This is added in API 11
- val rotate = TypedArrayUtils.getNamedFloat(
- a, parser, "rotation",
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_ROTATION,
- DefaultRotate
- )
-
- val pivotX = a.getFloat(
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_PIVOT_X,
- DefaultPivotX
- )
- val pivotY = a.getFloat(
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_PIVOT_Y,
- DefaultPivotY
- )
-
- // This is added in API 11
- val scaleX = TypedArrayUtils.getNamedFloat(
- a, parser, "scaleX",
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_SCALE_X,
- DefaultScaleX
- )
-
- // This is added in API 11
- val scaleY = TypedArrayUtils.getNamedFloat(
- a, parser, "scaleY",
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_SCALE_Y,
- DefaultScaleY
- )
-
- val translateX = TypedArrayUtils.getNamedFloat(
- a, parser, "translateX",
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_TRANSLATE_X,
- DefaultTranslateX
- )
- val translateY = TypedArrayUtils.getNamedFloat(
- a, parser, "translateY",
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_TRANSLATE_Y,
- DefaultTranslateY
- )
-
- val name: String =
- a.getString(AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_NAME) ?: ""
-
- parser.next()
-
- // TODO parse clip path
- val clipPathData = EmptyPath
- group(
- name = name,
- rotate = rotate,
- scaleX = scaleX,
- scaleY = scaleY,
- translateX = translateX,
- translateY = translateY,
- pivotX = pivotX,
- pivotY = pivotY,
- clipPathData = clipPathData
- ) {
- childNodes()
- }
-}
-
-@Suppress("UNUSED_PARAMETER")
-@Composable
-private fun inflateClip(
- a: TypedArray,
- parser: XmlPullParser,
- theme: Resources.Theme?,
- @Children childNodes: @Composable() () -> Unit
-) {
-// var pathName: String? =
-// a.getString(AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_CLIP_PATH_NAME)
-// if (pathName == null) {
-// pathName = ""
-// }
-// val pathData = a.getString(AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_CLIP_PATH_PATH_DATA)
-
- // TODO (njawad) finish parsing clip paths from xml resources
-}
-
-@Composable
-@SuppressWarnings("RestrictedApi")
-private fun inflatePath(a: TypedArray, parser: XmlPullParser, theme: Resources.Theme?) {
- val hasPathData = TypedArrayUtils.hasAttribute(parser, "pathData")
- if (!hasPathData) {
- // If there is no pathData in the VPath tag, then this is an empty VPath,
- // nothing need to be drawn.
- Log.v("VectorPath", "no path data available skipping path")
- return
- }
-
- val name: String = a.getString(AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_NAME) ?: ""
-
- val pathStr = a.getString(AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_PATH_DATA)
-
- val pathData: Array<PathNode> = addPathNodes(pathStr)
-
- val fillColor = TypedArrayUtils.getNamedComplexColor(
- a, parser, theme, "fillColor",
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_FILL_COLOR, 0
- )
- // TODO(njawad): restore these when they are used
-/* val fillAlpha = TypedArrayUtils.getNamedFloat(
- a, parser, "fillAlpha",
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_FILL_ALPHA, 1.0f
- )
- val lineCap = TypedArrayUtils.getNamedInt(
- a, parser, "strokeLineCap",
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_LINE_CAP, -1
- )
- val strokeLineCap =
- getStrokeLineCap(lineCap, StrokeCap.butt)
- val lineJoin = TypedArrayUtils.getNamedInt(
- a, parser, "strokeLineJoin",
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_LINE_JOIN, -1
- )
- val strokeLineJoin =
- getStrokeLineJoin(lineJoin, StrokeJoin.bevel)
- val strokeMiterlimit = TypedArrayUtils.getNamedFloat(
- a, parser, "strokeMiterLimit",
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_MITER_LIMIT,
- 1.0f
- )*/
- val strokeColor = TypedArrayUtils.getNamedComplexColor(
- a, parser, theme, "strokeColor",
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_COLOR, 0
- )
- val strokeAlpha = TypedArrayUtils.getNamedFloat(
- a, parser, "strokeAlpha",
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_ALPHA, 1.0f
- )
- val strokeLineWidth = TypedArrayUtils.getNamedFloat(
- a, parser, "strokeWidth",
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_WIDTH, 1.0f
- )
- /*val trimPathEnd = TypedArrayUtils.getNamedFloat(
- a, parser, "trimPathEnd",
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_TRIM_PATH_END, 1.0f
- )
- val trimPathOffset = TypedArrayUtils.getNamedFloat(
- a, parser, "trimPathOffset",
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_TRIM_PATH_OFFSET,
- 0.0f
- )
- val trimPathStart = TypedArrayUtils.getNamedFloat(
- a, parser, "trimPathStart",
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_TRIM_PATH_START,
- 0.0f
- )
- val fillRule = TypedArrayUtils.getNamedInt(
- a, parser, "fillType",
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_TRIM_PATH_FILLTYPE,
- FILL_TYPE_WINDING
- )*/
-
- // TODO update path with additional params
- path(
- name = name,
- fill = if (fillColor.willDraw()) fillColor.color else EmptyBrush,
- strokeAlpha = strokeAlpha,
- strokeLineWidth = strokeLineWidth,
- stroke = if (strokeColor.willDraw()) strokeColor.color else EmptyBrush,
- pathData = pathData
- )
-}
-
-@Composable
-@SuppressWarnings("RestrictedApi")
-private fun inflateInner(
- res: Resources,
- attrs: AttributeSet,
- theme: Resources.Theme?,
- innerDepth: Int,
- parser: XmlPullParser
-) {
- var eventType = parser.getEventType()
-
- // Parse everything until the end of the VectorGraphic element.
- while (eventType != XmlPullParser.END_DOCUMENT &&
- (parser.getDepth() >= innerDepth || eventType != XmlPullParser.END_TAG)
- ) {
- if (eventType == XmlPullParser.START_TAG) {
- val tagName = parser.getName()
- if (SHAPE_PATH.equals(tagName)) {
- Log.v("VectorPath", "parsing path...")
- val a = TypedArrayUtils.obtainAttributes(
- res, theme, attrs,
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH
- )
- inflatePath(a = a, parser = parser, theme = theme)
- a.recycle()
- } else if (SHAPE_CLIP_PATH.equals(tagName)) {
- val a = TypedArrayUtils.obtainAttributes(
- res, theme, attrs,
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_CLIP_PATH
- )
- inflateClip(a = a, parser = parser, theme = theme) {
- inflateInner(
- res = res,
- parser = parser,
- attrs = attrs,
- theme = theme,
- innerDepth = innerDepth
- )
- }
- a.recycle()
- } else if (SHAPE_GROUP.equals(tagName)) {
- val a = TypedArrayUtils.obtainAttributes(
- res, theme, attrs,
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP
- )
- inflateGroup(a = a, parser = parser, theme = theme) {
- inflateInner(
- res = res,
- parser = parser,
- attrs = attrs,
- theme = theme,
- innerDepth = innerDepth
- )
- }
- a.recycle()
- }
- }
- eventType = parser.next()
- }
-}
-
-@Composable
-@SuppressWarnings("RestrictedApi")
-private fun inflate(
- res: Resources,
- parser: XmlPullParser,
- attrs: AttributeSet,
- theme: Resources.Theme?
-) {
- val innerDepth = parser.getDepth() + 1
-
- val vectorAttrs = TypedArrayUtils.obtainAttributes(
- res, theme, attrs,
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_TYPE_ARRAY
- )
-
- // TODO (njawad) handle mirroring here
-// state.mAutoMirrored = TypedArrayUtils.getNamedBoolean(a, parser, "autoMirrored",
-// AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_AUTO_MIRRORED, state.mAutoMirrored)
-
- val viewportWidth = TypedArrayUtils.getNamedFloat(
- vectorAttrs, parser, "viewportWidth",
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_VIEWPORT_WIDTH,
- 0.0f
- )
-
- val viewportHeight = TypedArrayUtils.getNamedFloat(
- vectorAttrs, parser, "viewportHeight",
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_VIEWPORT_HEIGHT,
- 0.0f
- )
-
- if (viewportWidth <= 0) {
- throw XmlPullParserException(
- vectorAttrs.getPositionDescription() +
- "VectorGraphic requires viewportWidth > 0"
- )
- } else if (viewportHeight <= 0) {
- throw XmlPullParserException(
- vectorAttrs.getPositionDescription() +
- "VectorGraphic requires viewportHeight > 0"
- )
- }
-
- val defaultWidth = vectorAttrs.getDimension(
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_WIDTH, 0.0f
- )
- val defaultHeight = vectorAttrs.getDimension(
- AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_HEIGHT, 0.0f
- )
-
- vector(
- name = "vector",
- defaultWidth = defaultWidth,
- defaultHeight = defaultHeight,
- viewportWidth = viewportWidth,
- viewportHeight = viewportHeight
- ) {
- inflateInner(
- res = res,
- attrs = attrs,
- theme = theme,
- innerDepth = innerDepth,
- parser = parser
- )
- }
-}
-
-@Composable
-fun vectorResource(res: Resources, resId: Int) {
@SuppressLint("ResourceType") val parser = res.getXml(resId)
val attrs = Xml.asAttributeSet(parser)
- var type: Int
- try {
- type = parser.next()
- while (type != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) {
- // Empty loop
- type = parser.next()
- }
- if (type != XmlPullParser.START_TAG) {
- throw XmlPullParserException("No start tag found")
- }
- inflate(res = res, parser = parser, attrs = attrs, theme = null)
- } catch (e: XmlPullParserException) {
- Log.e(LOGTAG, "parser error", e)
- } catch (e: IOException) {
- Log.e(LOGTAG, "parser error", e)
+ val builder = parser.seekToStartTag().createVectorImageBuilder(res, theme, attrs)
+
+ while (!parser.isAtEnd()) {
+ parser.parseCurrentVectorNode(res, attrs, theme, builder)
+ parser.next()
}
-}
+ return builder.build()
+}
\ No newline at end of file
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/vectorgraphics/compat/XmlVectorParser.kt b/ui/ui-framework/src/main/java/androidx/ui/core/vectorgraphics/compat/XmlVectorParser.kt
new file mode 100644
index 0000000..88749ef
--- /dev/null
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/vectorgraphics/compat/XmlVectorParser.kt
@@ -0,0 +1,394 @@
+/*
+ * 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.ui.core.vectorgraphics.compat
+
+import android.content.res.Resources
+import android.util.AttributeSet
+import androidx.core.content.res.TypedArrayUtils
+import androidx.ui.core.Px
+import androidx.ui.core.vectorgraphics.VectorAssetBuilder
+import androidx.ui.graphics.vectorgraphics.DefaultPivotX
+import androidx.ui.graphics.vectorgraphics.DefaultPivotY
+import androidx.ui.graphics.vectorgraphics.DefaultRotation
+import androidx.ui.graphics.vectorgraphics.DefaultScaleX
+import androidx.ui.graphics.vectorgraphics.DefaultScaleY
+import androidx.ui.graphics.vectorgraphics.DefaultTranslationX
+import androidx.ui.graphics.vectorgraphics.DefaultTranslationY
+import androidx.ui.graphics.vectorgraphics.EmptyBrush
+import androidx.ui.graphics.vectorgraphics.EmptyPath
+import androidx.ui.graphics.vectorgraphics.PathNode
+import androidx.ui.graphics.vectorgraphics.addPathNodes
+import androidx.ui.painting.StrokeCap
+import androidx.ui.painting.StrokeJoin
+import org.xmlpull.v1.XmlPullParser
+import org.xmlpull.v1.XmlPullParserException
+
+private val LINECAP_BUTT = 0
+private val LINECAP_ROUND = 1
+private val LINECAP_SQUARE = 2
+
+private val LINEJOIN_MITER = 0
+private val LINEJOIN_ROUND = 1
+private val LINEJOIN_BEVEL = 2
+
+private val FILL_TYPE_WINDING = 0
+
+private val SHAPE_CLIP_PATH = "clip-VPath"
+private val SHAPE_GROUP = "group"
+private val SHAPE_PATH = "path"
+
+private fun getStrokeLineCap(id: Int, defValue: StrokeCap = StrokeCap.butt): StrokeCap =
+ when (id) {
+ LINECAP_BUTT -> StrokeCap.butt
+ LINECAP_ROUND -> StrokeCap.round
+ LINECAP_SQUARE -> StrokeCap.square
+ else -> defValue
+ }
+
+private fun getStrokeLineJoin(id: Int, defValue: StrokeJoin = StrokeJoin.miter): StrokeJoin =
+ when (id) {
+ LINEJOIN_MITER -> StrokeJoin.miter
+ LINEJOIN_ROUND -> StrokeJoin.round
+ LINEJOIN_BEVEL -> StrokeJoin.bevel
+ else -> defValue
+ }
+
+internal fun XmlPullParser.isAtEnd(): Boolean =
+ eventType == XmlPullParser.END_DOCUMENT ||
+ (depth < 1 && eventType == XmlPullParser.END_TAG)
+
+internal fun XmlPullParser.parseCurrentVectorNode(
+ res: Resources,
+ attrs: AttributeSet,
+ theme: Resources.Theme? = null,
+ builder: VectorAssetBuilder
+) {
+ when (eventType) {
+ XmlPullParser.START_TAG -> {
+ when (name) {
+ SHAPE_PATH -> {
+ parsePath(res, theme, attrs, builder)
+ }
+ SHAPE_CLIP_PATH -> {
+ // TODO parse clipping paths
+ }
+ SHAPE_GROUP -> {
+ parseGroup(res, theme, attrs, builder)
+ }
+ }
+ }
+ XmlPullParser.END_TAG -> {
+ if (SHAPE_GROUP == name) {
+ builder.popGroup()
+ }
+ }
+ }
+}
+
+/**
+ * Helper method to seek to the first tag within the VectorDrawable xml asset
+ */
+@Throws(XmlPullParserException::class)
+internal fun XmlPullParser.seekToStartTag(): XmlPullParser {
+ var type = next()
+ while (type != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) {
+ // Empty loop
+ type = next()
+ }
+ if (type != XmlPullParser.START_TAG) {
+ throw XmlPullParserException("No start tag found")
+ }
+ return this
+}
+
+@SuppressWarnings("RestrictedApi")
+internal fun XmlPullParser.createVectorImageBuilder(
+ res: Resources,
+ theme: Resources.Theme?,
+ attrs: AttributeSet
+): VectorAssetBuilder {
+ val vectorAttrs = TypedArrayUtils.obtainAttributes(
+ res,
+ theme,
+ attrs,
+ AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_TYPE_ARRAY
+ )
+
+ // TODO (njawad) handle mirroring here
+// state.mAutoMirrored = TypedArrayUtils.getNamedBoolean(a, parser, "autoMirrored",
+// AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_AUTO_MIRRORED, state.mAutoMirrored)
+
+ val viewportWidth = TypedArrayUtils.getNamedFloat(
+ vectorAttrs,
+ this,
+ "viewportWidth",
+ AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_VIEWPORT_WIDTH,
+ 0.0f
+ )
+
+ val viewportHeight = TypedArrayUtils.getNamedFloat(
+ vectorAttrs,
+ this,
+ "viewportHeight",
+ AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_VIEWPORT_HEIGHT,
+ 0.0f
+ )
+
+ if (viewportWidth <= 0) {
+ throw XmlPullParserException(
+ vectorAttrs.getPositionDescription() +
+ "<VectorGraphic> tag requires viewportWidth > 0"
+ )
+ } else if (viewportHeight <= 0) {
+ throw XmlPullParserException(
+ vectorAttrs.getPositionDescription() +
+ "<VectorGraphic> tag requires viewportHeight > 0"
+ )
+ }
+
+ val defaultWidth = vectorAttrs.getDimension(
+ AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_WIDTH, 0.0f
+ )
+ val defaultHeight = vectorAttrs.getDimension(
+ AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_HEIGHT, 0.0f
+ )
+
+ vectorAttrs.recycle()
+
+ return VectorAssetBuilder(
+ defaultWidth = Px(defaultWidth),
+ defaultHeight = Px(defaultHeight),
+ viewportWidth = viewportWidth,
+ viewportHeight = viewportHeight
+ )
+}
+
+@Throws(IllegalArgumentException::class)
+@SuppressWarnings("RestrictedApi")
+internal fun XmlPullParser.parsePath(
+ res: Resources,
+ theme: Resources.Theme?,
+ attrs: AttributeSet,
+ builder: VectorAssetBuilder
+) {
+ val a = TypedArrayUtils.obtainAttributes(
+ res,
+ theme,
+ attrs,
+ AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH
+ )
+
+ val hasPathData = TypedArrayUtils.hasAttribute(this, "pathData")
+ if (!hasPathData) {
+ // If there is no pathData in the VPath tag, then this is an empty VPath,
+ // nothing need to be drawn.
+ throw IllegalArgumentException("No path data available")
+ }
+
+ val name: String = a.getString(AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_NAME) ?: ""
+
+ val pathStr = a.getString(AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_PATH_DATA)
+
+ val pathData: Array<PathNode> = addPathNodes(pathStr)
+
+ val fillColor = TypedArrayUtils.getNamedComplexColor(
+ a,
+ this,
+ theme,
+ "fillColor",
+ AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_FILL_COLOR, 0
+ )
+ val fillAlpha = TypedArrayUtils.getNamedFloat(
+ a,
+ this,
+ "fillAlpha",
+ AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_FILL_ALPHA, 1.0f
+ )
+ val lineCap = TypedArrayUtils.getNamedInt(
+ a,
+ this,
+ "strokeLineCap",
+ AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_LINE_CAP, -1
+ )
+ val strokeLineCap = getStrokeLineCap(lineCap, StrokeCap.butt)
+ val lineJoin = TypedArrayUtils.getNamedInt(
+ a,
+ this,
+ "strokeLineJoin",
+ AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_LINE_JOIN, -1
+ )
+ val strokeLineJoin = getStrokeLineJoin(lineJoin, StrokeJoin.bevel)
+ val strokeMiterLimit = TypedArrayUtils.getNamedFloat(
+ a,
+ this,
+ "strokeMiterLimit",
+ AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_MITER_LIMIT,
+ 1.0f
+ )
+ val strokeColor = TypedArrayUtils.getNamedComplexColor(
+ a,
+ this,
+ theme,
+ "strokeColor",
+ AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_COLOR, 0
+ )
+ val strokeAlpha = TypedArrayUtils.getNamedFloat(
+ a,
+ this,
+ "strokeAlpha",
+ AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_ALPHA, 1.0f
+ )
+ val strokeLineWidth = TypedArrayUtils.getNamedFloat(
+ a,
+ this,
+ "strokeWidth",
+ AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_WIDTH, 1.0f
+ )
+
+ // TODO (njawad) handle trim paths + fill rule
+// val trimPathEnd = TypedArrayUtils.getNamedFloat(
+// a, this, "trimPathEnd",
+// AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_TRIM_PATH_END, 1.0f
+// )
+// val trimPathOffset = TypedArrayUtils.getNamedFloat(
+// a, this, "trimPathOffset",
+// AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_TRIM_PATH_OFFSET,
+// 0.0f
+// )
+// val trimPathStart = TypedArrayUtils.getNamedFloat(
+// a, this, "trimPathStart",
+// AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_TRIM_PATH_START,
+// 0.0f
+// )
+// val fillRule = TypedArrayUtils.getNamedInt(
+// a, this, "fillType",
+// AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_TRIM_PATH_FILLTYPE,
+// FILL_TYPE_WINDING
+// )
+
+ a.recycle()
+
+ @Suppress("IMPLICIT_CAST_TO_ANY")
+ val fillBrush = if (fillColor.willDraw()) fillColor.color else EmptyBrush
+
+ @Suppress("IMPLICIT_CAST_TO_ANY")
+ val strokeBrush = if (strokeColor.willDraw()) strokeColor.color else EmptyBrush
+
+ builder.addPath(
+ pathData,
+ name,
+ fillBrush,
+ fillAlpha,
+ strokeBrush,
+ strokeAlpha,
+ strokeLineWidth,
+ strokeLineCap,
+ strokeLineJoin,
+ strokeMiterLimit)
+}
+
+@SuppressWarnings("RestrictedApi")
+internal fun XmlPullParser.parseGroup(
+ res: Resources,
+ theme: Resources.Theme?,
+ attrs: AttributeSet,
+ builder: VectorAssetBuilder
+) {
+ val a = TypedArrayUtils.obtainAttributes(
+ res,
+ theme,
+ attrs,
+ AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP
+ )
+
+ // Account for any configuration changes.
+ // mChangingConfigurations |= Utils.getChangingConfigurations(a);
+
+ // Extract the theme attributes, if any.
+ // mThemeAttrs = null // TODO TINT THEME Not supported yet a.extractThemeAttrs();
+
+ // This is added in API 11
+ val rotate = TypedArrayUtils.getNamedFloat(
+ a,
+ this,
+ "rotation",
+ AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_ROTATION,
+ DefaultRotation
+ )
+
+ val pivotX = a.getFloat(
+ AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_PIVOT_X,
+ DefaultPivotX
+ )
+ val pivotY = a.getFloat(
+ AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_PIVOT_Y,
+ DefaultPivotY
+ )
+
+ // This is added in API 11
+ val scaleX = TypedArrayUtils.getNamedFloat(
+ a,
+ this,
+ "scaleX",
+ AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_SCALE_X,
+ DefaultScaleX
+ )
+
+ // This is added in API 11
+ val scaleY = TypedArrayUtils.getNamedFloat(
+ a,
+ this,
+ "scaleY",
+ AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_SCALE_Y,
+ DefaultScaleY
+ )
+
+ val translateX = TypedArrayUtils.getNamedFloat(
+ a,
+ this,
+ "translationX",
+ AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_TRANSLATE_X,
+ DefaultTranslationX
+ )
+ val translateY = TypedArrayUtils.getNamedFloat(
+ a,
+ this,
+ "translationY",
+ AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_TRANSLATE_Y,
+ DefaultTranslationY
+ )
+
+ val name: String =
+ a.getString(AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_NAME) ?: ""
+
+ // TODO parse clip path
+ val clipPathData = EmptyPath
+
+ a.recycle()
+
+ builder.pushGroup(
+ name,
+ rotate,
+ scaleX,
+ scaleY,
+ translateX,
+ translateY,
+ pivotX,
+ pivotY,
+ clipPathData
+ )
+}
diff --git a/ui/ui-framework/src/main/java/androidx/ui/semantics/Semantics.kt b/ui/ui-framework/src/main/java/androidx/ui/semantics/Semantics.kt
new file mode 100644
index 0000000..a771514
--- /dev/null
+++ b/ui/ui-framework/src/main/java/androidx/ui/semantics/Semantics.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.ui.semantics
+
+import androidx.compose.Children
+import androidx.compose.Composable
+import androidx.compose.ambient
+import androidx.compose.composer
+import androidx.compose.unaryPlus
+import androidx.ui.core.DefaultTestTag
+import androidx.ui.core.SemanticsComponentNode
+import androidx.ui.core.TestTag
+import androidx.ui.core.TestTagAmbient
+import androidx.ui.core.semantics.SemanticsConfiguration
+import androidx.ui.core.semantics.getOrNull
+
+@Composable
+fun Semantics(
+ /**
+ * If 'container' is true, this component will introduce a new
+ * node in the semantics tree. Otherwise, the semantics will be
+ * merged with the semantics of any ancestors.
+ *
+ * Whether descendants of this component can add their semantic information
+ * to the [SemanticsNode] introduced by this configuration is controlled by
+ * [explicitChildNodes].
+ */
+ container: Boolean = false,
+ /**
+ * Whether descendants of this component are allowed to add semantic
+ * information to the [SemanticsNode] annotated by this widget.
+ *
+ * When set to false descendants are allowed to annotate [SemanticNode]s of
+ * their parent with the semantic information they want to contribute to the
+ * semantic tree.
+ * When set to true the only way for descendants to contribute semantic
+ * information to the semantic tree is to introduce new explicit
+ * [SemanticNode]s to the tree.
+ *
+ * This setting is often used in combination with [isSemanticBoundary] to
+ * create semantic boundaries that are either writable or not for children.
+ */
+ explicitChildNodes: Boolean = false,
+ properties: (SemanticsPropertyReceiver.() -> Unit)? = null,
+ @Children children: @Composable() () -> Unit
+) {
+ val providedTestTag = +ambient(TestTagAmbient)
+ val semanticsConfiguration = SemanticsConfiguration().also {
+ properties?.invoke(it)
+ // TODO(b/138173101): replace with the real thing
+ it.testTag = it.getOrNull(SemanticsProperties.TestTag) ?: providedTestTag
+ }
+
+ <SemanticsComponentNode
+ container
+ explicitChildNodes
+ semanticsConfiguration> TestTag(tag = DefaultTestTag) {
+ children()
+ }
+ </SemanticsComponentNode>
+}
diff --git a/ui/ui-framework/src/main/java/androidx/ui/semantics/SemanticsProperties.kt b/ui/ui-framework/src/main/java/androidx/ui/semantics/SemanticsProperties.kt
new file mode 100644
index 0000000..3df6c88
--- /dev/null
+++ b/ui/ui-framework/src/main/java/androidx/ui/semantics/SemanticsProperties.kt
@@ -0,0 +1,93 @@
+/*
+ * 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.ui.semantics
+
+import androidx.ui.text.style.TextDirection
+
+object SemanticsProperties {
+ val AccessibilityLabel = object : SemanticsPropertyKey<String>("AccessibilityLabel") {
+ override fun merge(existingValue: String, newValue: String): String {
+ // TODO(b/138173613): Needs TextDirection, probably needs to pass both nodes
+ // to retrieve it
+ return existingValue + "\n" + newValue
+ }
+ }
+
+ val AccessibilityValue = SemanticsPropertyKey<String>("AccessibilityValue")
+
+ val Enabled = SemanticsPropertyKey<Boolean>("Enabled")
+
+ val Hidden = SemanticsPropertyKey<Boolean>("Hidden")
+
+ val TextDirection = SemanticsPropertyKey<TextDirection>("TextDirection")
+
+ // TODO(b/138172781): Move to FoundationSemanticsProperties
+ val TestTag = SemanticsPropertyKey<String>("TestTag")
+}
+
+class SemanticsActions {
+ companion object {
+ val OnClick = SemanticsPropertyKey<AccessibilityAction<() -> Unit>>("OnClick")
+
+ val CustomActions =
+ SemanticsPropertyKey<List<AccessibilityAction<() -> Unit>>>("CustomActions")
+ }
+}
+
+var SemanticsPropertyReceiver.accessibilityLabel by SemanticsProperties.AccessibilityLabel
+
+var SemanticsPropertyReceiver.accessibilityValue by SemanticsProperties.AccessibilityValue
+
+var SemanticsPropertyReceiver.enabled by SemanticsProperties.Enabled
+
+var SemanticsPropertyReceiver.hidden by SemanticsProperties.Hidden
+
+var SemanticsPropertyReceiver.textDirection by SemanticsProperties.TextDirection
+
+var SemanticsPropertyReceiver.onClick by SemanticsActions.OnClick
+
+fun SemanticsPropertyReceiver.onClick(label: String? = null, action: () -> Unit) {
+ this[SemanticsActions.OnClick] = AccessibilityAction(label, action)
+}
+
+var SemanticsPropertyReceiver.customActions by SemanticsActions.CustomActions
+
+// TODO(b/138172781): Move to FoundationSemanticsProperties.kt
+var SemanticsPropertyReceiver.testTag by SemanticsProperties.TestTag
+
+// TODO(b/138173613): Use this for merging labels
+/*
+private fun concatStrings(
+ thisString: String?,
+ otherString: String?,
+ thisTextDirection: TextDirection?,
+ otherTextDirection: TextDirection?
+): String? {
+ if (otherString.isNullOrEmpty())
+ return thisString
+ var nestedLabel = otherString
+ if (thisTextDirection != otherTextDirection && otherTextDirection != null) {
+ nestedLabel = when (otherTextDirection) {
+ TextDirection.Rtl -> "${Unicode.RLE}$nestedLabel${Unicode.PDF}"
+ TextDirection.Ltr -> "${Unicode.LRE}$nestedLabel${Unicode.PDF}"
+ }
+ }
+ if (thisString.isNullOrEmpty())
+ return nestedLabel
+ return "$thisString\n$nestedLabel"
+}
+*/
diff --git a/ui/ui-framework/src/test/java/androidx/ui/core/InputFieldDelegateTest.kt b/ui/ui-framework/src/test/java/androidx/ui/core/InputFieldDelegateTest.kt
deleted file mode 100644
index 0873772..0000000
--- a/ui/ui-framework/src/test/java/androidx/ui/core/InputFieldDelegateTest.kt
+++ /dev/null
@@ -1,316 +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.ui.core
-
-import androidx.ui.engine.geometry.Rect
-import androidx.ui.graphics.Color
-import androidx.ui.input.CommitTextEditOp
-import androidx.ui.input.EditOperation
-import androidx.ui.input.EditProcessor
-import androidx.ui.input.EditorState
-import androidx.ui.input.FinishComposingTextEditOp
-import androidx.ui.input.ImeAction
-import androidx.ui.input.KeyboardType
-import androidx.ui.input.SetSelectionEditOp
-import androidx.ui.input.TextInputService
-import androidx.ui.painting.Canvas
-import androidx.ui.text.AnnotatedString
-import androidx.ui.text.TextPainter
-import androidx.ui.text.TextRange
-import androidx.ui.text.TextStyle
-import com.nhaarman.mockitokotlin2.any
-import com.nhaarman.mockitokotlin2.argumentCaptor
-import com.nhaarman.mockitokotlin2.eq
-import com.nhaarman.mockitokotlin2.inOrder
-import com.nhaarman.mockitokotlin2.mock
-import com.nhaarman.mockitokotlin2.never
-import com.nhaarman.mockitokotlin2.times
-import com.nhaarman.mockitokotlin2.verify
-import com.nhaarman.mockitokotlin2.whenever
-import org.junit.Assert.assertEquals
-import org.junit.Assert.assertTrue
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@RunWith(JUnit4::class)
-class InputFieldDelegateTest {
-
- private lateinit var canvas: Canvas
- private lateinit var painter: TextPainter
- private lateinit var processor: EditProcessor
- private lateinit var onValueChange: (EditorState) -> Unit
- private lateinit var onEditorActionPerformed: (Any) -> Unit
- private lateinit var textInputService: TextInputService
- private lateinit var layoutCoordinates: LayoutCoordinates
-
- @Before
- fun setup() {
- painter = mock()
- canvas = mock()
- processor = mock()
- onValueChange = mock()
- onEditorActionPerformed = mock()
- textInputService = mock()
- layoutCoordinates = mock()
- }
-
- @Test
- fun draw_selection_test() {
- val selection = TextRange(0, 1)
- val selectionColor = Color.Blue
-
- InputFieldDelegate.draw(
- canvas = canvas,
- textPainter = painter,
- value = EditorState(text = "Hello, World", selection = selection),
- editorStyle = EditorStyle(selectionColor = selectionColor),
- hasFocus = true)
-
- verify(painter, times(1)).paintBackground(
- eq(selection.start), eq(selection.end), eq(selectionColor), eq(canvas), any())
- verify(painter, times(1)).paint(eq(canvas), any())
-
- verify(painter, never()).paintCursor(any(), any())
- }
-
- @Test
- fun draw_cursor_test() {
- val cursor = TextRange(1, 1)
-
- InputFieldDelegate.draw(
- canvas = canvas,
- textPainter = painter,
- value = EditorState(text = "Hello, World", selection = cursor),
- editorStyle = EditorStyle(),
- hasFocus = true)
-
- verify(painter, times(1)).paintCursor(eq(cursor.start), eq(canvas))
- verify(painter, times(1)).paint(eq(canvas), any())
- verify(painter, never()).paintBackground(any(), any(), any(), any(), any())
- }
-
- @Test
- fun dont_draw_cursor_test() {
- val cursor = TextRange(1, 1)
-
- InputFieldDelegate.draw(
- canvas = canvas,
- textPainter = painter,
- value = EditorState(text = "Hello, World", selection = cursor),
- editorStyle = EditorStyle(),
- hasFocus = false)
-
- verify(painter, never()).paintCursor(any(), any())
- verify(painter, times(1)).paint(eq(canvas), any())
- verify(painter, never()).paintBackground(any(), any(), any(), any(), any())
- }
-
- @Test
- fun draw_composition_test() {
- val composition = TextRange(0, 1)
- val compositionColor = Color.Red
-
- val cursor = TextRange(1, 1)
-
- InputFieldDelegate.draw(
- canvas = canvas,
- textPainter = painter,
- value = EditorState(text = "Hello, World", selection = cursor,
- composition = composition),
- editorStyle = EditorStyle(compositionColor = compositionColor),
- hasFocus = true)
-
- verify(painter, times(1)).paintBackground(
- eq(composition.start), eq(composition.end), eq(compositionColor), eq(canvas), any())
- verify(painter, times(1)).paint(eq(canvas), any())
- verify(painter, times(1)).paintCursor(eq(cursor.start), any())
- }
-
- @Test
- fun test_on_edit_command() {
- val ops = listOf(CommitTextEditOp("Hello, World", 1))
- val dummyEditorState = EditorState(text = "Hello, World", selection = TextRange(1, 1))
-
- whenever(processor.onEditCommands(ops)).thenReturn(dummyEditorState)
-
- InputFieldDelegate.onEditCommand(ops, processor, onValueChange)
-
- verify(onValueChange, times(1)).invoke(eq(dummyEditorState))
- }
-
- @Test
- fun test_on_release() {
- val position = PxPosition(100.px, 200.px)
- val offset = 10
- val dummyEditorState = EditorState(text = "Hello, World", selection = TextRange(1, 1))
-
- whenever(painter.getPositionForOffset(position.toOffset())).thenReturn(offset)
-
- val captor = argumentCaptor<List<EditOperation>>()
-
- whenever(processor.onEditCommands(captor.capture())).thenReturn(dummyEditorState)
-
- InputFieldDelegate.onRelease(position, painter, processor, onValueChange)
-
- assertEquals(1, captor.allValues.size)
- assertEquals(1, captor.firstValue.size)
- assertTrue(captor.firstValue[0] is SetSelectionEditOp)
- verify(onValueChange, times(1)).invoke(eq(dummyEditorState))
- }
-
- @Test
- fun test_draw_order() {
- val canvas: Canvas = mock()
-
- InputFieldDelegate.draw(
- canvas = canvas,
- textPainter = painter,
- value = EditorState(
- text = "Hello, World", selection = TextRange(1, 1),
- composition = TextRange(1, 3)
- ),
- editorStyle = EditorStyle(compositionColor = Color.Red),
- hasFocus = true
- )
-
- inOrder(painter) {
- verify(painter).paintBackground(eq(1), eq(3), eq(Color.Red), eq(canvas), any())
- verify(painter).paintCursor(eq(1), eq(canvas))
- }
- }
-
- @Test
- fun show_soft_input() {
- InputFieldDelegate.onPress(textInputService)
- verify(textInputService).showSoftwareKeyboard()
- }
-
- @Test
- fun on_focus() {
- val dummyEditorState = EditorState(text = "Hello, World", selection = TextRange(1, 1))
- InputFieldDelegate.onFocus(textInputService, dummyEditorState, processor,
- KeyboardType.Text, ImeAction.Unspecified, onValueChange, onEditorActionPerformed)
- verify(textInputService).startInput(
- eq(dummyEditorState),
- eq(KeyboardType.Text),
- eq(ImeAction.Unspecified),
- any(),
- eq(onEditorActionPerformed)
- )
- }
-
- @Test
- fun on_blur() {
- val captor = argumentCaptor<List<EditOperation>>()
-
- whenever(processor.onEditCommands(captor.capture())).thenReturn(EditorState())
-
- InputFieldDelegate.onBlur(textInputService, processor, onValueChange)
-
- assertEquals(1, captor.allValues.size)
- assertEquals(1, captor.firstValue.size)
- assertTrue(captor.firstValue[0] is FinishComposingTextEditOp)
- verify(textInputService).stopInput()
- }
-
- @Test
- fun notify_focused_rect() {
- val dummyRect = Rect(0f, 1f, 2f, 3f)
- whenever(painter.getBoundingBox(any())).thenReturn(dummyRect)
- val dummyPoint = PxPosition(5.px, 6.px)
- whenever(layoutCoordinates.localToRoot(any())).thenReturn(dummyPoint)
- val dummyEditorState = EditorState(text = "Hello, World", selection = TextRange(1, 1))
- InputFieldDelegate.notifyFocusedRect(
- dummyEditorState,
- painter,
- layoutCoordinates,
- textInputService,
- true /* hasFocus */)
- verify(textInputService).notifyFocusedRect(any())
- }
-
- @Test
- fun notify_focused_rect_without_focus() {
- val dummyEditorState = EditorState(text = "Hello, World", selection = TextRange(1, 1))
- InputFieldDelegate.notifyFocusedRect(
- dummyEditorState,
- painter,
- layoutCoordinates,
- textInputService,
- false /* hasFocus */)
- verify(textInputService, never()).notifyFocusedRect(any())
- }
-
- @Test
- fun notify_rect_tail() {
- val dummyRect = Rect(0f, 1f, 2f, 3f)
- whenever(painter.getBoundingBox(any())).thenReturn(dummyRect)
- val dummyPoint = PxPosition(5.px, 6.px)
- whenever(layoutCoordinates.localToRoot(any())).thenReturn(dummyPoint)
- val dummyEditorState = EditorState(text = "Hello, World", selection = TextRange(12, 12))
- InputFieldDelegate.notifyFocusedRect(
- dummyEditorState,
- painter,
- layoutCoordinates,
- textInputService,
- true /* hasFocus */)
- verify(textInputService).notifyFocusedRect(any())
- }
-
- @Test
- fun notify_rect_empty() {
- val dummyHeight = 64f
- whenever(painter.preferredLineHeight).thenReturn(dummyHeight)
- val dummyPoint = PxPosition(5.px, 6.px)
- whenever(layoutCoordinates.localToRoot(any())).thenReturn(dummyPoint)
- val dummyEditorState = EditorState(text = "", selection = TextRange(0, 0))
- InputFieldDelegate.notifyFocusedRect(
- dummyEditorState,
- painter,
- layoutCoordinates,
- textInputService,
- true /* hasFocus */)
- val captor = argumentCaptor<Rect>()
- verify(textInputService).notifyFocusedRect(captor.capture())
- assertEquals(dummyHeight, captor.firstValue.height)
- }
-
- @Test
- fun layout() {
- val constraints = Constraints(
- minWidth = 0.px.round(),
- maxWidth = 1024.px.round(),
- minHeight = 0.px.round(),
- maxHeight = 2048.px.round()
- )
-
- val dummyText = AnnotatedString(text = "Hello, World")
- whenever(painter.text).thenReturn(dummyText)
- whenever(painter.style).thenReturn(TextStyle())
- whenever(painter.density).thenReturn(Density(1.0f))
- whenever(painter.resourceLoader).thenReturn(mock())
- whenever(painter.height).thenReturn(512.0f)
-
- val res = InputFieldDelegate.layout(painter, constraints)
- assertEquals(1024.px.round(), res.first)
- assertEquals(512.px.round(), res.second)
-
- verify(painter, times(1)).layout(constraints)
- }
-}
diff --git a/ui/ui-framework/src/test/java/androidx/ui/core/PasswordVisualTransformationTest.kt b/ui/ui-framework/src/test/java/androidx/ui/core/PasswordVisualTransformationTest.kt
new file mode 100644
index 0000000..1284040
--- /dev/null
+++ b/ui/ui-framework/src/test/java/androidx/ui/core/PasswordVisualTransformationTest.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.ui.core
+
+import androidx.ui.text.AnnotatedString
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class PasswordVisualTransformationTest {
+ @Test
+ fun check_visual_output_is_masked_with_asterisk() {
+ val transformation = PasswordVisualTransformation(mask = '*')
+ val text = AnnotatedString("12345")
+ val (transformedText, map) = transformation.filter(text)
+
+ assertEquals("*****", transformedText.text)
+ for (i in 0..transformedText.text.length) {
+ assertEquals(i, map.originalToTransformed(i))
+ assertEquals(i, map.transformedToOriginal(i))
+ }
+ }
+
+ @Test
+ fun check_visual_output_is_masked_with_default() {
+ val filter = PasswordVisualTransformation()
+ val text = AnnotatedString("1234567890")
+ val (filtered, map) = filter.filter(text)
+
+ assertEquals("\u2022".repeat(10), filtered.text)
+ for (i in 0..filtered.text.length) {
+ assertEquals(i, map.originalToTransformed(i))
+ assertEquals(i, map.transformedToOriginal(i))
+ }
+ }
+}
\ No newline at end of file
diff --git a/ui/ui-framework/src/test/java/androidx/ui/core/TextFieldDelegateTest.kt b/ui/ui-framework/src/test/java/androidx/ui/core/TextFieldDelegateTest.kt
new file mode 100644
index 0000000..4657c23
--- /dev/null
+++ b/ui/ui-framework/src/test/java/androidx/ui/core/TextFieldDelegateTest.kt
@@ -0,0 +1,473 @@
+/*
+ * 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.ui.core
+
+import androidx.ui.engine.geometry.Rect
+import androidx.ui.graphics.Color
+import androidx.ui.input.CommitTextEditOp
+import androidx.ui.input.EditOperation
+import androidx.ui.input.EditProcessor
+import androidx.ui.input.EditorModel
+import androidx.ui.input.FinishComposingTextEditOp
+import androidx.ui.input.ImeAction
+import androidx.ui.input.KeyboardType
+import androidx.ui.input.SetSelectionEditOp
+import androidx.ui.input.TextInputService
+import androidx.ui.painting.Canvas
+import androidx.ui.text.AnnotatedString
+import androidx.ui.text.TextPainter
+import androidx.ui.text.TextRange
+import androidx.ui.text.TextStyle
+import com.nhaarman.mockitokotlin2.any
+import com.nhaarman.mockitokotlin2.argumentCaptor
+import com.nhaarman.mockitokotlin2.eq
+import com.nhaarman.mockitokotlin2.inOrder
+import com.nhaarman.mockitokotlin2.mock
+import com.nhaarman.mockitokotlin2.never
+import com.nhaarman.mockitokotlin2.times
+import com.nhaarman.mockitokotlin2.verify
+import com.nhaarman.mockitokotlin2.whenever
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class TextFieldDelegateTest {
+
+ private lateinit var canvas: Canvas
+ private lateinit var painter: TextPainter
+ private lateinit var processor: EditProcessor
+ private lateinit var onValueChange: (EditorModel) -> Unit
+ private lateinit var onEditorActionPerformed: (Any) -> Unit
+ private lateinit var textInputService: TextInputService
+ private lateinit var layoutCoordinates: LayoutCoordinates
+
+ val creditCardOffsetTranslator = object : OffsetMap {
+ override fun originalToTransformed(offset: Int): Int {
+ if (offset <= 3) return offset
+ if (offset <= 7) return offset + 1
+ if (offset <= 11) return offset + 2
+ if (offset <= 16) return offset + 3
+ return 19
+ }
+
+ override fun transformedToOriginal(offset: Int): Int {
+ if (offset <= 4) return offset
+ if (offset <= 9) return offset - 1
+ if (offset <= 14) return offset - 2
+ if (offset <= 19) return offset - 3
+ return 16
+ }
+ }
+
+ private val identityOffsetMap = object : OffsetMap {
+ override fun originalToTransformed(offset: Int): Int = offset
+ override fun transformedToOriginal(offset: Int): Int = offset
+ }
+
+ /**
+ * Test implementation of offset map which doubles the offset in transformed text.
+ */
+ private val skippingOffsetMap = object : OffsetMap {
+ override fun originalToTransformed(offset: Int): Int = offset * 2
+ override fun transformedToOriginal(offset: Int): Int = offset / 2
+ }
+
+ @Before
+ fun setup() {
+ painter = mock()
+ canvas = mock()
+ processor = mock()
+ onValueChange = mock()
+ onEditorActionPerformed = mock()
+ textInputService = mock()
+ layoutCoordinates = mock()
+ }
+
+ @Test
+ fun draw_selection_test() {
+ val selection = TextRange(0, 1)
+ val selectionColor = Color.Blue
+
+ TextFieldDelegate.draw(
+ canvas = canvas,
+ textPainter = painter,
+ value = EditorModel(text = "Hello, World", selection = selection),
+ editorStyle = EditorStyle(selectionColor = selectionColor),
+ hasFocus = true,
+ offsetMap = identityOffsetMap
+ )
+
+ verify(painter, times(1)).paintBackground(
+ eq(selection.start), eq(selection.end), eq(selectionColor), eq(canvas))
+ verify(painter, times(1)).paint(eq(canvas))
+
+ verify(painter, never()).paintCursor(any(), any())
+ }
+
+ @Test
+ fun draw_cursor_test() {
+ val cursor = TextRange(1, 1)
+
+ TextFieldDelegate.draw(
+ canvas = canvas,
+ textPainter = painter,
+ value = EditorModel(text = "Hello, World", selection = cursor),
+ editorStyle = EditorStyle(),
+ hasFocus = true,
+ offsetMap = identityOffsetMap
+ )
+
+ verify(painter, times(1)).paintCursor(eq(cursor.start), eq(canvas))
+ verify(painter, times(1)).paint(eq(canvas))
+ verify(painter, never()).paintBackground(any(), any(), any(), any())
+ }
+
+ @Test
+ fun dont_draw_cursor_test() {
+ val cursor = TextRange(1, 1)
+
+ TextFieldDelegate.draw(
+ canvas = canvas,
+ textPainter = painter,
+ value = EditorModel(text = "Hello, World", selection = cursor),
+ editorStyle = EditorStyle(),
+ hasFocus = false,
+ offsetMap = identityOffsetMap
+ )
+
+ verify(painter, never()).paintCursor(any(), any())
+ verify(painter, times(1)).paint(eq(canvas))
+ verify(painter, never()).paintBackground(any(), any(), any(), any())
+ }
+
+ @Test
+ fun draw_composition_test() {
+ val composition = TextRange(0, 1)
+ val compositionColor = Color.Red
+
+ val cursor = TextRange(1, 1)
+
+ TextFieldDelegate.draw(
+ canvas = canvas,
+ textPainter = painter,
+ value = EditorModel(text = "Hello, World", selection = cursor,
+ composition = composition),
+ editorStyle = EditorStyle(compositionColor = compositionColor),
+ hasFocus = true,
+ offsetMap = identityOffsetMap
+ )
+
+ verify(painter, times(1)).paintBackground(
+ eq(composition.start), eq(composition.end), eq(compositionColor), eq(canvas))
+ verify(painter, times(1)).paint(eq(canvas))
+ verify(painter, times(1)).paintCursor(eq(cursor.start), any())
+ }
+
+ @Test
+ fun test_on_edit_command() {
+ val ops = listOf(CommitTextEditOp("Hello, World", 1))
+ val dummyEditorState = EditorModel(text = "Hello, World", selection = TextRange(1, 1))
+
+ whenever(processor.onEditCommands(ops)).thenReturn(dummyEditorState)
+
+ TextFieldDelegate.onEditCommand(ops, processor, onValueChange)
+
+ verify(onValueChange, times(1)).invoke(eq(dummyEditorState))
+ }
+
+ @Test
+ fun test_on_release() {
+ val position = PxPosition(100.px, 200.px)
+ val offset = 10
+ val dummyEditorState = EditorModel(text = "Hello, World", selection = TextRange(1, 1))
+
+ whenever(painter.getOffsetForPosition(position)).thenReturn(offset)
+
+ val captor = argumentCaptor<List<EditOperation>>()
+
+ whenever(processor.onEditCommands(captor.capture())).thenReturn(dummyEditorState)
+
+ TextFieldDelegate.onRelease(
+ position,
+ painter,
+ processor,
+ identityOffsetMap,
+ onValueChange,
+ textInputService,
+ true)
+
+ assertEquals(1, captor.allValues.size)
+ assertEquals(1, captor.firstValue.size)
+ assertTrue(captor.firstValue[0] is SetSelectionEditOp)
+ verify(onValueChange, times(1)).invoke(eq(dummyEditorState))
+ verify(textInputService).showSoftwareKeyboard()
+ }
+
+ @Test
+ fun test_on_release_do_not_place_cursor_if_focus_is_out() {
+ val position = PxPosition(100.px, 200.px)
+ val offset = 10
+
+ whenever(painter.getOffsetForPosition(position)).thenReturn(offset)
+ TextFieldDelegate.onRelease(
+ position,
+ painter,
+ processor,
+ identityOffsetMap,
+ onValueChange,
+ textInputService,
+ false)
+
+ verify(onValueChange, never()).invoke(any())
+ verify(textInputService).showSoftwareKeyboard()
+ }
+
+ @Test
+ fun test_draw_order() {
+ val canvas: Canvas = mock()
+
+ TextFieldDelegate.draw(
+ canvas = canvas,
+ textPainter = painter,
+ value = EditorModel(
+ text = "Hello, World", selection = TextRange(1, 1),
+ composition = TextRange(1, 3)
+ ),
+ editorStyle = EditorStyle(compositionColor = Color.Red),
+ hasFocus = true,
+ offsetMap = identityOffsetMap
+ )
+
+ inOrder(painter) {
+ verify(painter).paintBackground(eq(1), eq(3), eq(Color.Red), eq(canvas))
+ verify(painter).paintCursor(eq(1), eq(canvas))
+ }
+ }
+
+ @Test
+ fun on_focus() {
+ val dummyEditorState = EditorModel(text = "Hello, World", selection = TextRange(1, 1))
+ TextFieldDelegate.onFocus(textInputService, dummyEditorState, processor,
+ KeyboardType.Text, ImeAction.Unspecified, onValueChange, onEditorActionPerformed)
+ verify(textInputService).startInput(
+ eq(dummyEditorState),
+ eq(KeyboardType.Text),
+ eq(ImeAction.Unspecified),
+ any(),
+ eq(onEditorActionPerformed)
+ )
+ }
+
+ @Test
+ fun on_blur() {
+ val captor = argumentCaptor<List<EditOperation>>()
+
+ whenever(processor.onEditCommands(captor.capture())).thenReturn(EditorModel())
+
+ TextFieldDelegate.onBlur(textInputService, processor, onValueChange)
+
+ assertEquals(1, captor.allValues.size)
+ assertEquals(1, captor.firstValue.size)
+ assertTrue(captor.firstValue[0] is FinishComposingTextEditOp)
+ verify(textInputService).stopInput()
+ }
+
+ @Test
+ fun notify_focused_rect() {
+ val dummyRect = Rect(0f, 1f, 2f, 3f)
+ whenever(painter.getBoundingBox(any())).thenReturn(dummyRect)
+ val dummyPoint = PxPosition(5.px, 6.px)
+ whenever(layoutCoordinates.localToRoot(any())).thenReturn(dummyPoint)
+ val dummyEditorState = EditorModel(text = "Hello, World", selection = TextRange(1, 1))
+ TextFieldDelegate.notifyFocusedRect(
+ dummyEditorState,
+ painter,
+ layoutCoordinates,
+ textInputService,
+ true /* hasFocus */,
+ identityOffsetMap
+ )
+ verify(textInputService).notifyFocusedRect(any())
+ }
+
+ @Test
+ fun notify_focused_rect_without_focus() {
+ val dummyEditorState = EditorModel(text = "Hello, World", selection = TextRange(1, 1))
+ TextFieldDelegate.notifyFocusedRect(
+ dummyEditorState,
+ painter,
+ layoutCoordinates,
+ textInputService,
+ false /* hasFocus */,
+ identityOffsetMap
+ )
+ verify(textInputService, never()).notifyFocusedRect(any())
+ }
+
+ @Test
+ fun notify_rect_tail() {
+ val dummyRect = Rect(0f, 1f, 2f, 3f)
+ whenever(painter.getBoundingBox(any())).thenReturn(dummyRect)
+ val dummyPoint = PxPosition(5.px, 6.px)
+ whenever(layoutCoordinates.localToRoot(any())).thenReturn(dummyPoint)
+ val dummyEditorState = EditorModel(text = "Hello, World", selection = TextRange(12, 12))
+ TextFieldDelegate.notifyFocusedRect(
+ dummyEditorState,
+ painter,
+ layoutCoordinates,
+ textInputService,
+ true /* hasFocus */,
+ identityOffsetMap
+ )
+ verify(textInputService).notifyFocusedRect(any())
+ }
+
+ @Test
+ fun notify_rect_empty() {
+ val dummyHeight = 64f
+ whenever(painter.preferredLineHeight).thenReturn(dummyHeight)
+ val dummyPoint = PxPosition(5.px, 6.px)
+ whenever(layoutCoordinates.localToRoot(any())).thenReturn(dummyPoint)
+ val dummyEditorState = EditorModel(text = "", selection = TextRange(0, 0))
+ TextFieldDelegate.notifyFocusedRect(
+ dummyEditorState,
+ painter,
+ layoutCoordinates,
+ textInputService,
+ true, /* hasFocus */
+ identityOffsetMap)
+ val captor = argumentCaptor<Rect>()
+ verify(textInputService).notifyFocusedRect(captor.capture())
+ assertEquals(dummyHeight, captor.firstValue.height)
+ }
+
+ @Test
+ fun layout() {
+ val constraints = Constraints(
+ minWidth = 0.px.round(),
+ maxWidth = 1024.px.round(),
+ minHeight = 0.px.round(),
+ maxHeight = 2048.px.round()
+ )
+
+ val dummyText = AnnotatedString(text = "Hello, World")
+ whenever(painter.text).thenReturn(dummyText)
+ whenever(painter.style).thenReturn(TextStyle())
+ whenever(painter.density).thenReturn(Density(1.0f))
+ whenever(painter.resourceLoader).thenReturn(mock())
+ whenever(painter.height).thenReturn(512.0f)
+
+ val res = TextFieldDelegate.layout(painter, constraints)
+ assertEquals(1024.px.round(), res.first)
+ assertEquals(512.px.round(), res.second)
+
+ verify(painter, times(1)).layout(constraints)
+ }
+
+ @Test
+ fun check_draw_uses_offset_map() {
+ val selection = TextRange(1, 3)
+ val selectionColor = Color.Blue
+
+ TextFieldDelegate.draw(
+ canvas = canvas,
+ textPainter = painter,
+ value = EditorModel(text = "Hello, World", selection = selection),
+ editorStyle = EditorStyle(selectionColor = selectionColor),
+ hasFocus = true,
+ offsetMap = skippingOffsetMap
+ )
+
+ val selectionStartInTransformedText = selection.start * 2
+ val selectionEmdInTransformedText = selection.end * 2
+
+ verify(painter, times(1)).paintBackground(
+ eq(selectionStartInTransformedText),
+ eq(selectionEmdInTransformedText),
+ eq(selectionColor),
+ eq(canvas))
+ }
+
+ @Test
+ fun check_notify_rect_uses_offset_map() {
+ val dummyRect = Rect(0f, 1f, 2f, 3f)
+ val dummyPoint = PxPosition(5.px, 6.px)
+ val dummyEditorState = EditorModel(text = "Hello, World", selection = TextRange(1, 3))
+ whenever(painter.getBoundingBox(any())).thenReturn(dummyRect)
+ whenever(layoutCoordinates.localToRoot(any())).thenReturn(dummyPoint)
+
+ TextFieldDelegate.notifyFocusedRect(
+ dummyEditorState,
+ painter,
+ layoutCoordinates,
+ textInputService,
+ true /* hasFocus */,
+ skippingOffsetMap
+ )
+ verify(painter).getBoundingBox(6)
+ verify(textInputService).notifyFocusedRect(any())
+ }
+
+ @Test
+ fun check_on_release_uses_offset_map() {
+ val position = PxPosition(100.px, 200.px)
+ val offset = 10
+ val dummyEditorState = EditorModel(text = "Hello, World", selection = TextRange(1, 1))
+
+ whenever(painter.getOffsetForPosition(position)).thenReturn(offset)
+
+ val captor = argumentCaptor<List<EditOperation>>()
+
+ whenever(processor.onEditCommands(captor.capture())).thenReturn(dummyEditorState)
+
+ TextFieldDelegate.onRelease(
+ position,
+ painter,
+ processor,
+ skippingOffsetMap,
+ onValueChange,
+ textInputService,
+ true)
+
+ val cursorOffsetInTransformedText = offset / 2
+ assertEquals(1, captor.allValues.size)
+ assertEquals(1, captor.firstValue.size)
+ assertTrue(captor.firstValue[0] is SetSelectionEditOp)
+ val setSelectionEditOp = captor.firstValue[0] as SetSelectionEditOp
+ assertEquals(cursorOffsetInTransformedText, setSelectionEditOp.start)
+ assertEquals(cursorOffsetInTransformedText, setSelectionEditOp.end)
+ verify(onValueChange, times(1)).invoke(eq(dummyEditorState))
+ }
+
+ @Test
+ fun use_identity_mapping_if_visual_transformation_is_null() {
+ val (visualText, offsetMap) = TextFieldDelegate.applyVisualFilter(
+ EditorModel(text = "Hello, World"),
+ null)
+
+ assertEquals("Hello, World", visualText.text)
+ for (i in 0..visualText.text.length) {
+ // Identity mapping returns if no visual filter is provided.
+ assertEquals(i, offsetMap.originalToTransformed(i))
+ assertEquals(i, offsetMap.transformedToOriginal(i))
+ }
+ }
+}
diff --git a/ui/ui-framework/src/test/java/androidx/ui/core/gesture/DoubleTapGestureDetectorTest.kt b/ui/ui-framework/src/test/java/androidx/ui/core/gesture/DoubleTapGestureDetectorTest.kt
new file mode 100644
index 0000000..1890b9f
--- /dev/null
+++ b/ui/ui-framework/src/test/java/androidx/ui/core/gesture/DoubleTapGestureDetectorTest.kt
@@ -0,0 +1,522 @@
+/*
+ * 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.ui.core.gesture
+
+import androidx.ui.core.PxPosition
+import androidx.ui.core.consumeDownChange
+import androidx.ui.core.milliseconds
+import androidx.ui.core.millisecondsToTimestamp
+import androidx.ui.core.px
+import androidx.ui.testutils.consume
+import androidx.ui.testutils.down
+import androidx.ui.testutils.invokeOverAllPasses
+import androidx.ui.testutils.moveTo
+import androidx.ui.testutils.up
+import com.google.common.truth.Truth.assertThat
+import com.nhaarman.mockitokotlin2.any
+import com.nhaarman.mockitokotlin2.mock
+import com.nhaarman.mockitokotlin2.never
+import com.nhaarman.mockitokotlin2.verify
+import kotlinx.coroutines.ObsoleteCoroutinesApi
+import kotlinx.coroutines.test.TestCoroutineContext
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import java.util.concurrent.TimeUnit
+
+// TODO(shepshapard): Add more tests for:
+// 1. More complex multi-pointer scenarios testing how consumption affects firing events
+// 2. More complex multi-pointer scenarios testing how pointers effect consumption
+
+@ObsoleteCoroutinesApi
+@RunWith(JUnit4::class)
+class DoubleTapGestureDetectorTest {
+
+ private val DoubleTapTimeoutMillis = 100.milliseconds
+ private val testContext = TestCoroutineContext()
+ private val onDoubleTap: (PxPosition) -> Unit = mock()
+ private lateinit var mRecognizer: DoubleTapGestureRecognizer
+
+ @Before
+ fun setup() {
+ mRecognizer = DoubleTapGestureRecognizer(onDoubleTap, testContext)
+ mRecognizer.doubleTapTimeout = DoubleTapTimeoutMillis
+ }
+
+ // Tests that verify conditions under which onDoubleTap will not be called.
+
+ @Test
+ fun pointerInputHandler_down_onDoubleTapNotCalled() {
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(down()))
+ verify(onDoubleTap, never()).invoke(any())
+ }
+
+ @Test
+ fun pointerInputHandler_downUp_onDoubleTapNotCalled() {
+ val down = down(timestamp = 0L.millisecondsToTimestamp())
+ val up = down.up(timestamp = 1L.millisecondsToTimestamp())
+
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(down)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(up)
+
+ verify(onDoubleTap, never()).invoke(any())
+ }
+
+ @Test
+ fun pointerInputHandler_downUpDownWithinTimeout_onLongPressNotCalled() {
+ val down1 = down(timestamp = 0L.millisecondsToTimestamp())
+ val up = down1.up(timestamp = 1L.millisecondsToTimestamp())
+ val down2 = down(timestamp = 100L.millisecondsToTimestamp())
+
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(down1)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(up)
+ testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(down2)
+
+ verify(onDoubleTap, never()).invoke(any())
+ }
+
+ @Test
+ fun pointerInputHandler_downUpDownOutsideTimeout_onLongPressNotCalled() {
+ val down1 = down(timestamp = 0L.millisecondsToTimestamp())
+ val up = down1.up(timestamp = 1L.millisecondsToTimestamp())
+ val down2 = down(timestamp = 101L.millisecondsToTimestamp())
+
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(down1)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(up)
+ testContext.advanceTimeBy(100, TimeUnit.MILLISECONDS)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(down2)
+
+ verify(onDoubleTap, never()).invoke(any())
+ }
+
+ @Test
+ fun pointerInputHandler_downUpDownOutsideTimeoutUp_onLongPressNotCalled() {
+ val down1 = down(timestamp = 0L.millisecondsToTimestamp())
+ val up1 = down1.up(timestamp = 1L.millisecondsToTimestamp())
+ val down2 = down(timestamp = 101L.millisecondsToTimestamp())
+ val up2 = down2.up(timestamp = 102L.millisecondsToTimestamp())
+
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(down1)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(up1)
+ testContext.advanceTimeBy(100, TimeUnit.MILLISECONDS)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(down2)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(up2)
+
+ verify(onDoubleTap, never()).invoke(any())
+ }
+
+ @Test
+ fun pointerInputHandler_downMoveConsumedUpDownInsideTimeoutUp_onLongPressNotCalled() {
+ val down1 = down(timestamp = 0L.millisecondsToTimestamp())
+ val moveConsumed = down1.moveTo(1L.millisecondsToTimestamp(), x = 1f).consume(dx = 1f)
+ val up1 = moveConsumed.up(timestamp = 2L.millisecondsToTimestamp())
+ val down2 = down(timestamp = 101L.millisecondsToTimestamp())
+ val up2 = down2.up(timestamp = 102L.millisecondsToTimestamp())
+
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(down1)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(moveConsumed)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(up1)
+ testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(down2)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(up2)
+
+ verify(onDoubleTap, never()).invoke(any())
+ }
+
+ @Test
+ fun pointerInputHandler_downUpDownInsideTimeoutMoveConsumedUp_onLongPressNotCalled() {
+ val down1 = down(timestamp = 0L.millisecondsToTimestamp())
+ val up1 = down1.up(timestamp = 1L.millisecondsToTimestamp())
+ val down2 = down(timestamp = 100L.millisecondsToTimestamp())
+ val moveConsumed = down2.moveTo(101L.millisecondsToTimestamp(), x = 1f).consume(dx = 1f)
+ val up2 = moveConsumed.up(timestamp = 102L.millisecondsToTimestamp())
+
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(down1)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(up1)
+ testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(down2)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(moveConsumed)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(up2)
+
+ verify(onDoubleTap, never()).invoke(any())
+ }
+
+ @Test
+ fun pointerInputHandler_2Down1MoveConsumedUpDownInsideTimeoutUp_onLongPressNotCalled() {
+ val down1A = down(0, timestamp = 0L.millisecondsToTimestamp())
+ val down1B = down(1, timestamp = 0L.millisecondsToTimestamp())
+ val moveConsumed1A = down1A.moveTo(1L.millisecondsToTimestamp(), x = 1f).consume(dx = 1f)
+ val move1B = down1B.moveTo(1L.millisecondsToTimestamp())
+ val up1A = moveConsumed1A.up(timestamp = 2L.millisecondsToTimestamp())
+ val up1B = move1B.up(timestamp = 2L.millisecondsToTimestamp())
+ val down2 = down(timestamp = 101L.millisecondsToTimestamp())
+ val up2 = down2.up(timestamp = 102L.millisecondsToTimestamp())
+
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(down1A, down1B)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(moveConsumed1A, move1B)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(up1A, up1B)
+ testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(down2)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(up2)
+
+ verify(onDoubleTap, never()).invoke(any())
+ }
+
+ @Test
+ fun pointerInputHandler_downUp2DownInsideTimeout1MoveConsumedUp_onLongPressNotCalled() {
+ val down1 = down(timestamp = 0L.millisecondsToTimestamp())
+ val up2 = down1.up(timestamp = 1L.millisecondsToTimestamp())
+ val down2A = down(0, timestamp = 100L.millisecondsToTimestamp())
+ val down2B = down(1, timestamp = 100L.millisecondsToTimestamp())
+ val moveConsumed2A = down2A.moveTo(101L.millisecondsToTimestamp(), x = 1f).consume(dx = 1f)
+ val move2B = down2B.moveTo(101L.millisecondsToTimestamp())
+ val up2A = moveConsumed2A.up(timestamp = 102L.millisecondsToTimestamp())
+ val up2B = move2B.up(timestamp = 102L.millisecondsToTimestamp())
+
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(down1)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(up2)
+ testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(down2A, down2B)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(moveConsumed2A, move2B)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(up2A, up2B)
+
+ verify(onDoubleTap, never()).invoke(any())
+ }
+
+ @Test
+ fun pointerInputHandler_downConsumedUpDownWithinTimeoutUp_onLongPressNotCalled() {
+ val down1 = down(timestamp = 0L.millisecondsToTimestamp()).consumeDownChange()
+ val up1 = down1.up(timestamp = 1L.millisecondsToTimestamp())
+ val down2 = down(0, timestamp = 100L.millisecondsToTimestamp())
+ val up2 = down2.up(timestamp = 102L.millisecondsToTimestamp())
+
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(down1)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(up1)
+ testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(down2)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(up2)
+
+ verify(onDoubleTap, never()).invoke(any())
+ }
+
+ @Test
+ fun pointerInputHandler_downUpConsumedDownWithinTimeoutUp_onLongPressNotCalled() {
+ val down1 = down(timestamp = 0L.millisecondsToTimestamp())
+ val up1 = down1.up(timestamp = 1L.millisecondsToTimestamp()).consumeDownChange()
+ val down2 = down(0, timestamp = 100L.millisecondsToTimestamp())
+ val up2 = down2.up(timestamp = 102L.millisecondsToTimestamp())
+
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(down1)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(up1)
+ testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(down2)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(up2)
+
+ verify(onDoubleTap, never()).invoke(any())
+ }
+
+ @Test
+ fun pointerInputHandler_downUpDownConsumedWithinTimeoutUp_onLongPressNotCalled() {
+ val down1 = down(timestamp = 0L.millisecondsToTimestamp())
+ val up1 = down1.up(timestamp = 1L.millisecondsToTimestamp())
+ val down2 = down(0, timestamp = 100L.millisecondsToTimestamp()).consumeDownChange()
+ val up2 = down2.up(timestamp = 102L.millisecondsToTimestamp())
+
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(down1)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(up1)
+ testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(down2)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(up2)
+
+ verify(onDoubleTap, never()).invoke(any())
+ }
+
+ @Test
+ fun pointerInputHandler_downUpDownWithinTimeoutUpConsumed_onLongPressNotCalled() {
+ val down1 = down(timestamp = 0L.millisecondsToTimestamp())
+ val up1 = down1.up(timestamp = 1L.millisecondsToTimestamp())
+ val down2 = down(0, timestamp = 100L.millisecondsToTimestamp())
+ val up2 = down2.up(timestamp = 102L.millisecondsToTimestamp()).consumeDownChange()
+
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(down1)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(up1)
+ testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(down2)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(up2)
+
+ verify(onDoubleTap, never()).invoke(any())
+ }
+
+ @Test
+ fun pointerInputHandler_2down1Up1DownWithinTimeout1Up_onLongPressNotCalled() {
+ val down1A = down(0, timestamp = 0L.millisecondsToTimestamp())
+ val down1B = down(1, timestamp = 0L.millisecondsToTimestamp())
+ val move1A1 = down1A.moveTo(2L.millisecondsToTimestamp())
+ val up2B = down1B.up(timestamp = 2L.millisecondsToTimestamp())
+ val move1A2 = move1A1.moveTo(101L.millisecondsToTimestamp())
+ val down2 = down(id = 1, timestamp = 101L.millisecondsToTimestamp())
+ val move1A3 = move1A2.moveTo(102L.millisecondsToTimestamp())
+ val up2 = down2.up(timestamp = 102L.millisecondsToTimestamp())
+
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(down1A, down1B)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(move1A1, up2B)
+ testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(move1A2, down2)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(move1A3, up2)
+
+ verify(onDoubleTap, never()).invoke(any())
+ }
+
+ @Test
+ fun pointerInputHandler_1down1Up2DownWithinTimeout1Up_onLongPressNotCalled() {
+ val down1 = down(id = 0, timestamp = 0L.millisecondsToTimestamp())
+ val up1 = down1.up(timestamp = 1L.millisecondsToTimestamp())
+ val down2A = down(0, timestamp = 100L.millisecondsToTimestamp())
+ val down2B = down(1, timestamp = 100L.millisecondsToTimestamp())
+ val move2A = down2A.moveTo(timestamp = 101L.millisecondsToTimestamp())
+ val up2B = down2B.up(timestamp = 101L.millisecondsToTimestamp())
+
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(down1)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(up1)
+ testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(down2A, down2B)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(move2A, up2B)
+
+ verify(onDoubleTap, never()).invoke(any())
+ }
+
+ // Tests that verify conditions under which onDoubleTap will be called.
+
+ @Test
+ fun pointerInputHandler_downUpDownInsideTimeoutUp_onLongPressCalled() {
+ val down1 = down(timestamp = (0L).millisecondsToTimestamp())
+ val up1 = down1.up(timestamp = 1L.millisecondsToTimestamp())
+ val down2 = down(timestamp = 100L.millisecondsToTimestamp())
+ val up2 = down2.up(timestamp = 101L.millisecondsToTimestamp())
+
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(down1)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(up1)
+ testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(down2)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(up2)
+
+ verify(onDoubleTap).invoke(any())
+ }
+
+ @Test
+ fun pointerInputHandler_downMoveUpDownInsideTimeoutUp_onLongPressCalled() {
+ val down1 = down(timestamp = (0L).millisecondsToTimestamp())
+ val move = down1.moveTo(1L.millisecondsToTimestamp(), x = 1f)
+ val up1 = move.up(timestamp = 2L.millisecondsToTimestamp())
+ val down2 = down(timestamp = 101L.millisecondsToTimestamp())
+ val up2 = down2.up(timestamp = 102L.millisecondsToTimestamp())
+
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(down1)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(move)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(up1)
+ testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(down2)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(up2)
+
+ verify(onDoubleTap).invoke(any())
+ }
+
+ @Test
+ fun pointerInputHandler_downUpDownInsideTimeoutMoveUp_onLongPressCalled() {
+ val down1 = down(timestamp = (0L).millisecondsToTimestamp())
+ val up1 = down1.up(timestamp = 1L.millisecondsToTimestamp())
+ val down2 = down(timestamp = 10L.millisecondsToTimestamp())
+ val move = down2.moveTo(101L.millisecondsToTimestamp(), x = 1f)
+ val up2 = move.up(timestamp = 102L.millisecondsToTimestamp())
+
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(down1)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(up1)
+ testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(down2)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(move)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(up2)
+
+ verify(onDoubleTap).invoke(any())
+ }
+
+ @Test
+ fun pointerInputHandler_2Down1MoveUpDownInsideTimeoutUp_onLongPressCalled() {
+ val down1A = down(0, timestamp = 0L.millisecondsToTimestamp())
+ val down1B = down(1, timestamp = 0L.millisecondsToTimestamp())
+ val move1A = down1A.moveTo(1L.millisecondsToTimestamp(), x = 1f)
+ val move1B = down1B.moveTo(1L.millisecondsToTimestamp())
+ val up1A = move1A.up(timestamp = 2L.millisecondsToTimestamp())
+ val up1B = move1B.up(timestamp = 2L.millisecondsToTimestamp())
+ val down2 = down(timestamp = 101L.millisecondsToTimestamp())
+ val up2 = down2.up(timestamp = 102L.millisecondsToTimestamp())
+
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(down1A, down1B)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(move1A, move1B)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(up1A, up1B)
+ testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(down2)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(up2)
+
+ verify(onDoubleTap).invoke(any())
+ }
+
+ @Test
+ fun pointerInputHandler_downUp2DownInsideTimeout1MoveUp_onLongPressCalled() {
+ val down1 = down(timestamp = 0L.millisecondsToTimestamp())
+ val up1 = down1.up(timestamp = 1L.millisecondsToTimestamp())
+ val down2A = down(0, timestamp = 100L.millisecondsToTimestamp())
+ val down2B = down(1, timestamp = 100L.millisecondsToTimestamp())
+ val move2A = down2A.moveTo(101L.millisecondsToTimestamp(), x = 1f)
+ val move2B = down2B.moveTo(101L.millisecondsToTimestamp())
+ val up2A = move2A.up(timestamp = 102L.millisecondsToTimestamp())
+ val up2B = move2B.up(timestamp = 102L.millisecondsToTimestamp())
+
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(down1)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(up1)
+ testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(down2A, down2B)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(move2A, move2B)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(up2A, up2B)
+
+ verify(onDoubleTap).invoke(any())
+ }
+
+ // Tests that verify correctness of PxPosition value passed to onDoubleTap
+
+ @Test
+ fun pointerInputHandler_downUpDownUpAllAtOrigin_onDoubleTapCalledWithOrigin() {
+ val down1 = down(timestamp = (0L).millisecondsToTimestamp())
+ val up1 = down1.up(timestamp = 1L.millisecondsToTimestamp())
+ val down2 = down(timestamp = 100L.millisecondsToTimestamp())
+ val up2 = down2.up(timestamp = 101L.millisecondsToTimestamp())
+
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(down1)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(up1)
+ testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(down2)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(up2)
+
+ verify(onDoubleTap).invoke(PxPosition.Origin)
+ }
+
+ @Test
+ fun pointerInputHandler_downUpDownMoveUp_onDoubleTapCalledWithFinalMovePosition() {
+ val down1 = down(timestamp = (0L).millisecondsToTimestamp())
+ val up1 = down1.up(timestamp = 1L.millisecondsToTimestamp())
+ val down2 = down(timestamp = 100L.millisecondsToTimestamp())
+ val move2 = down2.moveTo(101L.millisecondsToTimestamp(), 3f, 5f)
+ val up2 = move2.up(timestamp = 102L.millisecondsToTimestamp())
+
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(down1)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(up1)
+ testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(down2)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(move2)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(up2)
+
+ verify(onDoubleTap).invoke(PxPosition(3.px, 5.px))
+ }
+
+ @Test
+ fun pointerInputHandler_downUp2Down2Move1UpThen1Up_onDoubleTapCalledWithFinalFingerPosition() {
+ val down1 = down(timestamp = (0L).millisecondsToTimestamp())
+ val up1 = down1.up(timestamp = 1L.millisecondsToTimestamp())
+ val down2A = down(id = 0, timestamp = 100L.millisecondsToTimestamp())
+ val down2B = down(id = 1, timestamp = 100L.millisecondsToTimestamp())
+ val move2A = down2A.moveTo(101L.millisecondsToTimestamp(), 3f, 5f)
+ val move2B1 = down2B.moveTo(101L.millisecondsToTimestamp(), -7f, -11f)
+ val up2A = move2A.up(timestamp = 102L.millisecondsToTimestamp())
+ val move2B2 = move2B1.moveTo(timestamp = 102L.millisecondsToTimestamp(), x = -7f, y = -11f)
+ val up2B = move2B2.up(timestamp = 103L.millisecondsToTimestamp())
+
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(down1)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(up1)
+ testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(down2A, down2B)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(move2A, move2B1)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(up2A, move2B2)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(up2B)
+
+ verify(onDoubleTap).invoke(PxPosition((-7).px, (-11).px))
+ }
+
+ // Tests that verify that consumption behavior
+
+ @Test
+ fun pointerInputHandler_down_downNotConsumed() {
+ val down = down()
+ val result = mRecognizer.pointerInputHandler.invokeOverAllPasses(down)
+ assertThat(result[0].consumed.downChange).isFalse()
+ }
+
+ @Test
+ fun pointerInputHandler_downUp_upNotConsumed() {
+ val down = down()
+ val up = down.up(1L.millisecondsToTimestamp())
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(down)
+ val result = mRecognizer.pointerInputHandler.invokeOverAllPasses(up)
+ assertThat(result[0].consumed.downChange).isFalse()
+ }
+
+ @Test
+ fun pointerInputHandler_downUpDownInsideTimeout_lastDownNotConsumed() {
+ val down = down()
+ val up = down.up(1L.millisecondsToTimestamp())
+ val down2 = down(timestamp = 100L.millisecondsToTimestamp())
+
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(down)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(up)
+ testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
+ val result = mRecognizer.pointerInputHandler.invokeOverAllPasses(down2)
+
+ assertThat(result[0].consumed.downChange).isFalse()
+ }
+
+ @Test
+ fun pointerInputHandler_downUpDownOutsideTimeoutUp_lastUpNotConsumed() {
+ val down = down()
+ val up = down.up(1L.millisecondsToTimestamp())
+ val down2 = down(timestamp = 101L.millisecondsToTimestamp())
+ val up2 = down2.up(timestamp = 102L.millisecondsToTimestamp())
+
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(down)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(up)
+ testContext.advanceTimeBy(100, TimeUnit.MILLISECONDS)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(down2)
+ val result = mRecognizer.pointerInputHandler.invokeOverAllPasses(up2)
+
+ assertThat(result[0].consumed.downChange).isFalse()
+ }
+
+ @Test
+ fun pointerInputHandler_downUpDownInsideTimeoutUp_lastUpConsumed() {
+ val down = down()
+ val up = down.up(1L.millisecondsToTimestamp())
+ val down2 = down(timestamp = 100L.millisecondsToTimestamp())
+ val up2 = down2.up(timestamp = 101L.millisecondsToTimestamp())
+
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(down)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(up)
+ testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(down2)
+ val result = mRecognizer.pointerInputHandler.invokeOverAllPasses(up2)
+
+ assertThat(result[0].consumed.downChange).isTrue()
+ }
+}
\ No newline at end of file
diff --git a/ui/ui-framework/src/test/java/androidx/ui/core/gesture/LongPressGestureDetectorTest.kt b/ui/ui-framework/src/test/java/androidx/ui/core/gesture/LongPressGestureDetectorTest.kt
new file mode 100644
index 0000000..45c5d9f
--- /dev/null
+++ b/ui/ui-framework/src/test/java/androidx/ui/core/gesture/LongPressGestureDetectorTest.kt
@@ -0,0 +1,403 @@
+/*
+ * 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.ui.core.gesture
+
+import androidx.ui.core.PointerEventPass
+import androidx.ui.core.PxPosition
+import androidx.ui.core.consumeDownChange
+import androidx.ui.core.milliseconds
+import androidx.ui.core.millisecondsToTimestamp
+import androidx.ui.core.px
+import androidx.ui.testutils.consume
+import androidx.ui.testutils.down
+import androidx.ui.testutils.invokeOverAllPasses
+import androidx.ui.testutils.moveBy
+import androidx.ui.testutils.moveTo
+import androidx.ui.testutils.up
+import com.google.common.truth.Truth.assertThat
+import com.nhaarman.mockitokotlin2.any
+import com.nhaarman.mockitokotlin2.mock
+import com.nhaarman.mockitokotlin2.never
+import com.nhaarman.mockitokotlin2.verify
+import kotlinx.coroutines.ObsoleteCoroutinesApi
+import kotlinx.coroutines.test.TestCoroutineContext
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import java.util.concurrent.TimeUnit
+
+@ObsoleteCoroutinesApi
+@RunWith(JUnit4::class)
+class LongPressGestureDetectorTest {
+
+ private val LongPressTimeoutMillis = 100.milliseconds
+ private val testContext = TestCoroutineContext()
+ private val listener: (PxPosition) -> Unit = mock()
+ private lateinit var mRecognizer: LongPressGestureRecognizer
+
+ @Before
+ fun setup() {
+ mRecognizer = LongPressGestureRecognizer(listener, testContext)
+ mRecognizer.longPressTimeout = LongPressTimeoutMillis
+ }
+
+ // Tests that verify conditions under which onLongPress will not be called.
+
+ @Test
+ fun pointerInputHandler_down_onLongPressNotCalled() {
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(down()))
+ verify(mRecognizer.onLongPress, never()).invoke(any())
+ }
+
+ @Test
+ fun pointerInputHandler_downWithinTimeout_onLongPressNotCalled() {
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(down()))
+ testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
+ verify(mRecognizer.onLongPress, never()).invoke(any())
+ }
+
+ @Test
+ fun pointerInputHandler_DownMoveConsumed_onLongPressNotCalled() {
+ val down = down(0)
+ val move = down.moveBy(50.milliseconds, 1f, 1f).consume(1f, 0f)
+
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(down))
+ testContext.advanceTimeBy(50, TimeUnit.MILLISECONDS)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(move))
+ testContext.advanceTimeBy(50, TimeUnit.MILLISECONDS)
+
+ verify(mRecognizer.onLongPress, never()).invoke(any())
+ }
+
+ @Test
+ fun pointerInputHandler_2Down1MoveConsumed_onLongPressNotCalled() {
+ val down0 = down(0)
+ val down1 = down(1)
+ val move0 = down0.moveBy(50.milliseconds, 1f, 1f).consume(1f, 0f)
+ val move1 = down0.moveBy(50.milliseconds, 0f, 0f)
+
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(down0, down1))
+ testContext.advanceTimeBy(50, TimeUnit.MILLISECONDS)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(move0, move1))
+ testContext.advanceTimeBy(50, TimeUnit.MILLISECONDS)
+
+ verify(mRecognizer.onLongPress, never()).invoke(any())
+ }
+
+ @Test
+ fun pointerInputHandler_DownUpConsumed_onLongPressNotCalled() {
+ val down = down(0)
+ val up = down.up(50L.millisecondsToTimestamp()).consumeDownChange()
+
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(down))
+ testContext.advanceTimeBy(50, TimeUnit.MILLISECONDS)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(up))
+ testContext.advanceTimeBy(50, TimeUnit.MILLISECONDS)
+
+ verify(mRecognizer.onLongPress, never()).invoke(any())
+ }
+
+ @Test
+ fun pointerInputHandler_DownUpNotConsumed_onLongPressNotCalled() {
+ val down = down(0)
+ val up = down.up(50L.millisecondsToTimestamp())
+
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(down))
+ testContext.advanceTimeBy(50, TimeUnit.MILLISECONDS)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(up))
+ testContext.advanceTimeBy(50, TimeUnit.MILLISECONDS)
+
+ verify(mRecognizer.onLongPress, never()).invoke(any())
+ }
+
+ @Test
+ fun pointerInputHandler_2DownIndependentlyUnderTimeoutAndDoNotOverlap_onLongPressNotCalled() {
+
+ // Arrange
+
+ val down0 = down(0)
+
+ val up0 = down0.up(50L.millisecondsToTimestamp())
+
+ val down1 = down(1, 51L.millisecondsToTimestamp())
+
+ // Act
+
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(
+ down0
+ ))
+
+ testContext.advanceTimeBy(50, TimeUnit.MILLISECONDS)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(
+ up0
+ ))
+
+ testContext.advanceTimeBy(1, TimeUnit.MILLISECONDS)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(
+ down1
+ ))
+
+ testContext.advanceTimeBy(50, TimeUnit.MILLISECONDS)
+
+ // Assert
+
+ verify(mRecognizer.onLongPress, never()).invoke(any())
+ }
+
+ // Tests that verify conditions under which onLongPress will be called.
+
+ @Test
+ fun pointerInputHandler_downBeyondTimeout_onLongPressCalled() {
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(down()))
+ testContext.advanceTimeBy(100, TimeUnit.MILLISECONDS)
+ verify(mRecognizer.onLongPress).invoke(any())
+ }
+
+ @Test
+ fun pointerInputHandler_2DownBeyondTimeout_onLongPressCalled() {
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(down(0), down(1)))
+ testContext.advanceTimeBy(100, TimeUnit.MILLISECONDS)
+ verify(mRecognizer.onLongPress).invoke(any())
+ }
+
+ @Test
+ fun pointerInputHandler_2DownIndependentlyUnderTimeoutButOverlapTimeIsOver_onLongPressCalled() {
+
+ // Arrange
+
+ val down0 = down(0)
+
+ val move0 = down0.moveTo(50L.millisecondsToTimestamp(), 0f, 0f)
+ val down1 = down(1, 50L.millisecondsToTimestamp())
+
+ val up0 = move0.up(75L.millisecondsToTimestamp())
+ val move1 = down1.moveTo(75L.millisecondsToTimestamp(), 0f, 0f)
+
+ // Act
+
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(
+ down0
+ ))
+
+ testContext.advanceTimeBy(50, TimeUnit.MILLISECONDS)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(
+ move0, down1
+ ))
+
+ testContext.advanceTimeBy(25, TimeUnit.MILLISECONDS)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(
+ up0, move1
+ ))
+
+ testContext.advanceTimeBy(25, TimeUnit.MILLISECONDS)
+
+ // Assert
+
+ verify(mRecognizer.onLongPress).invoke(any())
+ }
+
+ @Test
+ fun pointerInputHandler_downMoveNotConsumed_onLongPressCalled() {
+ val down = down(0)
+ val move = down.moveBy(50.milliseconds, 1f, 1f)
+
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(down))
+ testContext.advanceTimeBy(50, TimeUnit.MILLISECONDS)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(move))
+ testContext.advanceTimeBy(50, TimeUnit.MILLISECONDS)
+
+ verify(mRecognizer.onLongPress).invoke(any())
+ }
+
+ // Tests that verify correctness of PxPosition value passed to onLongPress
+
+ @Test
+ fun pointerInputHandler_down_onLongPressCalledWithDownPosition() {
+ val down = down(0, x = 13f, y = 17f)
+
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(down))
+ testContext.advanceTimeBy(100, TimeUnit.MILLISECONDS)
+
+ verify(mRecognizer.onLongPress).invoke(PxPosition(13.px, 17.px))
+ }
+
+ @Test
+ fun pointerInputHandler_downMove_onLongPressCalledWithMovePosition() {
+ val down = down(0, x = 13f, y = 17f)
+ val move = down.moveTo(50L.millisecondsToTimestamp(), -7f, 5f)
+
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(down))
+ testContext.advanceTimeBy(50, TimeUnit.MILLISECONDS)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(move))
+ testContext.advanceTimeBy(50, TimeUnit.MILLISECONDS)
+
+ verify(mRecognizer.onLongPress).invoke(PxPosition((-7).px, 5.px))
+ }
+
+ @Test
+ fun pointerInputHandler_downThenDown_onLongPressCalledWithFirstDownPosition() {
+ val down0 = down(0, x = 13f, y = 17f)
+
+ val move0 = down0.moveBy(50.milliseconds, 0f, 0f)
+ val down1 = down(1, 50L.millisecondsToTimestamp(), x = 11f, y = 19f)
+
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(down0))
+ testContext.advanceTimeBy(50, TimeUnit.MILLISECONDS)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(move0, down1))
+ testContext.advanceTimeBy(50, TimeUnit.MILLISECONDS)
+
+ verify(mRecognizer.onLongPress).invoke(PxPosition(13.px, 17.px))
+ }
+
+ @Test
+ fun pointerInputHandler_down0ThenDown1ThenUp0_onLongPressCalledWithDown1Position() {
+ val down0 = down(0, x = 13f, y = 17f)
+
+ val move0 = down0.moveTo(50L.millisecondsToTimestamp(), 27f, 29f)
+ val down1 = down(1, 50L.millisecondsToTimestamp(), x = 11f, y = 19f)
+
+ val up0 = move0.up(75L.millisecondsToTimestamp())
+ val move1 = down1.moveBy(25.milliseconds, 0f, 0f)
+
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(down0))
+ testContext.advanceTimeBy(50, TimeUnit.MILLISECONDS)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(move0, down1))
+ testContext.advanceTimeBy(25, TimeUnit.MILLISECONDS)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(up0, move1))
+ testContext.advanceTimeBy(25, TimeUnit.MILLISECONDS)
+
+ verify(mRecognizer.onLongPress).invoke(PxPosition(11.px, 19.px))
+ }
+
+ @Test
+ fun pointerInputHandler_down0ThenMove0AndDown1_onLongPressCalledWithMove0Position() {
+ val down0 = down(0, x = 13f, y = 17f)
+
+ val move0 = down0.moveTo(50L.millisecondsToTimestamp(), 27f, 29f)
+ val down1 = down(1, 50L.millisecondsToTimestamp(), x = 11f, y = 19f)
+
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(down0))
+ testContext.advanceTimeBy(50, TimeUnit.MILLISECONDS)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(move0, down1))
+ testContext.advanceTimeBy(50, TimeUnit.MILLISECONDS)
+
+ verify(mRecognizer.onLongPress).invoke(PxPosition(27.px, 29.px))
+ }
+
+ @Test
+ fun pointerInputHandler_down0Down1Move1Up0_onLongPressCalledWithMove1Position() {
+ val down0 = down(0, x = 13f, y = 17f)
+
+ val move0 = down0.moveBy(25.milliseconds, 0f, 0f)
+ val down1 = down(1, 25L.millisecondsToTimestamp(), x = 11f, y = 19f)
+
+ val up0 = move0.up(50L.millisecondsToTimestamp())
+ val move1 = down1.moveTo(50L.millisecondsToTimestamp(), 27f, 23f)
+
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(down0))
+ testContext.advanceTimeBy(25, TimeUnit.MILLISECONDS)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(move0, down1))
+ testContext.advanceTimeBy(25, TimeUnit.MILLISECONDS)
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(up0, move1))
+ testContext.advanceTimeBy(50, TimeUnit.MILLISECONDS)
+
+ verify(mRecognizer.onLongPress).invoke(PxPosition(27.px, 23.px))
+ }
+
+ // Tests that verify that consumption behavior
+
+ @Test
+ fun pointerInputHandler_1Down_notConsumed() {
+ val down0 = down(0)
+ val result = mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(
+ down0
+ ))
+ assertThat(result[0].consumed.downChange).isFalse()
+ }
+
+ @Test
+ fun pointerInputHandler_1DownThen1Down_notConsumed() {
+
+ // Arrange
+
+ val down0 = down(0, 0L.millisecondsToTimestamp())
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(
+ down0
+ ))
+
+ // Act
+
+ testContext.advanceTimeBy(10, TimeUnit.MILLISECONDS)
+ val move0 = down0.moveTo(10L.millisecondsToTimestamp(), 0f, 0f)
+ val down1 = down(0, 10L.millisecondsToTimestamp())
+ val result = mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(
+ move0, down1
+ ))
+
+ // Assert
+
+ assertThat(result[0].consumed.downChange).isFalse()
+ assertThat(result[1].consumed.downChange).isFalse()
+ }
+
+ @Test
+ fun pointerInputHandler_1DownUnderTimeUp_upNotConsumed() {
+
+ // Arrange
+
+ val down0 = down(0, 0L.millisecondsToTimestamp())
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(
+ down0
+ ))
+
+ // Act
+
+ testContext.advanceTimeBy(50, TimeUnit.MILLISECONDS)
+ val up0 = down0.up(50L.millisecondsToTimestamp())
+ val result = mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(
+ up0
+ ))
+
+ // Assert
+
+ assertThat(result[0].consumed.downChange).isFalse()
+ }
+
+ @Test
+ fun pointerInputHandler_1DownUOverTimeUp_upConsumedOnInitialDown() {
+
+ // Arrange
+
+ val down0 = down(0, 0L.millisecondsToTimestamp())
+ mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(
+ down0
+ ))
+
+ // Act
+
+ testContext.advanceTimeBy(101, TimeUnit.MILLISECONDS)
+ val up0 = down0.up(100L.millisecondsToTimestamp())
+ val result = mRecognizer.pointerInputHandler.invoke(listOf(
+ up0
+ ), PointerEventPass.InitialDown)
+
+ // Assert
+
+ assertThat(result[0].consumed.downChange).isTrue()
+ }
+}
\ No newline at end of file
diff --git a/ui/ui-layout/api/1.0.0-alpha01.txt b/ui/ui-layout/api/1.0.0-alpha01.txt
index c19b96d..fe4723d 100644
--- a/ui/ui-layout/api/1.0.0-alpha01.txt
+++ b/ui/ui-layout/api/1.0.0-alpha01.txt
@@ -182,23 +182,36 @@
public abstract sealed class TableColumnWidth {
}
- public static final class TableColumnWidth.Fixed extends androidx.ui.layout.TableColumnWidth {
- ctor public TableColumnWidth.Fixed(internal androidx.ui.core.Dp width);
- method public androidx.ui.layout.TableColumnWidth.Fixed copy(androidx.ui.core.Dp width);
+ public static final class TableColumnWidth.Flexible extends androidx.ui.layout.TableColumnWidth {
+ ctor public TableColumnWidth.Flexible(internal float flex);
+ method public androidx.ui.layout.TableColumnWidth.Flexible copy(float flex);
}
- public static final class TableColumnWidth.Flex extends androidx.ui.layout.TableColumnWidth {
- ctor public TableColumnWidth.Flex(internal float flex);
- method public androidx.ui.layout.TableColumnWidth.Flex copy(float flex);
+ public abstract static sealed class TableColumnWidth.Inflexible extends androidx.ui.layout.TableColumnWidth {
}
- public static final class TableColumnWidth.Fraction extends androidx.ui.layout.TableColumnWidth {
- ctor public TableColumnWidth.Fraction(@FloatRange(from=null, to=null) internal float fraction);
- method public androidx.ui.layout.TableColumnWidth.Fraction copy(float fraction);
+ public static final class TableColumnWidth.Inflexible.Fixed extends androidx.ui.layout.TableColumnWidth.Inflexible {
+ ctor public TableColumnWidth.Inflexible.Fixed(internal androidx.ui.core.Dp width);
+ method public androidx.ui.layout.TableColumnWidth.Inflexible.Fixed copy(androidx.ui.core.Dp width);
}
- public static final class TableColumnWidth.Wrap extends androidx.ui.layout.TableColumnWidth {
- field public static final androidx.ui.layout.TableColumnWidth.Wrap! INSTANCE;
+ public static final class TableColumnWidth.Inflexible.Fraction extends androidx.ui.layout.TableColumnWidth.Inflexible {
+ ctor public TableColumnWidth.Inflexible.Fraction(@FloatRange(from=null, to=null) internal float fraction);
+ method public androidx.ui.layout.TableColumnWidth.Inflexible.Fraction copy(float fraction);
+ }
+
+ public static final class TableColumnWidth.Inflexible.Max extends androidx.ui.layout.TableColumnWidth.Inflexible {
+ ctor public TableColumnWidth.Inflexible.Max(internal androidx.ui.layout.TableColumnWidth.Inflexible a, internal androidx.ui.layout.TableColumnWidth.Inflexible b);
+ method public androidx.ui.layout.TableColumnWidth.Inflexible.Max copy(androidx.ui.layout.TableColumnWidth.Inflexible a, androidx.ui.layout.TableColumnWidth.Inflexible b);
+ }
+
+ public static final class TableColumnWidth.Inflexible.Min extends androidx.ui.layout.TableColumnWidth.Inflexible {
+ ctor public TableColumnWidth.Inflexible.Min(internal androidx.ui.layout.TableColumnWidth.Inflexible a, internal androidx.ui.layout.TableColumnWidth.Inflexible b);
+ method public androidx.ui.layout.TableColumnWidth.Inflexible.Min copy(androidx.ui.layout.TableColumnWidth.Inflexible a, androidx.ui.layout.TableColumnWidth.Inflexible b);
+ }
+
+ public static final class TableColumnWidth.Inflexible.Wrap extends androidx.ui.layout.TableColumnWidth.Inflexible {
+ field public static final androidx.ui.layout.TableColumnWidth.Inflexible.Wrap! INSTANCE;
}
public final class TableKt {
diff --git a/ui/ui-layout/api/current.txt b/ui/ui-layout/api/current.txt
index c19b96d..fe4723d 100644
--- a/ui/ui-layout/api/current.txt
+++ b/ui/ui-layout/api/current.txt
@@ -182,23 +182,36 @@
public abstract sealed class TableColumnWidth {
}
- public static final class TableColumnWidth.Fixed extends androidx.ui.layout.TableColumnWidth {
- ctor public TableColumnWidth.Fixed(internal androidx.ui.core.Dp width);
- method public androidx.ui.layout.TableColumnWidth.Fixed copy(androidx.ui.core.Dp width);
+ public static final class TableColumnWidth.Flexible extends androidx.ui.layout.TableColumnWidth {
+ ctor public TableColumnWidth.Flexible(internal float flex);
+ method public androidx.ui.layout.TableColumnWidth.Flexible copy(float flex);
}
- public static final class TableColumnWidth.Flex extends androidx.ui.layout.TableColumnWidth {
- ctor public TableColumnWidth.Flex(internal float flex);
- method public androidx.ui.layout.TableColumnWidth.Flex copy(float flex);
+ public abstract static sealed class TableColumnWidth.Inflexible extends androidx.ui.layout.TableColumnWidth {
}
- public static final class TableColumnWidth.Fraction extends androidx.ui.layout.TableColumnWidth {
- ctor public TableColumnWidth.Fraction(@FloatRange(from=null, to=null) internal float fraction);
- method public androidx.ui.layout.TableColumnWidth.Fraction copy(float fraction);
+ public static final class TableColumnWidth.Inflexible.Fixed extends androidx.ui.layout.TableColumnWidth.Inflexible {
+ ctor public TableColumnWidth.Inflexible.Fixed(internal androidx.ui.core.Dp width);
+ method public androidx.ui.layout.TableColumnWidth.Inflexible.Fixed copy(androidx.ui.core.Dp width);
}
- public static final class TableColumnWidth.Wrap extends androidx.ui.layout.TableColumnWidth {
- field public static final androidx.ui.layout.TableColumnWidth.Wrap! INSTANCE;
+ public static final class TableColumnWidth.Inflexible.Fraction extends androidx.ui.layout.TableColumnWidth.Inflexible {
+ ctor public TableColumnWidth.Inflexible.Fraction(@FloatRange(from=null, to=null) internal float fraction);
+ method public androidx.ui.layout.TableColumnWidth.Inflexible.Fraction copy(float fraction);
+ }
+
+ public static final class TableColumnWidth.Inflexible.Max extends androidx.ui.layout.TableColumnWidth.Inflexible {
+ ctor public TableColumnWidth.Inflexible.Max(internal androidx.ui.layout.TableColumnWidth.Inflexible a, internal androidx.ui.layout.TableColumnWidth.Inflexible b);
+ method public androidx.ui.layout.TableColumnWidth.Inflexible.Max copy(androidx.ui.layout.TableColumnWidth.Inflexible a, androidx.ui.layout.TableColumnWidth.Inflexible b);
+ }
+
+ public static final class TableColumnWidth.Inflexible.Min extends androidx.ui.layout.TableColumnWidth.Inflexible {
+ ctor public TableColumnWidth.Inflexible.Min(internal androidx.ui.layout.TableColumnWidth.Inflexible a, internal androidx.ui.layout.TableColumnWidth.Inflexible b);
+ method public androidx.ui.layout.TableColumnWidth.Inflexible.Min copy(androidx.ui.layout.TableColumnWidth.Inflexible a, androidx.ui.layout.TableColumnWidth.Inflexible b);
+ }
+
+ public static final class TableColumnWidth.Inflexible.Wrap extends androidx.ui.layout.TableColumnWidth.Inflexible {
+ field public static final androidx.ui.layout.TableColumnWidth.Inflexible.Wrap! INSTANCE;
}
public final class TableKt {
diff --git a/ui/ui-layout/api/restricted_1.0.0-alpha01.txt b/ui/ui-layout/api/restricted_1.0.0-alpha01.txt
index c19b96d..fe4723d 100644
--- a/ui/ui-layout/api/restricted_1.0.0-alpha01.txt
+++ b/ui/ui-layout/api/restricted_1.0.0-alpha01.txt
@@ -182,23 +182,36 @@
public abstract sealed class TableColumnWidth {
}
- public static final class TableColumnWidth.Fixed extends androidx.ui.layout.TableColumnWidth {
- ctor public TableColumnWidth.Fixed(internal androidx.ui.core.Dp width);
- method public androidx.ui.layout.TableColumnWidth.Fixed copy(androidx.ui.core.Dp width);
+ public static final class TableColumnWidth.Flexible extends androidx.ui.layout.TableColumnWidth {
+ ctor public TableColumnWidth.Flexible(internal float flex);
+ method public androidx.ui.layout.TableColumnWidth.Flexible copy(float flex);
}
- public static final class TableColumnWidth.Flex extends androidx.ui.layout.TableColumnWidth {
- ctor public TableColumnWidth.Flex(internal float flex);
- method public androidx.ui.layout.TableColumnWidth.Flex copy(float flex);
+ public abstract static sealed class TableColumnWidth.Inflexible extends androidx.ui.layout.TableColumnWidth {
}
- public static final class TableColumnWidth.Fraction extends androidx.ui.layout.TableColumnWidth {
- ctor public TableColumnWidth.Fraction(@FloatRange(from=null, to=null) internal float fraction);
- method public androidx.ui.layout.TableColumnWidth.Fraction copy(float fraction);
+ public static final class TableColumnWidth.Inflexible.Fixed extends androidx.ui.layout.TableColumnWidth.Inflexible {
+ ctor public TableColumnWidth.Inflexible.Fixed(internal androidx.ui.core.Dp width);
+ method public androidx.ui.layout.TableColumnWidth.Inflexible.Fixed copy(androidx.ui.core.Dp width);
}
- public static final class TableColumnWidth.Wrap extends androidx.ui.layout.TableColumnWidth {
- field public static final androidx.ui.layout.TableColumnWidth.Wrap! INSTANCE;
+ public static final class TableColumnWidth.Inflexible.Fraction extends androidx.ui.layout.TableColumnWidth.Inflexible {
+ ctor public TableColumnWidth.Inflexible.Fraction(@FloatRange(from=null, to=null) internal float fraction);
+ method public androidx.ui.layout.TableColumnWidth.Inflexible.Fraction copy(float fraction);
+ }
+
+ public static final class TableColumnWidth.Inflexible.Max extends androidx.ui.layout.TableColumnWidth.Inflexible {
+ ctor public TableColumnWidth.Inflexible.Max(internal androidx.ui.layout.TableColumnWidth.Inflexible a, internal androidx.ui.layout.TableColumnWidth.Inflexible b);
+ method public androidx.ui.layout.TableColumnWidth.Inflexible.Max copy(androidx.ui.layout.TableColumnWidth.Inflexible a, androidx.ui.layout.TableColumnWidth.Inflexible b);
+ }
+
+ public static final class TableColumnWidth.Inflexible.Min extends androidx.ui.layout.TableColumnWidth.Inflexible {
+ ctor public TableColumnWidth.Inflexible.Min(internal androidx.ui.layout.TableColumnWidth.Inflexible a, internal androidx.ui.layout.TableColumnWidth.Inflexible b);
+ method public androidx.ui.layout.TableColumnWidth.Inflexible.Min copy(androidx.ui.layout.TableColumnWidth.Inflexible a, androidx.ui.layout.TableColumnWidth.Inflexible b);
+ }
+
+ public static final class TableColumnWidth.Inflexible.Wrap extends androidx.ui.layout.TableColumnWidth.Inflexible {
+ field public static final androidx.ui.layout.TableColumnWidth.Inflexible.Wrap! INSTANCE;
}
public final class TableKt {
diff --git a/ui/ui-layout/api/restricted_current.txt b/ui/ui-layout/api/restricted_current.txt
index c19b96d..fe4723d 100644
--- a/ui/ui-layout/api/restricted_current.txt
+++ b/ui/ui-layout/api/restricted_current.txt
@@ -182,23 +182,36 @@
public abstract sealed class TableColumnWidth {
}
- public static final class TableColumnWidth.Fixed extends androidx.ui.layout.TableColumnWidth {
- ctor public TableColumnWidth.Fixed(internal androidx.ui.core.Dp width);
- method public androidx.ui.layout.TableColumnWidth.Fixed copy(androidx.ui.core.Dp width);
+ public static final class TableColumnWidth.Flexible extends androidx.ui.layout.TableColumnWidth {
+ ctor public TableColumnWidth.Flexible(internal float flex);
+ method public androidx.ui.layout.TableColumnWidth.Flexible copy(float flex);
}
- public static final class TableColumnWidth.Flex extends androidx.ui.layout.TableColumnWidth {
- ctor public TableColumnWidth.Flex(internal float flex);
- method public androidx.ui.layout.TableColumnWidth.Flex copy(float flex);
+ public abstract static sealed class TableColumnWidth.Inflexible extends androidx.ui.layout.TableColumnWidth {
}
- public static final class TableColumnWidth.Fraction extends androidx.ui.layout.TableColumnWidth {
- ctor public TableColumnWidth.Fraction(@FloatRange(from=null, to=null) internal float fraction);
- method public androidx.ui.layout.TableColumnWidth.Fraction copy(float fraction);
+ public static final class TableColumnWidth.Inflexible.Fixed extends androidx.ui.layout.TableColumnWidth.Inflexible {
+ ctor public TableColumnWidth.Inflexible.Fixed(internal androidx.ui.core.Dp width);
+ method public androidx.ui.layout.TableColumnWidth.Inflexible.Fixed copy(androidx.ui.core.Dp width);
}
- public static final class TableColumnWidth.Wrap extends androidx.ui.layout.TableColumnWidth {
- field public static final androidx.ui.layout.TableColumnWidth.Wrap! INSTANCE;
+ public static final class TableColumnWidth.Inflexible.Fraction extends androidx.ui.layout.TableColumnWidth.Inflexible {
+ ctor public TableColumnWidth.Inflexible.Fraction(@FloatRange(from=null, to=null) internal float fraction);
+ method public androidx.ui.layout.TableColumnWidth.Inflexible.Fraction copy(float fraction);
+ }
+
+ public static final class TableColumnWidth.Inflexible.Max extends androidx.ui.layout.TableColumnWidth.Inflexible {
+ ctor public TableColumnWidth.Inflexible.Max(internal androidx.ui.layout.TableColumnWidth.Inflexible a, internal androidx.ui.layout.TableColumnWidth.Inflexible b);
+ method public androidx.ui.layout.TableColumnWidth.Inflexible.Max copy(androidx.ui.layout.TableColumnWidth.Inflexible a, androidx.ui.layout.TableColumnWidth.Inflexible b);
+ }
+
+ public static final class TableColumnWidth.Inflexible.Min extends androidx.ui.layout.TableColumnWidth.Inflexible {
+ ctor public TableColumnWidth.Inflexible.Min(internal androidx.ui.layout.TableColumnWidth.Inflexible a, internal androidx.ui.layout.TableColumnWidth.Inflexible b);
+ method public androidx.ui.layout.TableColumnWidth.Inflexible.Min copy(androidx.ui.layout.TableColumnWidth.Inflexible a, androidx.ui.layout.TableColumnWidth.Inflexible b);
+ }
+
+ public static final class TableColumnWidth.Inflexible.Wrap extends androidx.ui.layout.TableColumnWidth.Inflexible {
+ field public static final androidx.ui.layout.TableColumnWidth.Inflexible.Wrap! INSTANCE;
}
public final class TableKt {
diff --git a/ui/ui-layout/build.gradle b/ui/ui-layout/build.gradle
index 077ade1..3336d84 100644
--- a/ui/ui-layout/build.gradle
+++ b/ui/ui-layout/build.gradle
@@ -44,7 +44,6 @@
testImplementation(ANDROIDX_TEST_RUNNER)
testImplementation(JUNIT)
- androidTestImplementation project(":benchmark")
androidTestImplementation project(":ui:ui-platform")
androidTestImplementation(ANDROIDX_TEST_RULES)
diff --git a/ui/ui-layout/integration-tests/layout-demos/src/main/java/androidx/ui/layout/demos/ComplexLayoutDemos.kt b/ui/ui-layout/integration-tests/layout-demos/src/main/java/androidx/ui/layout/demos/ComplexLayoutDemos.kt
index 52c2f1c..568e0a2 100644
--- a/ui/ui-layout/integration-tests/layout-demos/src/main/java/androidx/ui/layout/demos/ComplexLayoutDemos.kt
+++ b/ui/ui-layout/integration-tests/layout-demos/src/main/java/androidx/ui/layout/demos/ComplexLayoutDemos.kt
@@ -227,7 +227,7 @@
}
@Composable
-fun SingleCompositionRow(@Children children: @Composable() () -> Unit) {
+fun SingleCompositionRow(children: @Composable() () -> Unit) {
Layout(layoutBlock = { measurables, constraints ->
val placeables = measurables.map {
it.measure(constraints.copy(minWidth = 0.ipx, maxWidth = IntPx.Infinity))
@@ -246,7 +246,7 @@
}
@Composable
-fun SingleCompositionColumn(@Children children: @Composable() () -> Unit) {
+fun SingleCompositionColumn(children: @Composable() () -> Unit) {
Layout(layoutBlock = { measurables, constraints ->
val placeables = measurables.map {
it.measure(constraints.copy(minHeight = 0.ipx, maxHeight = IntPx.Infinity))
@@ -306,7 +306,7 @@
}
@Composable
-fun SingleCompositionRowWithIntrinsics(@Children children: @Composable() () -> Unit) {
+fun SingleCompositionRowWithIntrinsics(children: @Composable() () -> Unit) {
ComplexLayout(children = children, block = {
layout { measurables, constraints ->
val placeables = measurables.map { measurable ->
diff --git a/ui/ui-layout/integration-tests/layout-demos/src/main/java/androidx/ui/layout/demos/LayoutActivity.kt b/ui/ui-layout/integration-tests/layout-demos/src/main/java/androidx/ui/layout/demos/LayoutActivity.kt
index 1b94f79..c42418b 100644
--- a/ui/ui-layout/integration-tests/layout-demos/src/main/java/androidx/ui/layout/demos/LayoutActivity.kt
+++ b/ui/ui-layout/integration-tests/layout-demos/src/main/java/androidx/ui/layout/demos/LayoutActivity.kt
@@ -53,7 +53,7 @@
width: Dp? = null,
height: Dp? = null,
color: Color,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
Wrap {
DrawRectangle(color = color)
diff --git a/ui/ui-layout/integration-tests/samples/src/main/java/androidx/ui/layout/samples/TableSamples.kt b/ui/ui-layout/integration-tests/samples/src/main/java/androidx/ui/layout/samples/TableSamples.kt
index 89c0fc0..fc5c908 100644
--- a/ui/ui-layout/integration-tests/samples/src/main/java/androidx/ui/layout/samples/TableSamples.kt
+++ b/ui/ui-layout/integration-tests/samples/src/main/java/androidx/ui/layout/samples/TableSamples.kt
@@ -50,11 +50,11 @@
Table(
columnWidth = { columnIndex ->
when (columnIndex) {
- 0 -> TableColumnWidth.Wrap
- 1 -> TableColumnWidth.Flex(flex = 1f)
- 2 -> TableColumnWidth.Flex(flex = 3f)
- 3 -> TableColumnWidth.Fixed(width = 50.dp)
- else -> TableColumnWidth.Fraction(fraction = 0.5f)
+ 0 -> TableColumnWidth.Inflexible.Wrap
+ 1 -> TableColumnWidth.Flexible(flex = 1f)
+ 2 -> TableColumnWidth.Flexible(flex = 3f)
+ 3 -> TableColumnWidth.Inflexible.Fixed(width = 50.dp)
+ else -> TableColumnWidth.Inflexible.Fraction(fraction = 0.5f)
}
}
) {
diff --git a/ui/ui-layout/src/androidTest/AndroidManifest.xml b/ui/ui-layout/src/androidTest/AndroidManifest.xml
index 463edf8..b1e8fb7 100644
--- a/ui/ui-layout/src/androidTest/AndroidManifest.xml
+++ b/ui/ui-layout/src/androidTest/AndroidManifest.xml
@@ -17,7 +17,12 @@
xmlns:tools="http://schemas.android.com/tools"
package="androidx.ui.layout">
+ <!--
+ ~ Important: disable debuggable for accurate performance results
+ ~ requestLegacyExternalStorage to enable legacy JSON reporting when targeting Q
+ -->
<application
+ android:requestLegacyExternalStorage="true"
android:debuggable="false"
tools:ignore="HardcodedDebugMode"
tools:replace="android:debuggable">
diff --git a/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/FlexTest.kt b/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/FlexTest.kt
index d68feb4..6221fed 100644
--- a/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/FlexTest.kt
+++ b/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/FlexTest.kt
@@ -145,9 +145,12 @@
val root = findAndroidCraneView()
waitForDraw(root)
- assertEquals(PxSize(root.width.px / 3, childrenHeight.toPx()), childSize[0])
assertEquals(
- PxSize(root.width.px * 2 / 3, (heightDp.toPx() * 2).round().toPx()),
+ PxSize((root.width.px / 3).round().toPx(), childrenHeight.toPx()),
+ childSize[0]
+ )
+ assertEquals(
+ PxSize((root.width.px * 2 / 3).round().toPx(), (heightDp.toPx() * 2).round().toPx()),
childSize[1]
)
assertEquals(
diff --git a/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/IntrinsicTest.kt b/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/IntrinsicTest.kt
index dd375d3..556d6b4 100644
--- a/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/IntrinsicTest.kt
+++ b/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/IntrinsicTest.kt
@@ -500,7 +500,7 @@
maxIntrinsicWidth: Dp,
minIntrinsicHeight: Dp,
height: Dp, maxIntrinsicHeight: Dp,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
ComplexLayout(children) {
layout { _, constraints ->
diff --git a/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/LayoutTest.kt b/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/LayoutTest.kt
index 0042948..d8c6eb1 100644
--- a/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/LayoutTest.kt
+++ b/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/LayoutTest.kt
@@ -70,7 +70,7 @@
activityTestRule.runOnUiThread(runnable)
}
- internal fun show(@Children composable: @Composable() () -> Unit) {
+ internal fun show(composable: @Composable() () -> Unit) {
val runnable: Runnable = object : Runnable {
override fun run() {
activity.setContent(composable)
@@ -149,6 +149,7 @@
{ w -> measurable.maxIntrinsicHeight(w) }
)
layoutLatch.countDown()
+ layoutResult(0.ipx, 0.ipx) {}
}
minIntrinsicWidth { _, _ -> 0.ipx }
maxIntrinsicWidth { _, _ -> 0.ipx }
diff --git a/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/OnPositionedTest.kt b/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/OnPositionedTest.kt
index f38837a..deca623 100644
--- a/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/OnPositionedTest.kt
+++ b/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/OnPositionedTest.kt
@@ -17,6 +17,7 @@
package androidx.ui.layout.test
import android.widget.FrameLayout
+import androidx.compose.Model
import androidx.test.filters.SmallTest
import androidx.compose.composer
import androidx.ui.core.LayoutCoordinates
@@ -192,4 +193,31 @@
assertThat(realGlobalPosition).isEqualTo(globalPosition)
assertThat(realLocalPosition).isEqualTo(localPosition)
}
+
+ @Test
+ fun justAddedOnPositionedCallbackFiredWithoutLayoutChanges() = withDensity(density) {
+ val needCallback = NeedCallback(false)
+
+ val callbackLatch = CountDownLatch(1)
+ show {
+ Container(expanded = true) {
+ if (needCallback.value) {
+ OnPositioned(onPositioned = {
+ callbackLatch.countDown()
+ })
+ }
+ }
+ }
+
+ activityTestRule.runOnUiThread(object : Runnable {
+ override fun run() {
+ needCallback.value = true
+ }
+ })
+
+ assertThat(callbackLatch.await(1000, TimeUnit.SECONDS)).isEqualTo(true)
+ }
}
+
+@Model
+private data class NeedCallback(var value: Boolean)
\ No newline at end of file
diff --git a/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/ScrollerPerformance.kt b/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/ScrollerPerformance.kt
deleted file mode 100644
index 3efac80..0000000
--- a/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/ScrollerPerformance.kt
+++ /dev/null
@@ -1,197 +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.ui.layout.test
-
-import android.view.View
-import androidx.benchmark.BenchmarkRule
-import androidx.benchmark.measureRepeated
-import androidx.compose.Composable
-import androidx.compose.CompositionContext
-import androidx.compose.FrameManager
-import androidx.compose.composer
-import androidx.compose.memo
-import androidx.compose.unaryPlus
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.LargeTest
-import androidx.ui.core.Draw
-import androidx.ui.core.dp
-import androidx.ui.core.px
-import androidx.ui.core.setContent
-import androidx.ui.core.toRect
-import androidx.ui.core.withDensity
-import androidx.ui.graphics.Color
-import androidx.ui.layout.Column
-import androidx.ui.layout.Container
-import androidx.ui.layout.CrossAxisAlignment
-import androidx.ui.layout.ScrollerPosition
-import androidx.ui.layout.VerticalScroller
-import androidx.ui.painting.Paint
-import androidx.ui.painting.PaintingStyle
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@LargeTest
-@RunWith(JUnit4::class)
-class ScrollerPerformance : LayoutTest() {
- @get:Rule
- val benchmarkRule = BenchmarkRule()
-
- @Test
- @FlakyTest
- fun benchmarkScrollLayout() {
- val scrollerPosition = ScrollerPosition()
- val compositionContext =
- composeScroller(scrollerPosition = scrollerPosition)
-
- val view = findAndroidCraneView()
-
- activityTestRule.runOnUiThread(object : Runnable {
- override fun run() {
- val widthSpec =
- View.MeasureSpec.makeMeasureSpec(view.measuredWidth, View.MeasureSpec.EXACTLY)
- val heightSpec =
- View.MeasureSpec.makeMeasureSpec(view.measuredHeight, View.MeasureSpec.EXACTLY)
- compositionContext.compose()
- view.measure(widthSpec, heightSpec)
- view.layout(view.left, view.top, view.right, view.bottom)
-
- val exec: BenchmarkRule.Scope.() -> Unit = {
- runWithTimingDisabled {
- if (scrollerPosition.position == 0.px) {
- scrollerPosition.position = 10.px
- } else {
- scrollerPosition.position = 0.px
- }
- FrameManager.nextFrame()
- compositionContext.recomposeSync()
- view.requestLayout()
- }
- view.measure(widthSpec, heightSpec)
- view.layout(view.left, view.top, view.right, view.bottom)
- }
- benchmarkRule.measureRepeated(exec)
- }
- })
- }
-
- @Test
- @FlakyTest
- fun benchmarkScrollComposition() {
- val scrollerPosition = ScrollerPosition()
- val compositionContext =
- composeScroller(scrollerPosition = scrollerPosition)
-
- activityTestRule.runOnUiThread(object : Runnable {
- override fun run() {
- compositionContext.compose()
- val exec: BenchmarkRule.Scope.() -> Unit = {
- runWithTimingDisabled {
- if (scrollerPosition.position == 0.px) {
- scrollerPosition.position = 10.px
- } else {
- scrollerPosition.position = 0.px
- }
- FrameManager.nextFrame()
- }
- compositionContext.recomposeSync()
- }
- benchmarkRule.measureRepeated(exec)
- }
- })
- }
-
- @Test
- @FlakyTest
- fun benchmarkLargeComposition() {
- val scrollerPosition = ScrollerPosition()
- val compositionContext =
- composeScroller(scrollerPosition = scrollerPosition)
-
- activityTestRule.runOnUiThread(object : Runnable {
- override fun run() {
- compositionContext.compose()
- val exec: BenchmarkRule.Scope.() -> Unit = {
- runWithTimingDisabled {
- if (scrollerPosition.position == 0.px) {
- scrollerPosition.position = 10.px
- } else {
- scrollerPosition.position = 0.px
- }
- FrameManager.nextFrame()
- }
- compositionContext.compose()
- }
- benchmarkRule.measureRepeated(exec)
- }
- })
- }
-
- fun composeScroller(
- scrollerPosition: ScrollerPosition = ScrollerPosition()
- ): CompositionContext {
- var compositionContext: CompositionContext? = null
- // We assume that the height of the device is more than 45 px
- withDensity(density) {
- val runnable: Runnable = object : Runnable {
- override fun run() {
- compositionContext = activity.setContent {
- VerticalScroller(
- scrollerPosition = scrollerPosition
- ) {
- Column(crossAxisAlignment = CrossAxisAlignment.Start) {
- for (green in 0..0xFF) {
- ColorStripe(0xFF, green, 0)
- }
- for (red in 0xFF downTo 0) {
- ColorStripe(red, 0xFF, 0)
- }
- for (blue in 0..0xFF) {
- ColorStripe(0, 0xFF, blue)
- }
- for (green in 0xFF downTo 0) {
- ColorStripe(0, green, 0xFF)
- }
- for (red in 0..0xFF) {
- ColorStripe(red, 0, 0xFF)
- }
- for (blue in 0xFF downTo 0) {
- ColorStripe(0xFF, 0, blue)
- }
- }
- }
- }
- }
- }
- activityTestRule.runOnUiThread(runnable)
- }
- return compositionContext!!
- }
-}
-
-@Composable
-fun ColorStripe(red: Int, green: Int, blue: Int) {
- val paint = +memo { Paint() }
- Container(height = 5.dp, width = 45.dp) {
- Draw { canvas, parentSize ->
- paint.color = Color(red = red, green = green, blue = blue)
- paint.style = PaintingStyle.fill
- canvas.drawRect(parentSize.toRect(), paint)
- }
- }
-}
diff --git a/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/TableTest.kt b/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/TableTest.kt
index 4aac01a..889d4c9 100644
--- a/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/TableTest.kt
+++ b/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/TableTest.kt
@@ -23,6 +23,8 @@
import androidx.ui.core.PxSize
import androidx.ui.core.Ref
import androidx.ui.core.ipx
+import androidx.ui.core.max
+import androidx.ui.core.min
import androidx.ui.core.withDensity
import androidx.ui.layout.Align
import androidx.ui.layout.Alignment
@@ -31,6 +33,7 @@
import androidx.ui.layout.DpConstraints
import androidx.ui.layout.Table
import androidx.ui.layout.TableColumnWidth
+import androidx.ui.layout.sum
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@@ -49,8 +52,8 @@
val size = 64.ipx
val sizeDp = size.toDp()
- val maxWidth = 256.ipx
- val maxWidthDp = maxWidth.toDp()
+ val tableWidth = 256.ipx
+ val tableWidthDp = tableWidth.toDp()
val tableSize = Ref<PxSize>()
val childSize = Array(rows) { Array(columns) { Ref<PxSize>() } }
@@ -59,7 +62,7 @@
show {
Align(Alignment.TopLeft) {
- ConstrainedBox(constraints = DpConstraints(maxWidth = maxWidthDp)) {
+ ConstrainedBox(constraints = DpConstraints(maxWidth = tableWidthDp)) {
OnChildPositioned(onPositioned = { coordinates ->
tableSize.value = coordinates.size
positionedLatch.countDown()
@@ -87,17 +90,17 @@
positionedLatch.await(1, TimeUnit.SECONDS)
assertEquals(
- PxSize(maxWidth, size * rows),
+ PxSize(tableWidth, size * rows),
tableSize.value
)
for (i in 0 until rows) {
for (j in 0 until columns) {
assertEquals(
- PxSize(maxWidth / columns, size),
+ PxSize(tableWidth / columns, size),
childSize[i][j].value
)
assertEquals(
- PxPosition(maxWidth * j / columns, size * i),
+ PxPosition(tableWidth * j / columns, size * i),
childPosition[i][j].value
)
}
@@ -105,7 +108,7 @@
}
@Test
- fun testTable_rowHeights() = withDensity(density) {
+ fun testTable_withDifferentRowHeights() = withDensity(density) {
val rows = 8
val columns = 8
@@ -113,8 +116,8 @@
val sizeDp = size.toDp()
val halfSize = 32.ipx
val halfSizeDp = halfSize.toDp()
- val maxWidth = 256.ipx
- val maxWidthDp = maxWidth.toDp()
+ val tableWidth = 256.ipx
+ val tableWidthDp = tableWidth.toDp()
val tableSize = Ref<PxSize>()
val childSize = Array(rows) { Array(columns) { Ref<PxSize>() } }
@@ -123,7 +126,7 @@
show {
Align(Alignment.TopLeft) {
- ConstrainedBox(constraints = DpConstraints(maxWidth = maxWidthDp)) {
+ ConstrainedBox(constraints = DpConstraints(maxWidth = tableWidthDp)) {
OnChildPositioned(onPositioned = { coordinates ->
tableSize.value = coordinates.size
positionedLatch.countDown()
@@ -133,7 +136,7 @@
tableRow {
for (j in 0 until columns) {
Container(
- height = if (j == 0) sizeDp else halfSizeDp,
+ height = if (j % 2 == 0) sizeDp else halfSizeDp,
expanded = true
) {
SaveLayoutInfo(
@@ -154,17 +157,17 @@
positionedLatch.await(1, TimeUnit.SECONDS)
assertEquals(
- PxSize(maxWidth, size * rows),
+ PxSize(tableWidth, size * rows),
tableSize.value
)
for (i in 0 until rows) {
for (j in 0 until columns) {
assertEquals(
- PxSize(maxWidth / columns, if (j == 0) size else halfSize),
+ PxSize(tableWidth / columns, if (j % 2 == 0) size else halfSize),
childSize[i][j].value
)
assertEquals(
- PxPosition(maxWidth * j / columns, size * i),
+ PxPosition(tableWidth * j / columns, size * i),
childPosition[i][j].value
)
}
@@ -172,7 +175,74 @@
}
@Test
- fun testTable_withColumnWidths_wrap() = withDensity(density) {
+ fun testTable_withColumnWidth_flexible() = withDensity(density) {
+ val rows = 8
+ val columns = 8
+
+ val size = 64.ipx
+ val sizeDp = size.toDp()
+ val tableWidth = 256.ipx
+ val tableWidthDp = tableWidth.toDp()
+
+ val tableSize = Ref<PxSize>()
+ val childSize = Array(rows) { Array(columns) { Ref<PxSize>() } }
+ val childPosition = Array(rows) { Array(columns) { Ref<PxPosition>() } }
+ val positionedLatch = CountDownLatch(rows * columns + 1)
+
+ val flexes = Array(columns) { j -> 2f.pow(max(j - 1, 0)) }
+ val totalFlex = flexes.sum()
+
+ show {
+ Align(Alignment.TopLeft) {
+ ConstrainedBox(constraints = DpConstraints(maxWidth = tableWidthDp)) {
+ OnChildPositioned(onPositioned = { coordinates ->
+ tableSize.value = coordinates.size
+ positionedLatch.countDown()
+ }) {
+ Table(columnWidth = { j ->
+ TableColumnWidth.Flexible(flex = flexes[j])
+ }) {
+ for (i in 0 until rows) {
+ tableRow {
+ for (j in 0 until columns) {
+ Container(height = sizeDp, expanded = true) {
+ SaveLayoutInfo(
+ size = childSize[i][j],
+ position = childPosition[i][j],
+ positionedLatch = positionedLatch
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ positionedLatch.await(1, TimeUnit.SECONDS)
+
+ assertEquals(
+ PxSize(tableWidth, size * rows),
+ tableSize.value
+ )
+ for (i in 0 until rows) {
+ for (j in 0 until columns) {
+ assertEquals(
+ PxSize(tableWidth * flexes[j] / totalFlex, size),
+ childSize[i][j].value
+ )
+ assertEquals(
+ PxPosition(tableWidth * flexes.take(j).sum() / totalFlex, size * i),
+ childPosition[i][j].value
+ )
+ }
+ }
+ }
+
+ @Test
+ fun testTable_withColumnWidth_inflexible_wrap() = withDensity(density) {
val rows = 8
val columns = 8
@@ -190,7 +260,7 @@
tableSize.value = coordinates.size
positionedLatch.countDown()
}) {
- Table(columnWidth = { TableColumnWidth.Wrap }) {
+ Table(columnWidth = { TableColumnWidth.Inflexible.Wrap }) {
for (i in 0 until rows) {
tableRow {
for (j in 0 until columns) {
@@ -230,74 +300,7 @@
}
@Test
- fun testTable_withColumnWidths_flex() = withDensity(density) {
- val rows = 8
- val columns = 8
-
- val size = 64.ipx
- val sizeDp = size.toDp()
- val maxWidth = 256.ipx
- val maxWidthDp = maxWidth.toDp()
-
- val tableSize = Ref<PxSize>()
- val childSize = Array(rows) { Array(columns) { Ref<PxSize>() } }
- val childPosition = Array(rows) { Array(columns) { Ref<PxPosition>() } }
- val positionedLatch = CountDownLatch(rows * columns + 1)
-
- val flexes = Array(columns) { j -> 2f.pow(max(j - 1, 0)) }
- val totalFlex = flexes.sum()
-
- show {
- Align(Alignment.TopLeft) {
- ConstrainedBox(constraints = DpConstraints(maxWidth = maxWidthDp)) {
- OnChildPositioned(onPositioned = { coordinates ->
- tableSize.value = coordinates.size
- positionedLatch.countDown()
- }) {
- Table(columnWidth = { j ->
- TableColumnWidth.Flex(flex = flexes[j])
- }) {
- for (i in 0 until rows) {
- tableRow {
- for (j in 0 until columns) {
- Container(height = sizeDp, expanded = true) {
- SaveLayoutInfo(
- size = childSize[i][j],
- position = childPosition[i][j],
- positionedLatch = positionedLatch
- )
- }
- }
- }
- }
- }
- }
- }
- }
- }
-
- positionedLatch.await(1, TimeUnit.SECONDS)
-
- assertEquals(
- PxSize(maxWidth, size * rows),
- tableSize.value
- )
- for (i in 0 until rows) {
- for (j in 0 until columns) {
- assertEquals(
- PxSize(maxWidth * flexes[j] / totalFlex, size),
- childSize[i][j].value
- )
- assertEquals(
- PxPosition(maxWidth * flexes.take(j).sum() / totalFlex, size * i),
- childPosition[i][j].value
- )
- }
- }
- }
-
- @Test
- fun testTable_withColumnWidths_fixed() = withDensity(density) {
+ fun testTable_withColumnWidth_inflexible_fixed() = withDensity(density) {
val rows = 8
val columns = 8
@@ -315,7 +318,7 @@
tableSize.value = coordinates.size
positionedLatch.countDown()
}) {
- Table(columnWidth = { TableColumnWidth.Fixed(width = sizeDp) }) {
+ Table(columnWidth = { TableColumnWidth.Inflexible.Fixed(width = sizeDp) }) {
for (i in 0 until rows) {
tableRow {
for (j in 0 until columns) {
@@ -355,14 +358,14 @@
}
@Test
- fun testTable_withColumnWidths_fraction() = withDensity(density) {
+ fun testTable_withColumnWidth_inflexible_fraction() = withDensity(density) {
val rows = 8
val columns = 8
val size = 64.ipx
val sizeDp = size.toDp()
- val maxWidth = 256.ipx
- val maxWidthDp = maxWidth.toDp()
+ val tableWidth = 256.ipx
+ val tableWidthDp = tableWidth.toDp()
val tableSize = Ref<PxSize>()
val childSize = Array(rows) { Array(columns) { Ref<PxSize>() } }
@@ -373,13 +376,13 @@
show {
Align(Alignment.TopLeft) {
- ConstrainedBox(constraints = DpConstraints(maxWidth = maxWidthDp)) {
+ ConstrainedBox(constraints = DpConstraints(maxWidth = tableWidthDp)) {
OnChildPositioned(onPositioned = { coordinates ->
tableSize.value = coordinates.size
positionedLatch.countDown()
}) {
Table(columnWidth = { j ->
- TableColumnWidth.Fraction(fraction = fractions[j])
+ TableColumnWidth.Inflexible.Fraction(fraction = fractions[j])
}) {
for (i in 0 until rows) {
tableRow {
@@ -403,17 +406,17 @@
positionedLatch.await(1, TimeUnit.SECONDS)
assertEquals(
- PxSize(maxWidth * fractions.sum(), size * rows),
+ PxSize(tableWidth * fractions.sum(), size * rows),
tableSize.value
)
for (i in 0 until rows) {
for (j in 0 until columns) {
assertEquals(
- PxSize(maxWidth * fractions[j], size),
+ PxSize(tableWidth * fractions[j], size),
childSize[i][j].value
)
assertEquals(
- PxPosition(maxWidth * fractions.take(j).sum(), size * i),
+ PxPosition(tableWidth * fractions.take(j).sum(), size * i),
childPosition[i][j].value
)
}
@@ -421,16 +424,16 @@
}
@Test
- fun testTable_withColumnWidths_mixed() = withDensity(density) {
+ fun testTable_withColumnWidth_inflexible_min() = withDensity(density) {
val rows = 8
- val columns = 5
+ val columns = 8
val size = 64.ipx
val sizeDp = size.toDp()
- val halfSize = 32.ipx
- val halfSizeDp = halfSize.toDp()
- val maxWidth = 256.ipx
- val maxWidthDp = maxWidth.toDp()
+ val minWidth = 24.ipx
+ val minWidthDp = minWidth.toDp()
+ val tableWidth = 256.ipx
+ val tableWidthDp = tableWidth.toDp()
val tableSize = Ref<PxSize>()
val childSize = Array(rows) { Array(columns) { Ref<PxSize>() } }
@@ -439,18 +442,416 @@
show {
Align(Alignment.TopLeft) {
- ConstrainedBox(constraints = DpConstraints(maxWidth = maxWidthDp)) {
+ ConstrainedBox(constraints = DpConstraints(maxWidth = tableWidthDp)) {
+ OnChildPositioned(onPositioned = { coordinates ->
+ tableSize.value = coordinates.size
+ positionedLatch.countDown()
+ }) {
+ Table(columnWidth = { j ->
+ TableColumnWidth.Inflexible.Min(
+ a = TableColumnWidth.Inflexible.Fixed(width = minWidthDp),
+ b = TableColumnWidth.Inflexible.Fraction(
+ fraction = if (j % 2 == 0) 1f / columns else 1f / (columns * 2)
+ )
+ )
+ }) {
+ for (i in 0 until rows) {
+ tableRow {
+ for (j in 0 until columns) {
+ Container(height = sizeDp, expanded = true) {
+ SaveLayoutInfo(
+ size = childSize[i][j],
+ position = childPosition[i][j],
+ positionedLatch = positionedLatch
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ positionedLatch.await(1, TimeUnit.SECONDS)
+
+ val expectedWidths = Array(columns) { j ->
+ min(minWidth, if (j % 2 == 0) tableWidth / columns else tableWidth / (columns * 2))
+ }
+
+ assertEquals(
+ PxSize(expectedWidths.sum(), size * rows),
+ tableSize.value
+ )
+ for (i in 0 until rows) {
+ for (j in 0 until columns) {
+ assertEquals(
+ PxSize(expectedWidths[j], size),
+ childSize[i][j].value
+ )
+ assertEquals(
+ PxPosition(expectedWidths.take(j).sum(), size * i),
+ childPosition[i][j].value
+ )
+ }
+ }
+ }
+
+ @Test
+ fun testTable_withColumnWidth_inflexible_max() = withDensity(density) {
+ val rows = 8
+ val columns = 8
+
+ val size = 64.ipx
+ val sizeDp = size.toDp()
+ val maxWidth = 24.ipx
+ val maxWidthDp = maxWidth.toDp()
+ val tableWidth = 256.ipx
+ val tableWidthDp = tableWidth.toDp()
+
+ val tableSize = Ref<PxSize>()
+ val childSize = Array(rows) { Array(columns) { Ref<PxSize>() } }
+ val childPosition = Array(rows) { Array(columns) { Ref<PxPosition>() } }
+ val positionedLatch = CountDownLatch(rows * columns + 1)
+
+ show {
+ Align(Alignment.TopLeft) {
+ ConstrainedBox(constraints = DpConstraints(maxWidth = tableWidthDp)) {
+ OnChildPositioned(onPositioned = { coordinates ->
+ tableSize.value = coordinates.size
+ positionedLatch.countDown()
+ }) {
+ Table(columnWidth = { j ->
+ TableColumnWidth.Inflexible.Max(
+ a = TableColumnWidth.Inflexible.Fixed(width = maxWidthDp),
+ b = TableColumnWidth.Inflexible.Fraction(
+ fraction = if (j % 2 == 0) 1f / columns else 1f / (columns * 2)
+ )
+ )
+ }) {
+ for (i in 0 until rows) {
+ tableRow {
+ for (j in 0 until columns) {
+ Container(height = sizeDp, expanded = true) {
+ SaveLayoutInfo(
+ size = childSize[i][j],
+ position = childPosition[i][j],
+ positionedLatch = positionedLatch
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ positionedLatch.await(1, TimeUnit.SECONDS)
+
+ val expectedWidths = Array(columns) { j ->
+ max(maxWidth, if (j % 2 == 0) tableWidth / columns else tableWidth / (columns * 2))
+ }
+
+ assertEquals(
+ PxSize(expectedWidths.sum(), size * rows),
+ tableSize.value
+ )
+ for (i in 0 until rows) {
+ for (j in 0 until columns) {
+ assertEquals(
+ PxSize(expectedWidths[j], size),
+ childSize[i][j].value
+ )
+ assertEquals(
+ PxPosition(expectedWidths.take(j).sum(), size * i),
+ childPosition[i][j].value
+ )
+ }
+ }
+ }
+
+ @Test
+ fun testTable_withColumnWidth_inflexible_min_oneWrap() = withDensity(density) {
+ val rows = 8
+ val columns = 8
+
+ val size = 64.ipx
+ val sizeDp = size.toDp()
+ val halfSize = 32.ipx
+ val halfSizeDp = halfSize.toDp()
+
+ val tableSize = Ref<PxSize>()
+ val childSize = Array(rows) { Array(columns) { Ref<PxSize>() } }
+ val childPosition = Array(rows) { Array(columns) { Ref<PxPosition>() } }
+ val positionedLatch = CountDownLatch(rows * columns + 1)
+
+ show {
+ Align(Alignment.TopLeft) {
+ OnChildPositioned(onPositioned = { coordinates ->
+ tableSize.value = coordinates.size
+ positionedLatch.countDown()
+ }) {
+ Table(columnWidth = { TableColumnWidth.Inflexible.Min(
+ a = TableColumnWidth.Inflexible.Wrap,
+ b = TableColumnWidth.Inflexible.Fixed(width = sizeDp)
+ ) }) {
+ for (i in 0 until rows) {
+ tableRow {
+ for (j in 0 until columns) {
+ Container(width = halfSizeDp, height = sizeDp) {
+ SaveLayoutInfo(
+ size = childSize[i][j],
+ position = childPosition[i][j],
+ positionedLatch = positionedLatch
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ positionedLatch.await(1, TimeUnit.SECONDS)
+
+ assertEquals(
+ PxSize(halfSize * columns, size * rows),
+ tableSize.value
+ )
+ for (i in 0 until rows) {
+ for (j in 0 until columns) {
+ assertEquals(
+ PxSize(halfSize, size),
+ childSize[i][j].value
+ )
+ assertEquals(
+ PxPosition(halfSize * j, size * i),
+ childPosition[i][j].value
+ )
+ }
+ }
+ }
+
+ @Test
+ fun testTable_withColumnWidth_inflexible_max_oneWrap() = withDensity(density) {
+ val rows = 8
+ val columns = 8
+
+ val size = 64.ipx
+ val sizeDp = size.toDp()
+ val halfSize = 32.ipx
+ val halfSizeDp = halfSize.toDp()
+
+ val tableSize = Ref<PxSize>()
+ val childSize = Array(rows) { Array(columns) { Ref<PxSize>() } }
+ val childPosition = Array(rows) { Array(columns) { Ref<PxPosition>() } }
+ val positionedLatch = CountDownLatch(rows * columns + 1)
+
+ show {
+ Align(Alignment.TopLeft) {
+ OnChildPositioned(onPositioned = { coordinates ->
+ tableSize.value = coordinates.size
+ positionedLatch.countDown()
+ }) {
+ Table(columnWidth = { TableColumnWidth.Inflexible.Max(
+ a = TableColumnWidth.Inflexible.Wrap,
+ b = TableColumnWidth.Inflexible.Fixed(width = sizeDp)
+ ) }) {
+ for (i in 0 until rows) {
+ tableRow {
+ for (j in 0 until columns) {
+ Container(width = halfSizeDp, height = sizeDp) {
+ SaveLayoutInfo(
+ size = childSize[i][j],
+ position = childPosition[i][j],
+ positionedLatch = positionedLatch
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ positionedLatch.await(1, TimeUnit.SECONDS)
+
+ assertEquals(
+ PxSize(size * columns, size * rows),
+ tableSize.value
+ )
+ for (i in 0 until rows) {
+ for (j in 0 until columns) {
+ assertEquals(
+ PxSize(halfSize, size),
+ childSize[i][j].value
+ )
+ assertEquals(
+ PxPosition(size * j, size * i),
+ childPosition[i][j].value
+ )
+ }
+ }
+ }
+
+ @Test
+ fun testTable_withColumnWidth_inflexible_min_bothWrap() = withDensity(density) {
+ val rows = 8
+ val columns = 8
+
+ val size = 64.ipx
+ val sizeDp = size.toDp()
+
+ val tableSize = Ref<PxSize>()
+ val childSize = Array(rows) { Array(columns) { Ref<PxSize>() } }
+ val childPosition = Array(rows) { Array(columns) { Ref<PxPosition>() } }
+ val positionedLatch = CountDownLatch(rows * columns + 1)
+
+ show {
+ Align(Alignment.TopLeft) {
+ OnChildPositioned(onPositioned = { coordinates ->
+ tableSize.value = coordinates.size
+ positionedLatch.countDown()
+ }) {
+ Table(columnWidth = { TableColumnWidth.Inflexible.Min(
+ a = TableColumnWidth.Inflexible.Wrap,
+ b = TableColumnWidth.Inflexible.Wrap
+ ) }) {
+ for (i in 0 until rows) {
+ tableRow {
+ for (j in 0 until columns) {
+ Container(width = sizeDp, height = sizeDp) {
+ SaveLayoutInfo(
+ size = childSize[i][j],
+ position = childPosition[i][j],
+ positionedLatch = positionedLatch
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ positionedLatch.await(1, TimeUnit.SECONDS)
+
+ assertEquals(
+ PxSize(size * columns, size * rows),
+ tableSize.value
+ )
+ for (i in 0 until rows) {
+ for (j in 0 until columns) {
+ assertEquals(
+ PxSize(size, size),
+ childSize[i][j].value
+ )
+ assertEquals(
+ PxPosition(size * j, size * i),
+ childPosition[i][j].value
+ )
+ }
+ }
+ }
+
+ @Test
+ fun testTable_withColumnWidth_inflexible_max_bothWrap() = withDensity(density) {
+ val rows = 8
+ val columns = 8
+
+ val size = 64.ipx
+ val sizeDp = size.toDp()
+
+ val tableSize = Ref<PxSize>()
+ val childSize = Array(rows) { Array(columns) { Ref<PxSize>() } }
+ val childPosition = Array(rows) { Array(columns) { Ref<PxPosition>() } }
+ val positionedLatch = CountDownLatch(rows * columns + 1)
+
+ show {
+ Align(Alignment.TopLeft) {
+ OnChildPositioned(onPositioned = { coordinates ->
+ tableSize.value = coordinates.size
+ positionedLatch.countDown()
+ }) {
+ Table(columnWidth = { TableColumnWidth.Inflexible.Max(
+ a = TableColumnWidth.Inflexible.Wrap,
+ b = TableColumnWidth.Inflexible.Wrap
+ ) }) {
+ for (i in 0 until rows) {
+ tableRow {
+ for (j in 0 until columns) {
+ Container(width = sizeDp, height = sizeDp) {
+ SaveLayoutInfo(
+ size = childSize[i][j],
+ position = childPosition[i][j],
+ positionedLatch = positionedLatch
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ positionedLatch.await(1, TimeUnit.SECONDS)
+
+ assertEquals(
+ PxSize(size * columns, size * rows),
+ tableSize.value
+ )
+ for (i in 0 until rows) {
+ for (j in 0 until columns) {
+ assertEquals(
+ PxSize(size, size),
+ childSize[i][j].value
+ )
+ assertEquals(
+ PxPosition(size * j, size * i),
+ childPosition[i][j].value
+ )
+ }
+ }
+ }
+
+ @Test
+ fun testTable_withDifferentColumnWidths() = withDensity(density) {
+ val rows = 8
+ val columns = 5
+
+ val size = 64.ipx
+ val sizeDp = size.toDp()
+ val halfSize = 32.ipx
+ val halfSizeDp = halfSize.toDp()
+ val tableWidth = 256.ipx
+ val tableWidthDp = tableWidth.toDp()
+
+ val tableSize = Ref<PxSize>()
+ val childSize = Array(rows) { Array(columns) { Ref<PxSize>() } }
+ val childPosition = Array(rows) { Array(columns) { Ref<PxPosition>() } }
+ val positionedLatch = CountDownLatch(rows * columns + 1)
+
+ show {
+ Align(Alignment.TopLeft) {
+ ConstrainedBox(constraints = DpConstraints(maxWidth = tableWidthDp)) {
OnChildPositioned(onPositioned = { coordinates ->
tableSize.value = coordinates.size
positionedLatch.countDown()
}) {
Table(columnWidth = { j ->
when (j) {
- 0 -> TableColumnWidth.Wrap
- 1 -> TableColumnWidth.Flex(flex = 1f)
- 2 -> TableColumnWidth.Flex(flex = 3f)
- 3 -> TableColumnWidth.Fixed(width = sizeDp)
- else -> TableColumnWidth.Fraction(fraction = 0.5f)
+ 0 -> TableColumnWidth.Inflexible.Wrap
+ 1 -> TableColumnWidth.Flexible(flex = 1f)
+ 2 -> TableColumnWidth.Flexible(flex = 3f)
+ 3 -> TableColumnWidth.Inflexible.Fixed(width = sizeDp)
+ else -> TableColumnWidth.Inflexible.Fraction(fraction = 0.5f)
}
}) {
for (i in 0 until rows) {
@@ -501,10 +902,9 @@
positionedLatch.await(1, TimeUnit.SECONDS)
assertEquals(
- PxSize(maxWidth, size * rows),
+ PxSize(tableWidth, size * rows),
tableSize.value
)
-
for (i in 0 until rows) {
// Wrap column 0
assertEquals(
@@ -517,7 +917,7 @@
)
// Flex column 1
assertEquals(
- PxSize((maxWidth / 2 - size - halfSize) / 4, size),
+ PxSize((tableWidth / 2 - size - halfSize) / 4, size),
childSize[i][1].value
)
assertEquals(
@@ -526,11 +926,11 @@
)
// Flex column 2
assertEquals(
- PxSize((maxWidth / 2 - size - halfSize) * 3 / 4, size),
+ PxSize((tableWidth / 2 - size - halfSize) * 3 / 4, size),
childSize[i][2].value
)
assertEquals(
- PxPosition(halfSize + (maxWidth / 2 - size - halfSize) / 4, size * i),
+ PxPosition(halfSize + (tableWidth / 2 - size - halfSize) / 4, size * i),
childPosition[i][2].value
)
// Fixed column 3
@@ -539,16 +939,16 @@
childSize[i][3].value
)
assertEquals(
- PxPosition(maxWidth / 2 - size, size * i),
+ PxPosition(tableWidth / 2 - size, size * i),
childPosition[i][3].value
)
// Fraction column 4
assertEquals(
- PxSize(maxWidth / 2, size),
+ PxSize(tableWidth / 2, size),
childSize[i][4].value
)
assertEquals(
- PxPosition(maxWidth / 2, size * i),
+ PxPosition(tableWidth / 2, size * i),
childPosition[i][4].value
)
}
diff --git a/ui/ui-layout/src/main/java/androidx/ui/layout/Align.kt b/ui/ui-layout/src/main/java/androidx/ui/layout/Align.kt
index 07144eb..888706f 100644
--- a/ui/ui-layout/src/main/java/androidx/ui/layout/Align.kt
+++ b/ui/ui-layout/src/main/java/androidx/ui/layout/Align.kt
@@ -69,7 +69,7 @@
* For a widget that does alignment and tries to be the same size as its child, see [Wrap].
*/
@Composable
-fun Align(alignment: Alignment, @Children children: @Composable() () -> Unit) {
+fun Align(alignment: Alignment, children: @Composable() () -> Unit) {
Layout(layoutBlock = { measurables, constraints ->
val measurable = measurables.firstOrNull()
// The child cannot be larger than our max constraints, but we ignore min constraints.
@@ -112,6 +112,6 @@
* }
*/
@Composable
-fun Center(@Children children: @Composable() () -> Unit) {
+fun Center(children: @Composable() () -> Unit) {
Align(alignment = Alignment.Center, children = children)
}
diff --git a/ui/ui-layout/src/main/java/androidx/ui/layout/AspectRatio.kt b/ui/ui-layout/src/main/java/androidx/ui/layout/AspectRatio.kt
index 8117ad6..2856b4b 100644
--- a/ui/ui-layout/src/main/java/androidx/ui/layout/AspectRatio.kt
+++ b/ui/ui-layout/src/main/java/androidx/ui/layout/AspectRatio.kt
@@ -51,7 +51,7 @@
@Composable
fun AspectRatio(
aspectRatio: Float,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
Layout(children) { measurables, constraints ->
val size = listOf(
diff --git a/ui/ui-layout/src/main/java/androidx/ui/layout/ConstrainedBox.kt b/ui/ui-layout/src/main/java/androidx/ui/layout/ConstrainedBox.kt
index e30c9b3..7ad2633 100644
--- a/ui/ui-layout/src/main/java/androidx/ui/layout/ConstrainedBox.kt
+++ b/ui/ui-layout/src/main/java/androidx/ui/layout/ConstrainedBox.kt
@@ -38,7 +38,7 @@
@Composable
fun ConstrainedBox(
constraints: DpConstraints,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
ComplexLayout(children) {
layout { measurables, incomingConstraints ->
diff --git a/ui/ui-layout/src/main/java/androidx/ui/layout/Container.kt b/ui/ui-layout/src/main/java/androidx/ui/layout/Container.kt
index 9027e93..dc48a4b 100644
--- a/ui/ui-layout/src/main/java/androidx/ui/layout/Container.kt
+++ b/ui/ui-layout/src/main/java/androidx/ui/layout/Container.kt
@@ -61,7 +61,7 @@
constraints: DpConstraints = DpConstraints(),
width: Dp? = null,
height: Dp? = null,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
trace("UI:Container") {
Layout(children = children, layoutBlock = { measurables, incomingConstraints ->
diff --git a/ui/ui-layout/src/main/java/androidx/ui/layout/Flex.kt b/ui/ui-layout/src/main/java/androidx/ui/layout/Flex.kt
index 4860313..21a9a37 100644
--- a/ui/ui-layout/src/main/java/androidx/ui/layout/Flex.kt
+++ b/ui/ui-layout/src/main/java/androidx/ui/layout/Flex.kt
@@ -178,7 +178,7 @@
mainAxisSize: FlexSize = FlexSize.Max,
crossAxisAlignment: CrossAxisAlignment = CrossAxisAlignment.Center,
crossAxisSize: FlexSize = FlexSize.Min,
- @Children block: @Composable() () -> Unit
+ block: @Composable() () -> Unit
) {
FlexRow(
mainAxisAlignment = mainAxisAlignment,
@@ -214,7 +214,7 @@
mainAxisSize: FlexSize = FlexSize.Max,
crossAxisAlignment: CrossAxisAlignment = CrossAxisAlignment.Center,
crossAxisSize: FlexSize = FlexSize.Min,
- @Children block: @Composable() () -> Unit
+ block: @Composable() () -> Unit
) {
FlexColumn(
mainAxisAlignment = mainAxisAlignment,
diff --git a/ui/ui-layout/src/main/java/androidx/ui/layout/Intrinsic.kt b/ui/ui-layout/src/main/java/androidx/ui/layout/Intrinsic.kt
index 58b8205..3bd2915 100644
--- a/ui/ui-layout/src/main/java/androidx/ui/layout/Intrinsic.kt
+++ b/ui/ui-layout/src/main/java/androidx/ui/layout/Intrinsic.kt
@@ -42,7 +42,7 @@
* the [ConstrainedBox]s to use the same width.
*/
@Composable
-fun MinIntrinsicWidth(@Children children: @Composable() () -> Unit) {
+fun MinIntrinsicWidth(children: @Composable() () -> Unit) {
ComplexLayout(children) {
layout { measurables, constraints ->
val measurable = measurables.firstOrNull()
@@ -87,7 +87,7 @@
* the divider to use the same height.
*/
@Composable
-fun MinIntrinsicHeight(@Children children: @Composable() () -> Unit) {
+fun MinIntrinsicHeight(children: @Composable() () -> Unit) {
ComplexLayout(children) {
layout { measurables, constraints ->
val measurable = measurables.firstOrNull()
@@ -132,7 +132,7 @@
* The sample is a layout containing three widgets having the same width as the widest one.
*/
@Composable
-fun MaxIntrinsicWidth(@Children children: @Composable() () -> Unit) {
+fun MaxIntrinsicWidth(children: @Composable() () -> Unit) {
ComplexLayout(children) {
layout { measurables, constraints ->
val measurable = measurables.firstOrNull()
@@ -177,7 +177,7 @@
* and the divider to use the same height.
*/
@Composable
-fun MaxIntrinsicHeight(@Children children: @Composable() () -> Unit) {
+fun MaxIntrinsicHeight(children: @Composable() () -> Unit) {
ComplexLayout(children) {
layout { measurables, constraints ->
val measurable = measurables.firstOrNull()
diff --git a/ui/ui-layout/src/main/java/androidx/ui/layout/Padding.kt b/ui/ui-layout/src/main/java/androidx/ui/layout/Padding.kt
index 8e8bbe45..79225a6 100644
--- a/ui/ui-layout/src/main/java/androidx/ui/layout/Padding.kt
+++ b/ui/ui-layout/src/main/java/androidx/ui/layout/Padding.kt
@@ -53,7 +53,7 @@
@Composable
fun Padding(
padding: EdgeInsets,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
Layout(layoutBlock = { measurables, constraints ->
val measurable = measurables.firstOrNull()
@@ -98,7 +98,7 @@
top: Dp = 0.dp,
right: Dp = 0.dp,
bottom: Dp = 0.dp,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
Padding(
padding = EdgeInsets(left = left, top = top, right = right, bottom = bottom),
@@ -121,7 +121,7 @@
@Composable
fun Padding(
padding: Dp,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
Padding(padding = EdgeInsets(padding), children = children)
}
diff --git a/ui/ui-layout/src/main/java/androidx/ui/layout/Scroller.kt b/ui/ui-layout/src/main/java/androidx/ui/layout/Scroller.kt
index 69bfe04..677be31 100644
--- a/ui/ui-layout/src/main/java/androidx/ui/layout/Scroller.kt
+++ b/ui/ui-layout/src/main/java/androidx/ui/layout/Scroller.kt
@@ -52,7 +52,7 @@
private fun VerticalDragGestureDetector(
max: Px = Px.Infinity,
offsetChange: (Px) -> Unit,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
val offset = +state { 0.px }
DragGestureDetector(
@@ -105,7 +105,7 @@
onScrollChanged: (position: Px, maxPosition: Px) -> Unit = { position, _ ->
scrollerPosition.position = position
},
- @Children child: @Composable() () -> Unit
+ child: @Composable() () -> Unit
) {
val maxPosition = +state { 0.px }
Layout(children = {
diff --git a/ui/ui-layout/src/main/java/androidx/ui/layout/Table.kt b/ui/ui-layout/src/main/java/androidx/ui/layout/Table.kt
index 3965a5a..1dd6894 100644
--- a/ui/ui-layout/src/main/java/androidx/ui/layout/Table.kt
+++ b/ui/ui-layout/src/main/java/androidx/ui/layout/Table.kt
@@ -32,6 +32,7 @@
import androidx.ui.core.coerceIn
import androidx.ui.core.isFinite
import androidx.ui.core.max
+import androidx.ui.core.min
/**
* Collects information about the children of a [Table] when
@@ -61,27 +62,39 @@
*/
sealed class TableColumnWidth {
/**
- * Sizes the column to be the width of the widest child in that column.
+ * Sizes the column by taking a part of the remaining space according
+ * to [flex] once all the inflexible columns have been measured.
*/
- object Wrap : TableColumnWidth()
+ data class Flexible(internal val flex: Float) : TableColumnWidth()
- /**
- * Sizes the column by taking a part of the remaining space
- * once all the other columns have been measured according to [flex].
- */
- data class Flex(internal val flex: Float) : TableColumnWidth()
+ sealed class Inflexible : TableColumnWidth() {
+ /**
+ * Sizes the column to be the width of the widest child in that column.
+ */
+ object Wrap : Inflexible()
- /**
- * Sizes the column to a specific width.
- */
- data class Fixed(internal val width: Dp) : TableColumnWidth()
+ /**
+ * Sizes the column to a specific width.
+ */
+ data class Fixed(internal val width: Dp) : Inflexible()
- /**
- * Sizes the column to a fraction of the table’s maximum width constraint.
- */
- data class Fraction(
- @FloatRange(from = 0.0, to = 1.0) internal val fraction: Float
- ) : TableColumnWidth()
+ /**
+ * Sizes the column to a fraction of the table’s maximum width constraint.
+ */
+ data class Fraction(
+ @FloatRange(from = 0.0, to = 1.0) internal val fraction: Float
+ ) : Inflexible()
+
+ /**
+ * Sizes the column such that it is the size that is the min of two width specifications.
+ */
+ data class Min(internal val a: Inflexible, internal val b: Inflexible) : Inflexible()
+
+ /**
+ * Sizes the column such that it is the size that is the max of two width specifications.
+ */
+ data class Max(internal val a: Inflexible, internal val b: Inflexible) : Inflexible()
+ }
}
/**
@@ -96,7 +109,7 @@
@Composable
fun Table(
childAlignment: Alignment = Alignment.TopLeft,
- columnWidth: (columnIndex: Int) -> TableColumnWidth = { TableColumnWidth.Flex(1f) },
+ columnWidth: (columnIndex: Int) -> TableColumnWidth = { TableColumnWidth.Flexible(1f) },
@Children(composable = false) block: TableChildren.() -> Unit
) {
val children: @Composable() () -> Unit = with(TableChildren()) {
@@ -111,8 +124,8 @@
// Group the measurables into rows using rowGroup.
val measurables = m.groupBy { it.rowGroup }.values.toTypedArray()
- val rows = measurables.size
- val columns = measurables.map { it.size }.max() ?: 0
+ val rowCount = measurables.size
+ val columnCount = measurables.map { it.size }.max() ?: 0
var totalFlex = 0f
var availableSpace = if (constraints.maxWidth.isFinite()) {
@@ -121,60 +134,80 @@
constraints.minWidth
}
- val rowHeights = Array(rows) { IntPx.Zero }
- val columnWidths = Array(columns) { IntPx.Zero }
+ val rowHeights = Array(rowCount) { IntPx.Zero }
+ val columnWidths = Array(columnCount) { IntPx.Zero }
- val placeables = Array(rows) { arrayOfNulls<Placeable>(columns) }
+ val placeables = Array(rowCount) { arrayOfNulls<Placeable>(columnCount) }
- // Compute widths of non-flex columns.
- for (j in 0 until columns) {
- when (val spec = columnWidth(j)) {
- is TableColumnWidth.Flex -> {
- totalFlex += spec.flex
+ // Compute the actual width of a column for the given specification.
+ fun TableColumnWidth.Inflexible.computeWidth(column: Int): IntPx {
+ return when (this) {
+ is TableColumnWidth.Inflexible.Wrap -> {
+ // Measure children in this column to get their preferred widths.
+ // TODO(calintat): Use minIntrinsicWidth and delay measuring until later.
+ var result = IntPx.Zero
+ for (row in 0 until rowCount) {
+ val p = placeables[row][column]
+ if (p != null) {
+ result = max(result, p.width)
+ } else {
+ val placeable = measurables[row][column].measure(Constraints())
+ placeables[row][column] = placeable
+ result = max(result, placeable.width)
+ }
+ }
+ result
}
- is TableColumnWidth.Fixed -> {
- columnWidths[j] = spec.width.toIntPx()
+ is TableColumnWidth.Inflexible.Fixed -> {
+ this.width.toIntPx()
}
- is TableColumnWidth.Fraction -> {
- columnWidths[j] = if (constraints.maxWidth.isFinite()) {
- constraints.maxWidth * spec.fraction
+ is TableColumnWidth.Inflexible.Fraction -> {
+ if (constraints.maxWidth.isFinite()) {
+ constraints.maxWidth * this.fraction
} else {
IntPx.Zero
}
}
- is TableColumnWidth.Wrap -> {
- // Measure children in intrinsic columns.
- for (i in 0 until rows) {
- val placeable = measurables[i][j].measure(Constraints())
- placeables[i][j] = placeable
- rowHeights[i] = max(rowHeights[i], placeable.height)
- columnWidths[j] = max(columnWidths[j], placeable.width)
- }
+ is TableColumnWidth.Inflexible.Min -> {
+ min(this.a.computeWidth(column), this.b.computeWidth(column))
+ }
+ is TableColumnWidth.Inflexible.Max -> {
+ max(this.a.computeWidth(column), this.b.computeWidth(column))
}
}
- availableSpace -= columnWidths[j]
+ }
+
+ // Compute widths of inflexible columns.
+ for (column in 0 until columnCount) {
+ when (val spec = columnWidth(column)) {
+ is TableColumnWidth.Flexible -> {
+ totalFlex += spec.flex
+ }
+ is TableColumnWidth.Inflexible -> {
+ columnWidths[column] = spec.computeWidth(column)
+ availableSpace -= columnWidths[column]
+ }
+ }
}
availableSpace = availableSpace.coerceAtLeast(IntPx.Zero)
// Compute widths of flex columns.
- for (j in 0 until columns) {
- val spec = columnWidth(j)
- if (spec is TableColumnWidth.Flex) {
- columnWidths[j] = availableSpace * (spec.flex / totalFlex)
+ for (column in 0 until columnCount) {
+ val spec = columnWidth(column)
+ if (spec is TableColumnWidth.Flexible) {
+ columnWidths[column] = availableSpace * (spec.flex / totalFlex)
}
}
- // Measure the remaining children.
- for (i in 0 until rows) {
- for (j in 0 until columns) {
- if (placeables[i][j] == null) {
- val placeable = measurables[i][j].measure(
- Constraints(maxWidth = columnWidths[j])
- )
- placeables[i][j] = placeable
- rowHeights[i] = max(rowHeights[i], placeable.height)
+ // Measure the remaining children and calculate row heights.
+ for (row in 0 until rowCount) {
+ for (column in 0 until columnCount) {
+ if (placeables[row][column] == null) {
+ placeables[row][column] = measurables[row][column].measure(
+ Constraints(minWidth = IntPx.Zero, maxWidth = columnWidths[column]))
}
+ rowHeights[row] = max(rowHeights[row], placeables[row][column]!!.height)
}
}
@@ -183,18 +216,18 @@
val tableHeight = rowHeights.sum().coerceIn(constraints.minHeight, constraints.maxHeight)
layout(tableWidth, tableHeight) {
- for (i in 0 until rows) {
- for (j in 0 until columns) {
- val placeable = placeables[i][j]!!
+ for (row in 0 until rowCount) {
+ for (column in 0 until columnCount) {
+ val placeable = placeables[row][column]!!
val position = childAlignment.align(
IntPxSize(
- width = columnWidths[j] - placeable.width,
- height = rowHeights[i] - placeable.height
+ width = columnWidths[column] - placeable.width,
+ height = rowHeights[row] - placeable.height
)
)
placeable.place(
- x = columnWidths.take(j).sum() + position.x,
- y = rowHeights.take(i).sum() + position.y
+ x = columnWidths.take(column).sum() + position.x,
+ y = rowHeights.take(row).sum() + position.y
)
}
}
@@ -202,5 +235,5 @@
}
}
-private fun Array<IntPx>.sum() = this.fold(IntPx.Zero) { a, b -> a + b }
-private fun Collection<IntPx>.sum() = this.fold(IntPx.Zero) { a, b -> a + b }
+internal fun Array<IntPx>.sum() = this.fold(IntPx.Zero) { a, b -> a + b }
+internal fun Collection<IntPx>.sum() = this.fold(IntPx.Zero) { a, b -> a + b }
\ No newline at end of file
diff --git a/ui/ui-layout/src/main/java/androidx/ui/layout/Wrap.kt b/ui/ui-layout/src/main/java/androidx/ui/layout/Wrap.kt
index 5a08cf8..3846114 100644
--- a/ui/ui-layout/src/main/java/androidx/ui/layout/Wrap.kt
+++ b/ui/ui-layout/src/main/java/androidx/ui/layout/Wrap.kt
@@ -35,7 +35,7 @@
* For a widget that does alignment and tries to be as large as possible, see [Align].
*/
@Composable
-fun Wrap(alignment: Alignment = Alignment.TopLeft, @Children children: @Composable() () -> Unit) {
+fun Wrap(alignment: Alignment = Alignment.TopLeft, children: @Composable() () -> Unit) {
Layout(layoutBlock = { measurables, constraints ->
val measurable = measurables.firstOrNull()
// The child cannot be larger than our max constraints, but we ignore min constraints.
diff --git a/ui/ui-material/api/1.0.0-alpha01.txt b/ui/ui-material/api/1.0.0-alpha01.txt
index eba63b6..df0d16a 100644
--- a/ui/ui-material/api/1.0.0-alpha01.txt
+++ b/ui/ui-material/api/1.0.0-alpha01.txt
@@ -240,6 +240,14 @@
}
+package androidx.ui.material.internal {
+
+ public final class AnchoredControllerByStateKt {
+ ctor public AnchoredControllerByStateKt();
+ }
+
+}
+
package androidx.ui.material.ripple {
public final class DefaultRippleEffectFactory implements androidx.ui.material.ripple.RippleEffectFactory {
diff --git a/ui/ui-material/api/current.txt b/ui/ui-material/api/current.txt
index eba63b6..df0d16a 100644
--- a/ui/ui-material/api/current.txt
+++ b/ui/ui-material/api/current.txt
@@ -240,6 +240,14 @@
}
+package androidx.ui.material.internal {
+
+ public final class AnchoredControllerByStateKt {
+ ctor public AnchoredControllerByStateKt();
+ }
+
+}
+
package androidx.ui.material.ripple {
public final class DefaultRippleEffectFactory implements androidx.ui.material.ripple.RippleEffectFactory {
diff --git a/ui/ui-material/api/restricted_1.0.0-alpha01.txt b/ui/ui-material/api/restricted_1.0.0-alpha01.txt
index eba63b6..df0d16a 100644
--- a/ui/ui-material/api/restricted_1.0.0-alpha01.txt
+++ b/ui/ui-material/api/restricted_1.0.0-alpha01.txt
@@ -240,6 +240,14 @@
}
+package androidx.ui.material.internal {
+
+ public final class AnchoredControllerByStateKt {
+ ctor public AnchoredControllerByStateKt();
+ }
+
+}
+
package androidx.ui.material.ripple {
public final class DefaultRippleEffectFactory implements androidx.ui.material.ripple.RippleEffectFactory {
diff --git a/ui/ui-material/api/restricted_current.txt b/ui/ui-material/api/restricted_current.txt
index eba63b6..df0d16a 100644
--- a/ui/ui-material/api/restricted_current.txt
+++ b/ui/ui-material/api/restricted_current.txt
@@ -240,6 +240,14 @@
}
+package androidx.ui.material.internal {
+
+ public final class AnchoredControllerByStateKt {
+ ctor public AnchoredControllerByStateKt();
+ }
+
+}
+
package androidx.ui.material.ripple {
public final class DefaultRippleEffectFactory implements androidx.ui.material.ripple.RippleEffectFactory {
diff --git a/ui/ui-material/build.gradle b/ui/ui-material/build.gradle
index 0392547..a6cacda 100644
--- a/ui/ui-material/build.gradle
+++ b/ui/ui-material/build.gradle
@@ -42,6 +42,7 @@
implementation project(":ui:ui-text")
implementation project(":ui:ui-animation")
implementation project(":ui:ui-foundation")
+ implementation project(":ui:ui-vector")
testImplementation(ANDROIDX_TEST_RULES)
testImplementation(ANDROIDX_TEST_RUNNER)
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/SelectionControlsActivity.kt b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/SelectionControlsActivity.kt
index a0eaee6..ded34be 100644
--- a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/SelectionControlsActivity.kt
+++ b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/SelectionControlsActivity.kt
@@ -157,11 +157,11 @@
val (checked, onChecked) = +state { false }
val (checked2, onChecked2) = +state { false }
val (checked3, onChecked3) = +state { true }
- val (checked4, onChecked4) = +state { true }
+ val (checked4, _) = +state { true }
Switch(checked = checked, onCheckedChange = onChecked)
Switch(checked = checked2, onCheckedChange = onChecked2, color = customColor)
Switch(checked = checked3, onCheckedChange = onChecked3, color = customColor2)
- Switch(checked = checked4, onCheckedChange = onChecked4, color = customColor3)
+ Switch(checked = checked4, onCheckedChange = {}, color = customColor3)
}
}
@@ -177,4 +177,4 @@
RadioButton(selected = false, color = customColor, onSelect = null)
}
}
-}
+}
\ No newline at end of file
diff --git a/ui/ui-material/integration-tests/material-studies/src/main/java/androidx/ui/material/studies/MissingMaterialComponents.kt b/ui/ui-material/integration-tests/material-studies/src/main/java/androidx/ui/material/studies/MissingMaterialComponents.kt
index bc83b65..ff66edf 100644
--- a/ui/ui-material/integration-tests/material-studies/src/main/java/androidx/ui/material/studies/MissingMaterialComponents.kt
+++ b/ui/ui-material/integration-tests/material-studies/src/main/java/androidx/ui/material/studies/MissingMaterialComponents.kt
@@ -31,7 +31,7 @@
*/
@Composable
-fun Scaffold(appBar: @Composable() () -> Unit, @Children children: @Composable() () -> Unit) {
+fun Scaffold(appBar: @Composable() () -> Unit, children: @Composable() () -> Unit) {
FlexColumn {
inflexible {
appBar()
diff --git a/ui/ui-material/integration-tests/material-studies/src/main/java/androidx/ui/material/studies/rally/RallyTheme.kt b/ui/ui-material/integration-tests/material-studies/src/main/java/androidx/ui/material/studies/rally/RallyTheme.kt
index e80124f..f1d3435 100644
--- a/ui/ui-material/integration-tests/material-studies/src/main/java/androidx/ui/material/studies/rally/RallyTheme.kt
+++ b/ui/ui-material/integration-tests/material-studies/src/main/java/androidx/ui/material/studies/rally/RallyTheme.kt
@@ -36,7 +36,7 @@
val rallyBlue = Color(0xFF72DEFF.toInt())
@Composable
-fun RallyTheme(@Children children: @Composable() () -> Unit) {
+fun RallyTheme(children: @Composable() () -> Unit) {
val colors = MaterialColors(
primary = rallyGreen,
surface = Color(0xFF26282F.toInt()),
diff --git a/ui/ui-material/src/androidTest/java/androidx/ui/material/AppBarTest.kt b/ui/ui-material/src/androidTest/java/androidx/ui/material/AppBarTest.kt
index de0c637..5aa2aff 100644
--- a/ui/ui-material/src/androidTest/java/androidx/ui/material/AppBarTest.kt
+++ b/ui/ui-material/src/androidTest/java/androidx/ui/material/AppBarTest.kt
@@ -27,7 +27,6 @@
import androidx.ui.core.LayoutCoordinates
import androidx.ui.core.OnChildPositioned
import androidx.ui.core.PxPosition
-import androidx.ui.core.Semantics
import androidx.ui.core.Text
import androidx.ui.core.currentTextStyle
import androidx.ui.core.ipx
@@ -35,11 +34,13 @@
import androidx.ui.core.toPx
import androidx.ui.foundation.ColoredRect
import androidx.ui.graphics.Color
+import androidx.ui.semantics.Semantics
+import androidx.ui.semantics.testTag
import androidx.ui.text.TextStyle
import androidx.ui.test.assertCountEquals
import androidx.ui.test.assertIsVisible
import androidx.ui.test.createComposeRule
-import androidx.ui.test.findAll
+import androidx.ui.test.findAllByTag
import androidx.ui.test.findByText
import org.junit.Rule
import org.junit.Test
@@ -183,13 +184,15 @@
TopAppBar(
contextualActions = createImageList(numberOfActions),
action = { action ->
- Semantics(testTag = tag) { action() }
+ Semantics(properties = { testTag = tag }) {
+ action()
+ }
}
)
}
}
- findAll { testTag == tag }.assertCountEquals(numberOfActions)
+ findAllByTag(tag).assertCountEquals(numberOfActions)
}
@Test
@@ -202,13 +205,15 @@
TopAppBar(
contextualActions = createImageList(numberOfActions),
action = { action ->
- Semantics(testTag = tag) { action() }
+ Semantics(properties = { testTag = tag }) {
+ action()
+ }
}
)
}
}
- findAll { testTag == tag }.assertCountEquals(maxNumberOfActions)
+ findAllByTag(tag).assertCountEquals(maxNumberOfActions)
}
@Test
@@ -423,13 +428,15 @@
BottomAppBar(
contextualActions = createImageList(numberOfActions),
action = { action ->
- Semantics(testTag = tag) { action() }
+ Semantics(properties = { testTag = tag }) {
+ action()
+ }
}
)
}
}
- findAll { testTag == tag }.assertCountEquals(numberOfActions)
+ findAllByTag(tag).assertCountEquals(numberOfActions)
}
@Test
@@ -442,13 +449,15 @@
BottomAppBar(
contextualActions = createImageList(numberOfActions),
action = { action ->
- Semantics(testTag = tag) { action() }
+ Semantics(properties = { testTag = tag }) {
+ action()
+ }
}
)
}
}
- findAll { testTag == tag }.assertCountEquals(maxNumberOfActions)
+ findAllByTag(tag).assertCountEquals(maxNumberOfActions)
}
private fun createImageList(count: Int) =
diff --git a/ui/ui-material/src/androidTest/java/androidx/ui/material/ButtonUiTest.kt b/ui/ui-material/src/androidTest/java/androidx/ui/material/ButtonUiTest.kt
index fb9cc63..6d0be1c 100644
--- a/ui/ui-material/src/androidTest/java/androidx/ui/material/ButtonUiTest.kt
+++ b/ui/ui-material/src/androidTest/java/androidx/ui/material/ButtonUiTest.kt
@@ -46,8 +46,7 @@
val composeTestRule = createComposeRule()
private val defaultButtonSemantics = createFullSemantics(
- isEnabled = true,
- isButton = true
+ isEnabled = true
)
@Test
@@ -79,8 +78,7 @@
findByTag("myButton")
.assertSemanticsIsEqualTo(
createFullSemantics(
- isEnabled = false,
- isButton = true
+ isEnabled = false
)
)
}
diff --git a/ui/ui-material/src/androidTest/java/androidx/ui/material/CheckboxUiTest.kt b/ui/ui-material/src/androidTest/java/androidx/ui/material/CheckboxUiTest.kt
index cf168d3..376e7d8 100644
--- a/ui/ui-material/src/androidTest/java/androidx/ui/material/CheckboxUiTest.kt
+++ b/ui/ui-material/src/androidTest/java/androidx/ui/material/CheckboxUiTest.kt
@@ -21,20 +21,22 @@
import androidx.test.filters.MediumTest
import androidx.ui.core.TestTag
import androidx.ui.core.dp
+import androidx.ui.foundation.Strings
import androidx.ui.foundation.selection.ToggleableState
import androidx.ui.foundation.selection.ToggleableState.Checked
import androidx.ui.foundation.selection.ToggleableState.Indeterminate
import androidx.ui.foundation.selection.ToggleableState.Unchecked
+import androidx.ui.foundation.semantics.toggleableState
import androidx.ui.layout.Column
+import androidx.ui.semantics.accessibilityValue
import androidx.ui.test.assertIsChecked
-import androidx.ui.test.assertIsNotChecked
+import androidx.ui.test.assertIsUnchecked
import androidx.ui.test.assertSemanticsIsEqualTo
import androidx.ui.test.copyWith
import androidx.ui.test.createComposeRule
import androidx.ui.test.createFullSemantics
import androidx.ui.test.doClick
import androidx.ui.test.findByTag
-import com.google.common.truth.Truth
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -50,11 +52,13 @@
// TODO(b/126881459): this should be the default semantic for checkbox
private val defaultCheckboxCheckedSemantics = createFullSemantics(
isEnabled = true,
- isChecked = true
+ value = Strings.Checked,
+ toggleableState = Checked
)
private val defaultCheckboxUncheckedSemantics = defaultCheckboxCheckedSemantics.copyWith {
- isChecked = false
+ accessibilityValue = Strings.Unchecked
+ toggleableState = Unchecked
}
private val defaultTag = "myCheckbox"
@@ -89,7 +93,7 @@
}
findByTag(defaultTag)
- .assertIsNotChecked()
+ .assertIsUnchecked()
.doClick()
.assertIsChecked()
}
@@ -104,11 +108,11 @@
}
findByTag(defaultTag)
- .assertIsNotChecked()
+ .assertIsUnchecked()
.doClick()
.assertIsChecked()
.doClick()
- .assertIsNotChecked()
+ .assertIsUnchecked()
}
@Test
@@ -122,9 +126,9 @@
}
findByTag(defaultTag)
- .assertIsNotChecked()
+ .assertIsUnchecked()
.doClick()
- .assertIsNotChecked()
+ .assertIsUnchecked()
}
@Test
diff --git a/ui/ui-material/src/androidTest/java/androidx/ui/material/RadioGroupUiTest.kt b/ui/ui-material/src/androidTest/java/androidx/ui/material/RadioGroupUiTest.kt
index d43cd7a..378179d 100644
--- a/ui/ui-material/src/androidTest/java/androidx/ui/material/RadioGroupUiTest.kt
+++ b/ui/ui-material/src/androidTest/java/androidx/ui/material/RadioGroupUiTest.kt
@@ -17,13 +17,14 @@
package androidx.ui.material
import androidx.compose.composer
-import androidx.compose.Children
import androidx.compose.Composable
import androidx.compose.Model
import androidx.test.filters.MediumTest
import androidx.ui.core.TestTag
import androidx.ui.core.dp
+import androidx.ui.foundation.Strings
import androidx.ui.layout.Column
+import androidx.ui.semantics.accessibilityValue
import androidx.ui.test.assertIsInMutuallyExclusiveGroup
import androidx.ui.test.assertIsSelected
import androidx.ui.test.assertSemanticsIsEqualTo
@@ -32,8 +33,7 @@
import androidx.ui.test.createFullSemantics
import androidx.ui.test.doClick
import androidx.ui.test.findByTag
-import androidx.ui.test.assertIsNotSelected
-import com.google.common.truth.Truth
+import androidx.ui.test.assertIsUnselected
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -54,16 +54,16 @@
private val itemThree = "Sap"
private val unselectedRadioGroupItemSemantics = createFullSemantics(
- inMutuallyExclusiveGroup = true,
- isSelected = false
+ value = Strings.NotSelected
)
+
private val selectedRadioGroupItemSemantics = unselectedRadioGroupItemSemantics.copyWith {
- isSelected = true
+ accessibilityValue = Strings.Selected
}
private val options = listOf(itemOne, itemTwo, itemThree)
@Composable
- fun VerticalRadioGroupforTests(@Children children: @Composable() RadioGroupScope.() -> Unit) {
+ fun VerticalRadioGroupforTests(children: @Composable() RadioGroupScope.() -> Unit) {
RadioGroup {
Column {
children(p1 = this)
@@ -97,10 +97,10 @@
.assertIsSelected()
findByTag(itemTwo)
.assertIsInMutuallyExclusiveGroup()
- .assertIsNotSelected()
+ .assertIsUnselected()
findByTag(itemThree)
.assertIsInMutuallyExclusiveGroup()
- .assertIsNotSelected()
+ .assertIsUnselected()
}
@Test
diff --git a/ui/ui-material/src/androidTest/java/androidx/ui/material/RippleEffectTest.kt b/ui/ui-material/src/androidTest/java/androidx/ui/material/RippleEffectTest.kt
index d7707a2..173bb9f 100644
--- a/ui/ui-material/src/androidTest/java/androidx/ui/material/RippleEffectTest.kt
+++ b/ui/ui-material/src/androidTest/java/androidx/ui/material/RippleEffectTest.kt
@@ -192,7 +192,7 @@
private fun RippleCallback(
onRippleDrawn: (Matrix4) -> Unit = {},
onDispose: () -> Unit = {},
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
val theme = RippleTheme(testRippleEffect(onRippleDrawn, onDispose)) { Color(0) }
CurrentRippleTheme.Provider(value = theme, children = children)
diff --git a/ui/ui-material/src/androidTest/java/androidx/ui/material/SwitchUiTest.kt b/ui/ui-material/src/androidTest/java/androidx/ui/material/SwitchUiTest.kt
index 855bf30..7d4afcf 100644
--- a/ui/ui-material/src/androidTest/java/androidx/ui/material/SwitchUiTest.kt
+++ b/ui/ui-material/src/androidTest/java/androidx/ui/material/SwitchUiTest.kt
@@ -22,9 +22,13 @@
import androidx.test.filters.MediumTest
import androidx.ui.core.TestTag
import androidx.ui.core.dp
+import androidx.ui.foundation.Strings
+import androidx.ui.foundation.selection.ToggleableState
+import androidx.ui.foundation.semantics.toggleableState
import androidx.ui.layout.Column
+import androidx.ui.semantics.accessibilityValue
import androidx.ui.test.assertIsChecked
-import androidx.ui.test.assertIsNotChecked
+import androidx.ui.test.assertIsUnchecked
import androidx.ui.test.assertSemanticsIsEqualTo
import androidx.ui.test.copyWith
import androidx.ui.test.createComposeRule
@@ -45,10 +49,13 @@
private val defaultUncheckedSwitchSemantics = createFullSemantics(
isEnabled = true,
- isChecked = false
+ toggleableState = ToggleableState.Unchecked,
+ value = Strings.Unchecked // TODO(a11y): Do we still call this checked/unchecked?
)
+
private val defaultCheckedSwitchSemantics = defaultUncheckedSwitchSemantics.copyWith {
- isChecked = true
+ toggleableState = ToggleableState.Checked
+ accessibilityValue = Strings.Checked
}
private val defaultSwitchTag = "switch"
@@ -78,7 +85,7 @@
}
}
findByTag(defaultSwitchTag)
- .assertIsNotChecked()
+ .assertIsUnchecked()
.doClick()
.assertIsChecked()
}
@@ -93,11 +100,11 @@
}
}
findByTag(defaultSwitchTag)
- .assertIsNotChecked()
+ .assertIsUnchecked()
.doClick()
.assertIsChecked()
.doClick()
- .assertIsNotChecked()
+ .assertIsUnchecked()
}
@Test
@@ -109,9 +116,9 @@
}
}
findByTag(defaultSwitchTag)
- .assertIsNotChecked()
+ .assertIsUnchecked()
.doClick()
- .assertIsNotChecked()
+ .assertIsUnchecked()
}
@Test
diff --git a/ui/ui-material/src/androidTest/java/androidx/ui/material/TabTest.kt b/ui/ui-material/src/androidTest/java/androidx/ui/material/TabTest.kt
index b4b1c5c..ff7b033 100644
--- a/ui/ui-material/src/androidTest/java/androidx/ui/material/TabTest.kt
+++ b/ui/ui-material/src/androidTest/java/androidx/ui/material/TabTest.kt
@@ -35,11 +35,12 @@
import androidx.ui.painting.Image
import androidx.ui.painting.ImageConfig
import androidx.ui.test.assertCountEquals
-import androidx.ui.test.assertIsNotSelected
+import androidx.ui.test.assertIsUnselected
import androidx.ui.test.assertIsSelected
import androidx.ui.test.createComposeRule
import androidx.ui.test.doClick
import androidx.ui.test.findAll
+import androidx.ui.test.isInMutuallyExclusiveGroup
import com.google.common.truth.Truth
import org.junit.Rule
import org.junit.Test
@@ -182,7 +183,7 @@
if (index == 0) {
interaction.assertIsSelected()
} else {
- interaction.assertIsNotSelected()
+ interaction.assertIsUnselected()
}
}
}.assertCountEquals(3)
@@ -201,7 +202,7 @@
if (index == 0) {
interaction.assertIsSelected()
} else {
- interaction.assertIsNotSelected()
+ interaction.assertIsUnselected()
}
}
}.assertCountEquals(3)
@@ -215,7 +216,7 @@
if (index == lastIndex) {
interaction.assertIsSelected()
} else {
- interaction.assertIsNotSelected()
+ interaction.assertIsUnselected()
}
}
}.assertCountEquals(3)
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/AppBar.kt b/ui/ui-material/src/main/java/androidx/ui/material/AppBar.kt
index 84652bd..1f2b907 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/AppBar.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/AppBar.kt
@@ -20,7 +20,6 @@
import androidx.compose.composer
import androidx.compose.unaryPlus
import androidx.ui.core.CurrentTextStyleProvider
-import androidx.ui.core.Semantics
import androidx.ui.core.Text
import androidx.ui.core.dp
import androidx.ui.core.sp
@@ -46,6 +45,7 @@
import androidx.ui.material.BottomAppBar.FabPosition.End
import androidx.ui.material.ripple.Ripple
import androidx.ui.painting.Image
+import androidx.ui.semantics.Semantics
import androidx.ui.text.TextStyle
/**
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/Button.kt b/ui/ui-material/src/main/java/androidx/ui/material/Button.kt
index 9ece28d..5137a35 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/Button.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/Button.kt
@@ -74,7 +74,7 @@
color: Color = +themeColor { primary },
border: Border? = null,
elevation: Dp = 0.dp,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
val textStyle = +themeTextStyle { button }
Surface(shape = shape, color = color, border = border, elevation = elevation) {
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/Drawer.kt b/ui/ui-material/src/main/java/androidx/ui/material/Drawer.kt
index 557ee14..547149d8 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/Drawer.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/Drawer.kt
@@ -19,8 +19,10 @@
import androidx.animation.PhysicsBuilder
import androidx.compose.Composable
import androidx.compose.composer
+import androidx.compose.memo
import androidx.compose.onCommit
import androidx.compose.unaryPlus
+import androidx.ui.animation.animatedFloat
import androidx.ui.core.Dp
import androidx.ui.core.IntPx
import androidx.ui.core.Layout
@@ -34,9 +36,11 @@
import androidx.ui.core.withDensity
import androidx.ui.foundation.Clickable
import androidx.ui.foundation.ColoredRect
-import androidx.ui.foundation.gestures.AnchorsFlingConfig
-import androidx.ui.foundation.gestures.AnimatedDraggable
+import androidx.ui.foundation.animation.AnchorsFlingConfig
+import androidx.ui.foundation.gestures.Draggable
+import androidx.ui.foundation.animation.AnimatedFloatDragController
import androidx.ui.foundation.gestures.DragDirection
+import androidx.ui.foundation.gestures.DraggableCallback
import androidx.ui.layout.Alignment
import androidx.ui.layout.Container
import androidx.ui.layout.DpConstraints
@@ -120,32 +124,26 @@
val maxValue = 0f
val valueByState = if (drawerState == DrawerState.Opened) maxValue else minValue
- val flingConfig = AnchorsFlingConfig(
- listOf(minValue, maxValue),
- onAnimationFinished = { finalValue, cancelled ->
- if (!cancelled) {
- onStateChange(
- if (finalValue <= minValue) DrawerState.Closed else DrawerState.Opened
- )
- }
- },
- animationBuilder = AnimationBuilder
- )
+ val callback = DraggableCallback(onDragSettled = {
+ onStateChange(if (it <= minValue) DrawerState.Closed else DrawerState.Opened)
+ })
+ val flingConfig = AnchorsFlingConfig(listOf(minValue, maxValue), AnimationBuilder)
+ val controller = +memo { AnimatedFloatDragController(valueByState, flingConfig) }
+ +onCommit(valueByState) {
+ controller.animatedFloat.animateTo(valueByState, AnimationBuilder)
+ }
- AnimatedDraggable(
+ Draggable(
dragDirection = DragDirection.Horizontal,
- startValue = valueByState,
minValue = minValue,
maxValue = maxValue,
- flingConfig = flingConfig
- ) { animatedValue ->
- +onCommit(valueByState) {
- animatedValue.animateTo(valueByState, AnimationBuilder)
- }
- val fraction = calculateFraction(minValue, maxValue, animatedValue.value)
+ valueController = controller,
+ callback = callback
+ ) { value ->
+ val fraction = calculateFraction(minValue, maxValue, value)
val scrimAlpha = fraction * ScrimDefaultOpacity
val dpOffset = +withDensity {
- animatedValue.value.toDp()
+ value.toDp()
}
Stack {
@@ -208,31 +206,30 @@
)
val valueByState = if (drawerState == DrawerState.Opened) openedValue else maxValue
val anchors = listOf(minValue, maxValue, openedValue)
+ val callback = DraggableCallback(onDragSettled = {
+ onStateChange(if (it >= maxValue) DrawerState.Closed else DrawerState.Opened)
+ })
- val onAnimationFinished = { finalValue: Float, cancelled: Boolean ->
- if (!cancelled) {
- onStateChange(
- if (finalValue >= maxValue) DrawerState.Closed else DrawerState.Opened
- )
- }
+ val flingConfig = AnchorsFlingConfig(anchors, AnimationBuilder)
+
+ val controller = +memo { AnimatedFloatDragController(valueByState, flingConfig) }
+ +onCommit(valueByState) {
+ controller.animatedFloat.animateTo(valueByState, AnimationBuilder)
}
- AnimatedDraggable(
+ Draggable(
dragDirection = DragDirection.Vertical,
- startValue = valueByState,
minValue = minValue,
maxValue = maxValue,
- flingConfig = AnchorsFlingConfig(anchors, onAnimationFinished, AnimationBuilder)
- ) { animatedValue ->
- +onCommit(valueByState) {
- animatedValue.animateTo(valueByState, AnimationBuilder)
- }
+ valueController = controller,
+ callback = callback
+ ) { value ->
// as we scroll "from height to 0" backwards, (1 - fraction) will reverse it
val fractionToOpened =
- 1 - max(0f, calculateFraction(openedValue, maxValue, animatedValue.value))
+ 1 - max(0f, calculateFraction(openedValue, maxValue, value))
val scrimAlpha = fractionToOpened * ScrimDefaultOpacity
val dpOffset = +withDensity {
- animatedValue.value.toDp()
+ value.toDp()
}
Stack {
aligned(Alignment.TopLeft) {
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/FloatingActionButton.kt b/ui/ui-material/src/main/java/androidx/ui/material/FloatingActionButton.kt
index 3ce7acf..019eb31 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/FloatingActionButton.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/FloatingActionButton.kt
@@ -64,7 +64,7 @@
shape: Shape = CircleShape,
color: Color = +themeColor { primary },
elevation: Dp = 0.dp, // TODO(Andrey) add the default elevation when it ready b/123215187
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
Button(color = color, onClick = onClick, shape = shape, elevation = elevation) {
Container(constraints = DpConstraints(minWidth = minSize, minHeight = minSize)) {
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/MaterialTheme.kt b/ui/ui-material/src/main/java/androidx/ui/material/MaterialTheme.kt
index 296208b..76ce952 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/MaterialTheme.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/MaterialTheme.kt
@@ -57,7 +57,6 @@
fun MaterialTheme(
colors: MaterialColors = MaterialColors(),
typography: MaterialTypography = MaterialTypography(),
- @Children
children: @Composable() () -> Unit
) {
Colors.Provider(value = colors) {
@@ -222,7 +221,7 @@
* guidelines for descendants
*/
@Composable
-fun MaterialRippleTheme(@Children children: @Composable() () -> Unit) {
+fun MaterialRippleTheme(children: @Composable() () -> Unit) {
val materialColors = +ambient(Colors)
val defaultTheme = +memo {
RippleTheme(
@@ -287,7 +286,7 @@
* Applies the default [Shape]s for all the surfaces.
*/
@Composable
-fun MaterialButtonShapeTheme(@Children children: @Composable() () -> Unit) {
+fun MaterialButtonShapeTheme(children: @Composable() () -> Unit) {
val value = +withDensity {
Shapes(
button = RoundedCornerShape(CornerSizes(4.dp))
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/ProgressIndicator.kt b/ui/ui-material/src/main/java/androidx/ui/material/ProgressIndicator.kt
index a8485a0..e2da5bc 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/ProgressIndicator.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/ProgressIndicator.kt
@@ -282,7 +282,7 @@
}
@Composable
-private fun CircularIndicatorContainer(@Children children: @Composable() () -> Unit) {
+private fun CircularIndicatorContainer(children: @Composable() () -> Unit) {
Wrap {
Padding(CircularIndicatorPadding) {
Container(width = CircularIndicatorDiameter, height = CircularIndicatorDiameter) {
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/RadioButton.kt b/ui/ui-material/src/main/java/androidx/ui/material/RadioButton.kt
index c3bbe64..ae0f300 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/RadioButton.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/RadioButton.kt
@@ -76,7 +76,7 @@
* onOptionSelected = { ... })
*/
@Composable
-fun RadioGroup(@Children children: @Composable RadioGroupScope.() -> Unit) {
+fun RadioGroup(children: @Composable RadioGroupScope.() -> Unit) {
val scope = +memo { RadioGroupScope() }
children(p1 = scope)
}
@@ -142,7 +142,7 @@
fun RadioGroupItem(
selected: Boolean,
onSelect: () -> Unit,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
Container {
Ripple(bounded = true) {
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/Switch.kt b/ui/ui-material/src/main/java/androidx/ui/material/Switch.kt
index 7654959..da22e0f 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/Switch.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/Switch.kt
@@ -16,27 +16,26 @@
package androidx.ui.material
-import androidx.animation.ColorPropKey
-import androidx.animation.FastOutSlowInEasing
-import androidx.animation.FloatPropKey
-import androidx.animation.TransitionSpec
-import androidx.animation.transitionDefinition
+import androidx.animation.TweenBuilder
import androidx.compose.Composable
import androidx.compose.composer
-import androidx.compose.memo
import androidx.compose.unaryPlus
-import androidx.ui.animation.Transition
import androidx.ui.core.DensityReceiver
import androidx.ui.core.Draw
import androidx.ui.core.PxSize
import androidx.ui.core.dp
+import androidx.ui.core.px
+import androidx.ui.core.withDensity
import androidx.ui.engine.geometry.Offset
+import androidx.ui.foundation.gestures.DragDirection
+import androidx.ui.foundation.gestures.Draggable
import androidx.ui.foundation.selection.Toggleable
import androidx.ui.foundation.selection.ToggleableState
import androidx.ui.graphics.Color
import androidx.ui.layout.Container
import androidx.ui.layout.Padding
import androidx.ui.layout.Wrap
+import androidx.ui.material.internal.anchoredControllerByState
import androidx.ui.material.ripple.Ripple
import androidx.ui.painting.Canvas
import androidx.ui.painting.Paint
@@ -63,9 +62,7 @@
Ripple(bounded = false) {
Toggleable(value = value, onToggle = onCheckedChange?.let { { it(!checked) } }) {
Padding(padding = DefaultSwitchPadding) {
- Container(width = SwitchWidth, height = SwitchHeight) {
- DrawSwitch(checked = checked, checkedThumbColor = color)
- }
+ SwitchImpl(checked, onCheckedChange, color)
}
}
}
@@ -73,49 +70,58 @@
}
@Composable
-private fun DrawSwitch(checked: Boolean, checkedThumbColor: Color) {
- val uncheckedThumbColor = +themeColor { surface }
- val transDef = +memo(checkedThumbColor, uncheckedThumbColor) {
- generateTransitionDefinition(checkedThumbColor, uncheckedThumbColor)
+private fun SwitchImpl(checked: Boolean, onCheckedChange: ((Boolean) -> Unit)?, color: Color) {
+ val minBound = 0f
+ val maxBound = +withDensity { ThumbPathLength.toPx().value }
+ val (controller, callback) =
+ +anchoredControllerByState(
+ state = checked,
+ onStateChange = onCheckedChange ?: {},
+ anchorsToState = listOf(minBound to false, maxBound to true),
+ animationBuilder = AnimationBuilder
+ )
+ Draggable(
+ dragDirection = DragDirection.Horizontal,
+ minValue = minBound,
+ maxValue = maxBound,
+ valueController = controller,
+ callback = callback
+ ) { thumbPosition ->
+ Container(width = SwitchWidth, height = SwitchHeight, expanded = true) {
+ DrawSwitch(
+ checked = checked,
+ checkedThumbColor = color,
+ thumbPosition = thumbPosition
+ )
+ }
}
+}
+
+@Composable
+private fun DrawSwitch(checked: Boolean, checkedThumbColor: Color, thumbPosition: Float) {
+ val thumbColor = if (checked) checkedThumbColor else +themeColor { surface }
val trackColor = if (checked) {
checkedThumbColor.copy(alpha = CheckedTrackOpacity)
} else {
(+themeColor { onSurface }).copy(alpha = UncheckedTrackOpacity)
}
- DrawTrack(color = trackColor)
- Transition(definition = transDef, toState = checked) { state ->
- DrawThumb(
- color = state[ThumbColorProp],
- relativePosition = state[RelativeThumbTranslationProp]
- )
- }
-}
-
-@Composable
-private fun DrawTrack(color: Color) {
Draw { canvas, parentSize ->
- drawTrack(canvas, parentSize, color)
- }
-}
-
-@Composable
-private fun DrawThumb(relativePosition: Float, color: Color) {
- Draw { canvas, parentSize ->
- drawThumb(canvas, parentSize, relativePosition, color)
+ drawTrack(canvas, parentSize, trackColor)
+ drawThumb(canvas, parentSize, thumbPosition, thumbColor)
}
}
private fun DensityReceiver.drawTrack(
canvas: Canvas,
parentSize: PxSize,
- color: Color
+ trackColor: Color
) {
- val paint = Paint()
- paint.isAntiAlias = true
- paint.color = color
- paint.strokeCap = StrokeCap.round
- paint.strokeWidth = TrackStrokeWidth.toPx().value
+ val paint = Paint().apply {
+ isAntiAlias = true
+ color = trackColor
+ strokeCap = StrokeCap.round
+ strokeWidth = TrackStrokeWidth.toPx().value
+ }
val strokeRadius = TrackStrokeWidth / 2
val centerHeight = parentSize.height / 2
@@ -130,50 +136,19 @@
private fun DensityReceiver.drawThumb(
canvas: Canvas,
parentSize: PxSize,
- relativePosition: Float,
- color: Color
+ position: Float,
+ thumbColor: Color
) {
- val paint = Paint()
- paint.isAntiAlias = true
- paint.color = color
-
- val centerHeight = parentSize.height / 2
- val thumbRadius = ThumbDiameter / 2
- val thumbPathWidth = TrackWidth - ThumbDiameter
-
- val start = thumbRadius + thumbPathWidth * relativePosition
- canvas.drawCircle(
- Offset(start.toPx().value, centerHeight.value),
- // TODO(malkov): wierd +1 but necessary in order to properly cover track. Investigate
- thumbRadius.toPx().value + 1,
- paint
- )
-}
-
-private val RelativeThumbTranslationProp = FloatPropKey()
-private val ThumbColorProp = ColorPropKey()
-private const val SwitchAnimationDuration = 100
-
-private fun generateTransitionDefinition(checkedColor: Color, uncheckedColor: Color) =
- transitionDefinition {
- fun <T> TransitionSpec<Boolean>.switchTween() = tween<T> {
- duration = SwitchAnimationDuration
- easing = FastOutSlowInEasing
- }
-
- state(false) {
- this[RelativeThumbTranslationProp] = 0f
- this[ThumbColorProp] = uncheckedColor
- }
- state(true) {
- this[RelativeThumbTranslationProp] = 1f
- this[ThumbColorProp] = checkedColor
- }
- transition {
- RelativeThumbTranslationProp using switchTween()
- ThumbColorProp using switchTween()
- }
+ val paint = Paint().apply {
+ isAntiAlias = true
+ color = thumbColor
}
+ val centerHeight = parentSize.height / 2
+ val thumbRadius = (ThumbDiameter / 2).toPx().value
+ val x = position.px.value + thumbRadius
+
+ canvas.drawCircle(Offset(x, centerHeight.value), thumbRadius, paint)
+}
private val CheckedTrackOpacity = 0.54f
private val UncheckedTrackOpacity = 0.38f
@@ -186,4 +161,7 @@
// TODO(malkov): clarify this padding for Switch
private val DefaultSwitchPadding = 2.dp
private val SwitchWidth = TrackWidth
-private val SwitchHeight = ThumbDiameter
\ No newline at end of file
+private val SwitchHeight = ThumbDiameter
+private val ThumbPathLength = TrackWidth - ThumbDiameter
+
+private val AnimationBuilder = TweenBuilder<Float>().apply { duration = 100 }
\ No newline at end of file
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/internal/AnchoredControllerByState.kt b/ui/ui-material/src/main/java/androidx/ui/material/internal/AnchoredControllerByState.kt
new file mode 100644
index 0000000..c8416a3
--- /dev/null
+++ b/ui/ui-material/src/main/java/androidx/ui/material/internal/AnchoredControllerByState.kt
@@ -0,0 +1,78 @@
+/*
+ * 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.ui.material.internal
+
+import androidx.animation.AnimationBuilder
+import androidx.compose.composer
+import androidx.compose.effectOf
+import androidx.compose.memo
+import androidx.compose.onCommit
+import androidx.compose.state
+import androidx.ui.foundation.gestures.Draggable
+import androidx.ui.foundation.animation.AnchorsFlingConfig
+import androidx.ui.foundation.animation.AnimatedFloatDragController
+import androidx.ui.foundation.gestures.DragValueController
+import androidx.ui.foundation.gestures.DraggableCallback
+
+/**
+ * Effect to create special version if [AnimatedFloatDragController] that maps
+ * float anchors to states
+ *
+ * Alongside with control that [AnimatedFloatDragController] provides, this component ensures that:
+ * 1. The AnimatedFloat value will be in sync with call site state
+ * 2. When the anchor is reached, [onStateChange] will be called with state mapped to this anchor
+ * 3. When the anchor is reached and [onStateChange] with corresponding state is called, but
+ * call site didn't update state to the reached one for some reason,
+ * this component performs rollback to the previous (correct) state.
+ * 4. When new [state] is provided, component will be animated to state's anchor
+ *
+ * @param T type with which state is represented
+ * @param state current state to represent Float value with
+ * @param onStateChange callback to update call site's state
+ * @param anchorsToState pairs of anchors to states to map anchors to state and vise versa
+ * @param animationBuilder animation which will be used for animations
+ * @return pair of controller and callback to pass to [Draggable].
+ */
+// TODO(malkov/tianliu) (figure our how to make it better and make public)
+internal fun <T> anchoredControllerByState(
+ state: T,
+ onStateChange: (T) -> Unit,
+ anchorsToState: List<Pair<Float, T>>,
+ animationBuilder: AnimationBuilder<Float>
+) = effectOf<Pair<DragValueController, DraggableCallback>> {
+ val anchors = +memo(anchorsToState) { anchorsToState.map { it.first } }
+ val currentValue = anchorsToState.firstOrNull { it.second == state }!!.first
+
+ // This state is to force this component to be recomposed and trigger +onCommit below
+ // This is needed to stay in sync with drag state that caller side holds
+ val forceAnimationCheck = +state { true }
+ val callback = DraggableCallback(onDragSettled = { finalValue ->
+ val newState = anchorsToState.firstOrNull { it.first == finalValue }?.second
+ if (newState != null && newState != state) {
+ onStateChange(newState)
+ forceAnimationCheck.value = !forceAnimationCheck.value
+ }
+ })
+
+ val controller = +memo(anchorsToState, animationBuilder) {
+ AnimatedFloatDragController(currentValue, AnchorsFlingConfig(anchors, animationBuilder))
+ }
+ +onCommit(currentValue, forceAnimationCheck.value) {
+ controller.animatedFloat.animateTo(currentValue, animationBuilder)
+ }
+ controller to callback
+}
\ No newline at end of file
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/ripple/Ripple.kt b/ui/ui-material/src/main/java/androidx/ui/material/ripple/Ripple.kt
index 139a554..382e2ab 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/ripple/Ripple.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/ripple/Ripple.kt
@@ -60,7 +60,7 @@
fun Ripple(
bounded: Boolean,
radius: Dp? = null,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
val density = +ambientDensity()
val rippleSurface = +ambientRippleSurface()
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/ripple/RippleSurface.kt b/ui/ui-material/src/main/java/androidx/ui/material/ripple/RippleSurface.kt
index 48d547f..aa0dd8c 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/ripple/RippleSurface.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/ripple/RippleSurface.kt
@@ -87,7 +87,7 @@
@Composable
fun RippleSurface(
color: Color?,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
val owner = +memo { RippleSurfaceOwnerImpl() }
owner.backgroundColor = color
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/surface/Card.kt b/ui/ui-material/src/main/java/androidx/ui/material/surface/Card.kt
index 52f1f13f..544666b 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/surface/Card.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/surface/Card.kt
@@ -49,7 +49,7 @@
color: Color = +themeColor { surface },
border: Border? = null,
elevation: Dp = 0.dp,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
// TODO(Andrey: This currently adds no logic on top of Surface, I just reserve the name
// for now. We will see what will be the additional Card specific logic later.
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/surface/Surface.kt b/ui/ui-material/src/main/java/androidx/ui/material/surface/Surface.kt
index 0981820..8a75fb3 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/surface/Surface.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/surface/Surface.kt
@@ -84,7 +84,7 @@
color: Color = +themeColor { surface },
border: Border? = null,
elevation: Dp = 0.dp,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
SurfaceLayout {
DrawShadow(shape = shape, elevation = elevation)
@@ -110,7 +110,7 @@
* TODO("Andrey: Should be replaced with some basic layout implementation when we have it")
*/
@Composable
-private fun SurfaceLayout(@Children children: @Composable() () -> Unit) {
+private fun SurfaceLayout(children: @Composable() () -> Unit) {
Layout(children = children, layoutBlock = { measurables, constraints ->
if (measurables.size > 1) {
throw IllegalStateException("Surface can have only one direct measurable child!")
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/surface/TransparentSurface.kt b/ui/ui-material/src/main/java/androidx/ui/material/surface/TransparentSurface.kt
index 29aea48..f68b95c 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/surface/TransparentSurface.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/surface/TransparentSurface.kt
@@ -38,7 +38,7 @@
@Composable
fun TransparentSurface(
shape: Shape = RectangleShape,
- @Children children: @Composable() () -> Unit
+ children: @Composable() () -> Unit
) {
Surface(shape = shape, children = children, color = Color.Transparent)
}
diff --git a/ui/ui-platform/api/1.0.0-alpha01.txt b/ui/ui-platform/api/1.0.0-alpha01.txt
index dd10fd6..d667964 100644
--- a/ui/ui-platform/api/1.0.0-alpha01.txt
+++ b/ui/ui-platform/api/1.0.0-alpha01.txt
@@ -206,70 +206,17 @@
}
public final class SemanticsComponentNode extends androidx.ui.core.ComponentNode {
- ctor public SemanticsComponentNode(boolean container, boolean explicitChildNodes, Boolean? enabled, Boolean? checked, Boolean? selected, Boolean? button, Boolean? header, Boolean? textField, Boolean? focused, Boolean? inMutuallyExclusiveGroup, Boolean? obscured, Boolean? scopesRoute, Boolean? namesRoute, Boolean? hidden, String? label, String? value, String? hint, androidx.ui.text.style.TextDirection? textDirection, String? testTag, java.util.List<? extends androidx.ui.core.semantics.SemanticsAction<?>> actions);
+ ctor public SemanticsComponentNode(androidx.ui.core.semantics.SemanticsConfiguration semanticsConfiguration, boolean container, boolean explicitChildNodes);
ctor public SemanticsComponentNode();
- method public java.util.List<androidx.ui.core.semantics.SemanticsAction<?>> getActions();
- method public Boolean? getButton();
- method public Boolean? getChecked();
method public boolean getContainer();
- method public Boolean? getEnabled();
method public boolean getExplicitChildNodes();
- method public Boolean? getFocused();
- method public Boolean? getHeader();
- method public Boolean? getHidden();
- method public String? getHint();
- method public Boolean? getInMutuallyExclusiveGroup();
- method public String? getLabel();
- method public Boolean? getNamesRoute();
- method public Boolean? getObscured();
- method public Boolean? getScopesRoute();
- method public Boolean? getSelected();
method public androidx.ui.core.semantics.SemanticsConfiguration getSemanticsConfiguration();
- method public String? getTestTag();
- method public androidx.ui.text.style.TextDirection? getTextDirection();
- method public Boolean? getTextField();
- method public String? getValue();
- method public void setActions(java.util.List<? extends androidx.ui.core.semantics.SemanticsAction<?>> p);
- method public void setButton(Boolean? p);
- method public void setChecked(Boolean? p);
method public void setContainer(boolean p);
- method public void setEnabled(Boolean? p);
method public void setExplicitChildNodes(boolean p);
- method public void setFocused(Boolean? p);
- method public void setHeader(Boolean? p);
- method public void setHidden(Boolean? p);
- method public void setHint(String? p);
- method public void setInMutuallyExclusiveGroup(Boolean? p);
- method public void setLabel(String? p);
- method public void setNamesRoute(Boolean? p);
- method public void setObscured(Boolean? p);
- method public void setScopesRoute(Boolean? p);
- method public void setSelected(Boolean? p);
- method public void setTestTag(String? p);
- method public void setTextDirection(androidx.ui.text.style.TextDirection? p);
- method public void setTextField(Boolean? p);
- method public void setValue(String? p);
- property public final java.util.List<androidx.ui.core.semantics.SemanticsAction<?>> actions;
- property public final Boolean? button;
- property public final Boolean? checked;
+ method public void setSemanticsConfiguration(androidx.ui.core.semantics.SemanticsConfiguration p);
property public final boolean container;
- property public final Boolean? enabled;
property public final boolean explicitChildNodes;
- property public final Boolean? focused;
- property public final Boolean? header;
- property public final Boolean? hidden;
- property public final String? hint;
- property public final Boolean? inMutuallyExclusiveGroup;
- property public final String? label;
- property public final Boolean? namesRoute;
- property public final Boolean? obscured;
- property public final Boolean? scopesRoute;
- property public final Boolean? selected;
property public final androidx.ui.core.semantics.SemanticsConfiguration semanticsConfiguration;
- property public final String? testTag;
- property public final androidx.ui.text.style.TextDirection? textDirection;
- property public final Boolean? textField;
- property public final String? value;
}
public interface SemanticsTreeNode {
@@ -304,22 +251,6 @@
method public void sendEvent(android.view.MotionEvent event);
}
- public final class Unicode {
- field public static final String ALM = "\u061c";
- field public static final String FSI = "\u2068";
- field public static final androidx.ui.core.Unicode! INSTANCE;
- field public static final String LRE = "\u202a";
- field public static final String LRI = "\u2066";
- field public static final String LRM = "\u200e";
- field public static final String LRO = "\u202d";
- field public static final String PDF = "\u202c";
- field public static final String PDI = "\u2069";
- field public static final String RLE = "\u202b";
- field public static final String RLI = "\u2067";
- field public static final String RLM = "\u200f";
- field public static final String RLO = "\u202e";
- }
-
}
package androidx.ui.core.input {
@@ -357,138 +288,31 @@
package androidx.ui.core.semantics {
- public final class SemanticsConfiguration {
+ public final class SemanticsConfiguration implements java.lang.Iterable<java.util.Map.Entry<? extends androidx.ui.semantics.SemanticsPropertyKey<?>,?>> kotlin.jvm.internal.markers.KMappedMarker androidx.ui.semantics.SemanticsPropertyReceiver {
ctor public SemanticsConfiguration();
+ method public operator <T> boolean contains(androidx.ui.semantics.SemanticsPropertyKey<T> key);
method public androidx.ui.core.semantics.SemanticsConfiguration copy();
- method public java.util.List<androidx.ui.core.semantics.SemanticsAction<?>> getActions();
+ method public operator <T> T! get(androidx.ui.semantics.SemanticsPropertyKey<T> key);
method public boolean getExplicitChildNodes();
method public boolean getHasBeenAnnotated();
- method public boolean getHasImplicitScrolling();
- method public String? getHint();
- method public androidx.ui.core.semantics.SemanticsHintOverrides? getHintOverrides();
- method public String? getLabel();
- method public boolean getLiveRegion();
- method public boolean getNamesRoute();
- method public boolean getScopesRoute();
- method public Float? getScrollExtentMax();
- method public Float? getScrollExtentMin();
- method public Float? getScrollPosition();
- method public String? getTestTag();
- method public androidx.ui.text.style.TextDirection? getTextDirection();
- method public androidx.ui.text.TextSelection? getTextSelection();
- method public String? getValue();
- method public boolean isBlockingSemanticsOfPreviouslyPaintedNodes();
- method public boolean isButton();
- method public Boolean? isChecked();
- method public boolean isCompatibleWith(androidx.ui.core.semantics.SemanticsConfiguration? other);
- method public Boolean? isEnabled();
- method public boolean isFocused();
- method public boolean isHeader();
- method public boolean isHidden();
- method public boolean isImage();
- method public boolean isInMutuallyExclusiveGroup();
+ method public <T> T! getOrElse(androidx.ui.semantics.SemanticsPropertyKey<T> key, kotlin.jvm.functions.Function0<? extends T> defaultValue);
+ method public <T> T? getOrElseNullable(androidx.ui.semantics.SemanticsPropertyKey<T> key, kotlin.jvm.functions.Function0<? extends T> defaultValue);
method public boolean isMergingSemanticsOfDescendants();
- method public boolean isObscured();
- method public boolean isSelected();
method public boolean isSemanticBoundary();
- method public boolean isTextField();
- method public Boolean? isToggled();
- method public void setActions(java.util.List<? extends androidx.ui.core.semantics.SemanticsAction<?>> value);
- method public void setBlockingSemanticsOfPreviouslyPaintedNodes(boolean p);
- method public void setButton(boolean p);
- method public void setChecked(Boolean? value);
- method public void setEnabled(Boolean? value);
+ method public java.util.Iterator<java.util.Map.Entry<androidx.ui.semantics.SemanticsPropertyKey<?>,java.lang.Object>> iterator();
+ method public <T> void set(androidx.ui.semantics.SemanticsPropertyKey<T> key, T? value);
method public void setExplicitChildNodes(boolean p);
- method public void setFocused(boolean p);
- method public void setHasBeenAnnotated(boolean p);
- method public void setHasImplicitScrolling(boolean p);
- method public void setHeader(boolean p);
- method public void setHidden(boolean p);
- method public void setHint(String? p);
- method public void setHintOverrides(androidx.ui.core.semantics.SemanticsHintOverrides? p);
- method public void setImage(boolean p);
- method public void setInMutuallyExclusiveGroup(boolean p);
- method public void setLabel(String? p);
- method public void setLiveRegion(boolean p);
method public void setMergingSemanticsOfDescendants(boolean value);
- method public void setNamesRoute(boolean p);
- method public void setObscured(boolean p);
- method public void setScopesRoute(boolean p);
- method public void setScrollExtentMax(Float? p);
- method public void setScrollExtentMin(Float? p);
- method public void setScrollPosition(Float? p);
- method public void setSelected(boolean p);
method public void setSemanticBoundary(boolean value);
- method public void setTestTag(String? p);
- method public void setTextDirection(androidx.ui.text.style.TextDirection? p);
- method public void setTextField(boolean p);
- method public void setTextSelection(androidx.ui.text.TextSelection? p);
- method public void setToggled(Boolean? value);
- method public void setValue(String? p);
- property public final java.util.List<androidx.ui.core.semantics.SemanticsAction<?>> actions;
property public final boolean explicitChildNodes;
property public final boolean hasBeenAnnotated;
- property public final boolean hasImplicitScrolling;
- property public final String? hint;
- property public final androidx.ui.core.semantics.SemanticsHintOverrides? hintOverrides;
- property public final boolean isBlockingSemanticsOfPreviouslyPaintedNodes;
- property public final boolean isButton;
- property public final Boolean? isChecked;
- property public final Boolean? isEnabled;
- property public final boolean isFocused;
- property public final boolean isHeader;
- property public final boolean isHidden;
- property public final boolean isImage;
- property public final boolean isInMutuallyExclusiveGroup;
property public final boolean isMergingSemanticsOfDescendants;
- property public final boolean isObscured;
- property public final boolean isSelected;
property public final boolean isSemanticBoundary;
- property public final boolean isTextField;
- property public final Boolean? isToggled;
- property public final String? label;
- property public final boolean liveRegion;
- property public final boolean namesRoute;
- property public final boolean scopesRoute;
- property public final Float? scrollExtentMax;
- property public final Float? scrollExtentMin;
- property public final Float? scrollPosition;
- property public final String? testTag;
- property public final androidx.ui.text.style.TextDirection? textDirection;
- property public final androidx.ui.text.TextSelection? textSelection;
- property public final String? value;
}
public final class SemanticsConfigurationKt {
ctor public SemanticsConfigurationKt();
- }
-
- public enum SemanticsFlag {
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag HasCheckedState;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag HasEnabledState;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag HasImplicitScrolling;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag HasToggledState;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag IsButton;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag IsChecked;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag IsEnabled;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag IsFocused;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag IsHeader;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag IsHidden;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag IsImage;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag IsInMutuallyExclusiveGroup;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag IsLiveRegion;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag IsObscured;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag IsSelected;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag IsTextField;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag IsToggled;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag NamesRoute;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag ScopesRoute;
- field public static final androidx.ui.core.semantics.SemanticsFlag.Companion! Companion;
- }
-
- public static final class SemanticsFlag.Companion {
- method public java.util.Map<java.lang.Integer,androidx.ui.core.semantics.SemanticsFlag> getValues();
- property public final java.util.Map<java.lang.Integer,androidx.ui.core.semantics.SemanticsFlag> values;
+ method public static <T> T? getOrNull(androidx.ui.core.semantics.SemanticsConfiguration, androidx.ui.semantics.SemanticsPropertyKey<T> key);
}
public final class SemanticsHintOverrides {
@@ -517,7 +341,6 @@
method public androidx.ui.engine.geometry.Rect? getParentPaintClipRect();
method public androidx.ui.engine.geometry.Rect? getParentSemanticsClipRect();
method public androidx.ui.engine.geometry.Rect getRect();
- method public boolean isDifferentFromCurrentSemanticAnnotation(androidx.ui.core.semantics.SemanticsConfiguration config);
method public boolean isInvisible();
method public boolean isMergedIntoParent();
method public boolean isPartOfNodeMerging();
@@ -557,7 +380,26 @@
public final class SemanticsOwner {
ctor public SemanticsOwner();
- method public void performAction(int id, androidx.ui.core.semantics.SemanticsActionType<?> action, Object? args = null);
+ }
+
+}
+
+package androidx.ui.internal {
+
+ public final class Unicode {
+ field public static final String ALM = "\u061c";
+ field public static final String FSI = "\u2068";
+ field public static final androidx.ui.internal.Unicode! INSTANCE;
+ field public static final String LRE = "\u202a";
+ field public static final String LRI = "\u2066";
+ field public static final String LRM = "\u200e";
+ field public static final String LRO = "\u202d";
+ field public static final String PDF = "\u202c";
+ field public static final String PDI = "\u2069";
+ field public static final String RLE = "\u202b";
+ field public static final String RLI = "\u2067";
+ field public static final String RLM = "\u200f";
+ field public static final String RLO = "\u202e";
}
}
diff --git a/ui/ui-platform/api/current.txt b/ui/ui-platform/api/current.txt
index dd10fd6..d667964 100644
--- a/ui/ui-platform/api/current.txt
+++ b/ui/ui-platform/api/current.txt
@@ -206,70 +206,17 @@
}
public final class SemanticsComponentNode extends androidx.ui.core.ComponentNode {
- ctor public SemanticsComponentNode(boolean container, boolean explicitChildNodes, Boolean? enabled, Boolean? checked, Boolean? selected, Boolean? button, Boolean? header, Boolean? textField, Boolean? focused, Boolean? inMutuallyExclusiveGroup, Boolean? obscured, Boolean? scopesRoute, Boolean? namesRoute, Boolean? hidden, String? label, String? value, String? hint, androidx.ui.text.style.TextDirection? textDirection, String? testTag, java.util.List<? extends androidx.ui.core.semantics.SemanticsAction<?>> actions);
+ ctor public SemanticsComponentNode(androidx.ui.core.semantics.SemanticsConfiguration semanticsConfiguration, boolean container, boolean explicitChildNodes);
ctor public SemanticsComponentNode();
- method public java.util.List<androidx.ui.core.semantics.SemanticsAction<?>> getActions();
- method public Boolean? getButton();
- method public Boolean? getChecked();
method public boolean getContainer();
- method public Boolean? getEnabled();
method public boolean getExplicitChildNodes();
- method public Boolean? getFocused();
- method public Boolean? getHeader();
- method public Boolean? getHidden();
- method public String? getHint();
- method public Boolean? getInMutuallyExclusiveGroup();
- method public String? getLabel();
- method public Boolean? getNamesRoute();
- method public Boolean? getObscured();
- method public Boolean? getScopesRoute();
- method public Boolean? getSelected();
method public androidx.ui.core.semantics.SemanticsConfiguration getSemanticsConfiguration();
- method public String? getTestTag();
- method public androidx.ui.text.style.TextDirection? getTextDirection();
- method public Boolean? getTextField();
- method public String? getValue();
- method public void setActions(java.util.List<? extends androidx.ui.core.semantics.SemanticsAction<?>> p);
- method public void setButton(Boolean? p);
- method public void setChecked(Boolean? p);
method public void setContainer(boolean p);
- method public void setEnabled(Boolean? p);
method public void setExplicitChildNodes(boolean p);
- method public void setFocused(Boolean? p);
- method public void setHeader(Boolean? p);
- method public void setHidden(Boolean? p);
- method public void setHint(String? p);
- method public void setInMutuallyExclusiveGroup(Boolean? p);
- method public void setLabel(String? p);
- method public void setNamesRoute(Boolean? p);
- method public void setObscured(Boolean? p);
- method public void setScopesRoute(Boolean? p);
- method public void setSelected(Boolean? p);
- method public void setTestTag(String? p);
- method public void setTextDirection(androidx.ui.text.style.TextDirection? p);
- method public void setTextField(Boolean? p);
- method public void setValue(String? p);
- property public final java.util.List<androidx.ui.core.semantics.SemanticsAction<?>> actions;
- property public final Boolean? button;
- property public final Boolean? checked;
+ method public void setSemanticsConfiguration(androidx.ui.core.semantics.SemanticsConfiguration p);
property public final boolean container;
- property public final Boolean? enabled;
property public final boolean explicitChildNodes;
- property public final Boolean? focused;
- property public final Boolean? header;
- property public final Boolean? hidden;
- property public final String? hint;
- property public final Boolean? inMutuallyExclusiveGroup;
- property public final String? label;
- property public final Boolean? namesRoute;
- property public final Boolean? obscured;
- property public final Boolean? scopesRoute;
- property public final Boolean? selected;
property public final androidx.ui.core.semantics.SemanticsConfiguration semanticsConfiguration;
- property public final String? testTag;
- property public final androidx.ui.text.style.TextDirection? textDirection;
- property public final Boolean? textField;
- property public final String? value;
}
public interface SemanticsTreeNode {
@@ -304,22 +251,6 @@
method public void sendEvent(android.view.MotionEvent event);
}
- public final class Unicode {
- field public static final String ALM = "\u061c";
- field public static final String FSI = "\u2068";
- field public static final androidx.ui.core.Unicode! INSTANCE;
- field public static final String LRE = "\u202a";
- field public static final String LRI = "\u2066";
- field public static final String LRM = "\u200e";
- field public static final String LRO = "\u202d";
- field public static final String PDF = "\u202c";
- field public static final String PDI = "\u2069";
- field public static final String RLE = "\u202b";
- field public static final String RLI = "\u2067";
- field public static final String RLM = "\u200f";
- field public static final String RLO = "\u202e";
- }
-
}
package androidx.ui.core.input {
@@ -357,138 +288,31 @@
package androidx.ui.core.semantics {
- public final class SemanticsConfiguration {
+ public final class SemanticsConfiguration implements java.lang.Iterable<java.util.Map.Entry<? extends androidx.ui.semantics.SemanticsPropertyKey<?>,?>> kotlin.jvm.internal.markers.KMappedMarker androidx.ui.semantics.SemanticsPropertyReceiver {
ctor public SemanticsConfiguration();
+ method public operator <T> boolean contains(androidx.ui.semantics.SemanticsPropertyKey<T> key);
method public androidx.ui.core.semantics.SemanticsConfiguration copy();
- method public java.util.List<androidx.ui.core.semantics.SemanticsAction<?>> getActions();
+ method public operator <T> T! get(androidx.ui.semantics.SemanticsPropertyKey<T> key);
method public boolean getExplicitChildNodes();
method public boolean getHasBeenAnnotated();
- method public boolean getHasImplicitScrolling();
- method public String? getHint();
- method public androidx.ui.core.semantics.SemanticsHintOverrides? getHintOverrides();
- method public String? getLabel();
- method public boolean getLiveRegion();
- method public boolean getNamesRoute();
- method public boolean getScopesRoute();
- method public Float? getScrollExtentMax();
- method public Float? getScrollExtentMin();
- method public Float? getScrollPosition();
- method public String? getTestTag();
- method public androidx.ui.text.style.TextDirection? getTextDirection();
- method public androidx.ui.text.TextSelection? getTextSelection();
- method public String? getValue();
- method public boolean isBlockingSemanticsOfPreviouslyPaintedNodes();
- method public boolean isButton();
- method public Boolean? isChecked();
- method public boolean isCompatibleWith(androidx.ui.core.semantics.SemanticsConfiguration? other);
- method public Boolean? isEnabled();
- method public boolean isFocused();
- method public boolean isHeader();
- method public boolean isHidden();
- method public boolean isImage();
- method public boolean isInMutuallyExclusiveGroup();
+ method public <T> T! getOrElse(androidx.ui.semantics.SemanticsPropertyKey<T> key, kotlin.jvm.functions.Function0<? extends T> defaultValue);
+ method public <T> T? getOrElseNullable(androidx.ui.semantics.SemanticsPropertyKey<T> key, kotlin.jvm.functions.Function0<? extends T> defaultValue);
method public boolean isMergingSemanticsOfDescendants();
- method public boolean isObscured();
- method public boolean isSelected();
method public boolean isSemanticBoundary();
- method public boolean isTextField();
- method public Boolean? isToggled();
- method public void setActions(java.util.List<? extends androidx.ui.core.semantics.SemanticsAction<?>> value);
- method public void setBlockingSemanticsOfPreviouslyPaintedNodes(boolean p);
- method public void setButton(boolean p);
- method public void setChecked(Boolean? value);
- method public void setEnabled(Boolean? value);
+ method public java.util.Iterator<java.util.Map.Entry<androidx.ui.semantics.SemanticsPropertyKey<?>,java.lang.Object>> iterator();
+ method public <T> void set(androidx.ui.semantics.SemanticsPropertyKey<T> key, T? value);
method public void setExplicitChildNodes(boolean p);
- method public void setFocused(boolean p);
- method public void setHasBeenAnnotated(boolean p);
- method public void setHasImplicitScrolling(boolean p);
- method public void setHeader(boolean p);
- method public void setHidden(boolean p);
- method public void setHint(String? p);
- method public void setHintOverrides(androidx.ui.core.semantics.SemanticsHintOverrides? p);
- method public void setImage(boolean p);
- method public void setInMutuallyExclusiveGroup(boolean p);
- method public void setLabel(String? p);
- method public void setLiveRegion(boolean p);
method public void setMergingSemanticsOfDescendants(boolean value);
- method public void setNamesRoute(boolean p);
- method public void setObscured(boolean p);
- method public void setScopesRoute(boolean p);
- method public void setScrollExtentMax(Float? p);
- method public void setScrollExtentMin(Float? p);
- method public void setScrollPosition(Float? p);
- method public void setSelected(boolean p);
method public void setSemanticBoundary(boolean value);
- method public void setTestTag(String? p);
- method public void setTextDirection(androidx.ui.text.style.TextDirection? p);
- method public void setTextField(boolean p);
- method public void setTextSelection(androidx.ui.text.TextSelection? p);
- method public void setToggled(Boolean? value);
- method public void setValue(String? p);
- property public final java.util.List<androidx.ui.core.semantics.SemanticsAction<?>> actions;
property public final boolean explicitChildNodes;
property public final boolean hasBeenAnnotated;
- property public final boolean hasImplicitScrolling;
- property public final String? hint;
- property public final androidx.ui.core.semantics.SemanticsHintOverrides? hintOverrides;
- property public final boolean isBlockingSemanticsOfPreviouslyPaintedNodes;
- property public final boolean isButton;
- property public final Boolean? isChecked;
- property public final Boolean? isEnabled;
- property public final boolean isFocused;
- property public final boolean isHeader;
- property public final boolean isHidden;
- property public final boolean isImage;
- property public final boolean isInMutuallyExclusiveGroup;
property public final boolean isMergingSemanticsOfDescendants;
- property public final boolean isObscured;
- property public final boolean isSelected;
property public final boolean isSemanticBoundary;
- property public final boolean isTextField;
- property public final Boolean? isToggled;
- property public final String? label;
- property public final boolean liveRegion;
- property public final boolean namesRoute;
- property public final boolean scopesRoute;
- property public final Float? scrollExtentMax;
- property public final Float? scrollExtentMin;
- property public final Float? scrollPosition;
- property public final String? testTag;
- property public final androidx.ui.text.style.TextDirection? textDirection;
- property public final androidx.ui.text.TextSelection? textSelection;
- property public final String? value;
}
public final class SemanticsConfigurationKt {
ctor public SemanticsConfigurationKt();
- }
-
- public enum SemanticsFlag {
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag HasCheckedState;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag HasEnabledState;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag HasImplicitScrolling;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag HasToggledState;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag IsButton;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag IsChecked;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag IsEnabled;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag IsFocused;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag IsHeader;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag IsHidden;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag IsImage;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag IsInMutuallyExclusiveGroup;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag IsLiveRegion;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag IsObscured;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag IsSelected;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag IsTextField;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag IsToggled;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag NamesRoute;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag ScopesRoute;
- field public static final androidx.ui.core.semantics.SemanticsFlag.Companion! Companion;
- }
-
- public static final class SemanticsFlag.Companion {
- method public java.util.Map<java.lang.Integer,androidx.ui.core.semantics.SemanticsFlag> getValues();
- property public final java.util.Map<java.lang.Integer,androidx.ui.core.semantics.SemanticsFlag> values;
+ method public static <T> T? getOrNull(androidx.ui.core.semantics.SemanticsConfiguration, androidx.ui.semantics.SemanticsPropertyKey<T> key);
}
public final class SemanticsHintOverrides {
@@ -517,7 +341,6 @@
method public androidx.ui.engine.geometry.Rect? getParentPaintClipRect();
method public androidx.ui.engine.geometry.Rect? getParentSemanticsClipRect();
method public androidx.ui.engine.geometry.Rect getRect();
- method public boolean isDifferentFromCurrentSemanticAnnotation(androidx.ui.core.semantics.SemanticsConfiguration config);
method public boolean isInvisible();
method public boolean isMergedIntoParent();
method public boolean isPartOfNodeMerging();
@@ -557,7 +380,26 @@
public final class SemanticsOwner {
ctor public SemanticsOwner();
- method public void performAction(int id, androidx.ui.core.semantics.SemanticsActionType<?> action, Object? args = null);
+ }
+
+}
+
+package androidx.ui.internal {
+
+ public final class Unicode {
+ field public static final String ALM = "\u061c";
+ field public static final String FSI = "\u2068";
+ field public static final androidx.ui.internal.Unicode! INSTANCE;
+ field public static final String LRE = "\u202a";
+ field public static final String LRI = "\u2066";
+ field public static final String LRM = "\u200e";
+ field public static final String LRO = "\u202d";
+ field public static final String PDF = "\u202c";
+ field public static final String PDI = "\u2069";
+ field public static final String RLE = "\u202b";
+ field public static final String RLI = "\u2067";
+ field public static final String RLM = "\u200f";
+ field public static final String RLO = "\u202e";
}
}
diff --git a/ui/ui-platform/api/restricted_1.0.0-alpha01.txt b/ui/ui-platform/api/restricted_1.0.0-alpha01.txt
index 5fb4d01..1e7cf19 100644
--- a/ui/ui-platform/api/restricted_1.0.0-alpha01.txt
+++ b/ui/ui-platform/api/restricted_1.0.0-alpha01.txt
@@ -206,70 +206,17 @@
}
public final class SemanticsComponentNode extends androidx.ui.core.ComponentNode {
- ctor public SemanticsComponentNode(boolean container, boolean explicitChildNodes, Boolean? enabled, Boolean? checked, Boolean? selected, Boolean? button, Boolean? header, Boolean? textField, Boolean? focused, Boolean? inMutuallyExclusiveGroup, Boolean? obscured, Boolean? scopesRoute, Boolean? namesRoute, Boolean? hidden, String? label, String? value, String? hint, androidx.ui.text.style.TextDirection? textDirection, String? testTag, java.util.List<? extends androidx.ui.core.semantics.SemanticsAction<?>> actions);
+ ctor public SemanticsComponentNode(androidx.ui.core.semantics.SemanticsConfiguration semanticsConfiguration, boolean container, boolean explicitChildNodes);
ctor public SemanticsComponentNode();
- method public java.util.List<androidx.ui.core.semantics.SemanticsAction<?>> getActions();
- method public Boolean? getButton();
- method public Boolean? getChecked();
method public boolean getContainer();
- method public Boolean? getEnabled();
method public boolean getExplicitChildNodes();
- method public Boolean? getFocused();
- method public Boolean? getHeader();
- method public Boolean? getHidden();
- method public String? getHint();
- method public Boolean? getInMutuallyExclusiveGroup();
- method public String? getLabel();
- method public Boolean? getNamesRoute();
- method public Boolean? getObscured();
- method public Boolean? getScopesRoute();
- method public Boolean? getSelected();
method public androidx.ui.core.semantics.SemanticsConfiguration getSemanticsConfiguration();
- method public String? getTestTag();
- method public androidx.ui.text.style.TextDirection? getTextDirection();
- method public Boolean? getTextField();
- method public String? getValue();
- method public void setActions(java.util.List<? extends androidx.ui.core.semantics.SemanticsAction<?>> p);
- method public void setButton(Boolean? p);
- method public void setChecked(Boolean? p);
method public void setContainer(boolean p);
- method public void setEnabled(Boolean? p);
method public void setExplicitChildNodes(boolean p);
- method public void setFocused(Boolean? p);
- method public void setHeader(Boolean? p);
- method public void setHidden(Boolean? p);
- method public void setHint(String? p);
- method public void setInMutuallyExclusiveGroup(Boolean? p);
- method public void setLabel(String? p);
- method public void setNamesRoute(Boolean? p);
- method public void setObscured(Boolean? p);
- method public void setScopesRoute(Boolean? p);
- method public void setSelected(Boolean? p);
- method public void setTestTag(String? p);
- method public void setTextDirection(androidx.ui.text.style.TextDirection? p);
- method public void setTextField(Boolean? p);
- method public void setValue(String? p);
- property public final java.util.List<androidx.ui.core.semantics.SemanticsAction<?>> actions;
- property public final Boolean? button;
- property public final Boolean? checked;
+ method public void setSemanticsConfiguration(androidx.ui.core.semantics.SemanticsConfiguration p);
property public final boolean container;
- property public final Boolean? enabled;
property public final boolean explicitChildNodes;
- property public final Boolean? focused;
- property public final Boolean? header;
- property public final Boolean? hidden;
- property public final String? hint;
- property public final Boolean? inMutuallyExclusiveGroup;
- property public final String? label;
- property public final Boolean? namesRoute;
- property public final Boolean? obscured;
- property public final Boolean? scopesRoute;
- property public final Boolean? selected;
property public final androidx.ui.core.semantics.SemanticsConfiguration semanticsConfiguration;
- property public final String? testTag;
- property public final androidx.ui.text.style.TextDirection? textDirection;
- property public final Boolean? textField;
- property public final String? value;
}
public interface SemanticsTreeNode {
@@ -304,22 +251,6 @@
method public void sendEvent(android.view.MotionEvent event);
}
- public final class Unicode {
- field public static final String ALM = "\u061c";
- field public static final String FSI = "\u2068";
- field public static final androidx.ui.core.Unicode! INSTANCE;
- field public static final String LRE = "\u202a";
- field public static final String LRI = "\u2066";
- field public static final String LRM = "\u200e";
- field public static final String LRO = "\u202d";
- field public static final String PDF = "\u202c";
- field public static final String PDI = "\u2069";
- field public static final String RLE = "\u202b";
- field public static final String RLI = "\u2067";
- field public static final String RLM = "\u200f";
- field public static final String RLO = "\u202e";
- }
-
}
package androidx.ui.core.input {
@@ -357,138 +288,31 @@
package androidx.ui.core.semantics {
- public final class SemanticsConfiguration {
+ public final class SemanticsConfiguration implements java.lang.Iterable<java.util.Map.Entry<? extends androidx.ui.semantics.SemanticsPropertyKey<?>,?>> kotlin.jvm.internal.markers.KMappedMarker androidx.ui.semantics.SemanticsPropertyReceiver {
ctor public SemanticsConfiguration();
+ method public operator <T> boolean contains(androidx.ui.semantics.SemanticsPropertyKey<T> key);
method public androidx.ui.core.semantics.SemanticsConfiguration copy();
- method public java.util.List<androidx.ui.core.semantics.SemanticsAction<?>> getActions();
+ method public operator <T> T! get(androidx.ui.semantics.SemanticsPropertyKey<T> key);
method public boolean getExplicitChildNodes();
method public boolean getHasBeenAnnotated();
- method public boolean getHasImplicitScrolling();
- method public String? getHint();
- method public androidx.ui.core.semantics.SemanticsHintOverrides? getHintOverrides();
- method public String? getLabel();
- method public boolean getLiveRegion();
- method public boolean getNamesRoute();
- method public boolean getScopesRoute();
- method public Float? getScrollExtentMax();
- method public Float? getScrollExtentMin();
- method public Float? getScrollPosition();
- method public String? getTestTag();
- method public androidx.ui.text.style.TextDirection? getTextDirection();
- method public androidx.ui.text.TextSelection? getTextSelection();
- method public String? getValue();
- method public boolean isBlockingSemanticsOfPreviouslyPaintedNodes();
- method public boolean isButton();
- method public Boolean? isChecked();
- method public boolean isCompatibleWith(androidx.ui.core.semantics.SemanticsConfiguration? other);
- method public Boolean? isEnabled();
- method public boolean isFocused();
- method public boolean isHeader();
- method public boolean isHidden();
- method public boolean isImage();
- method public boolean isInMutuallyExclusiveGroup();
+ method public <T> T! getOrElse(androidx.ui.semantics.SemanticsPropertyKey<T> key, kotlin.jvm.functions.Function0<? extends T> defaultValue);
+ method public <T> T? getOrElseNullable(androidx.ui.semantics.SemanticsPropertyKey<T> key, kotlin.jvm.functions.Function0<? extends T> defaultValue);
method public boolean isMergingSemanticsOfDescendants();
- method public boolean isObscured();
- method public boolean isSelected();
method public boolean isSemanticBoundary();
- method public boolean isTextField();
- method public Boolean? isToggled();
- method public void setActions(java.util.List<? extends androidx.ui.core.semantics.SemanticsAction<?>> value);
- method public void setBlockingSemanticsOfPreviouslyPaintedNodes(boolean p);
- method public void setButton(boolean p);
- method public void setChecked(Boolean? value);
- method public void setEnabled(Boolean? value);
+ method public java.util.Iterator<java.util.Map.Entry<androidx.ui.semantics.SemanticsPropertyKey<?>,java.lang.Object>> iterator();
+ method public <T> void set(androidx.ui.semantics.SemanticsPropertyKey<T> key, T? value);
method public void setExplicitChildNodes(boolean p);
- method public void setFocused(boolean p);
- method public void setHasBeenAnnotated(boolean p);
- method public void setHasImplicitScrolling(boolean p);
- method public void setHeader(boolean p);
- method public void setHidden(boolean p);
- method public void setHint(String? p);
- method public void setHintOverrides(androidx.ui.core.semantics.SemanticsHintOverrides? p);
- method public void setImage(boolean p);
- method public void setInMutuallyExclusiveGroup(boolean p);
- method public void setLabel(String? p);
- method public void setLiveRegion(boolean p);
method public void setMergingSemanticsOfDescendants(boolean value);
- method public void setNamesRoute(boolean p);
- method public void setObscured(boolean p);
- method public void setScopesRoute(boolean p);
- method public void setScrollExtentMax(Float? p);
- method public void setScrollExtentMin(Float? p);
- method public void setScrollPosition(Float? p);
- method public void setSelected(boolean p);
method public void setSemanticBoundary(boolean value);
- method public void setTestTag(String? p);
- method public void setTextDirection(androidx.ui.text.style.TextDirection? p);
- method public void setTextField(boolean p);
- method public void setTextSelection(androidx.ui.text.TextSelection? p);
- method public void setToggled(Boolean? value);
- method public void setValue(String? p);
- property public final java.util.List<androidx.ui.core.semantics.SemanticsAction<?>> actions;
property public final boolean explicitChildNodes;
property public final boolean hasBeenAnnotated;
- property public final boolean hasImplicitScrolling;
- property public final String? hint;
- property public final androidx.ui.core.semantics.SemanticsHintOverrides? hintOverrides;
- property public final boolean isBlockingSemanticsOfPreviouslyPaintedNodes;
- property public final boolean isButton;
- property public final Boolean? isChecked;
- property public final Boolean? isEnabled;
- property public final boolean isFocused;
- property public final boolean isHeader;
- property public final boolean isHidden;
- property public final boolean isImage;
- property public final boolean isInMutuallyExclusiveGroup;
property public final boolean isMergingSemanticsOfDescendants;
- property public final boolean isObscured;
- property public final boolean isSelected;
property public final boolean isSemanticBoundary;
- property public final boolean isTextField;
- property public final Boolean? isToggled;
- property public final String? label;
- property public final boolean liveRegion;
- property public final boolean namesRoute;
- property public final boolean scopesRoute;
- property public final Float? scrollExtentMax;
- property public final Float? scrollExtentMin;
- property public final Float? scrollPosition;
- property public final String? testTag;
- property public final androidx.ui.text.style.TextDirection? textDirection;
- property public final androidx.ui.text.TextSelection? textSelection;
- property public final String? value;
}
public final class SemanticsConfigurationKt {
ctor public SemanticsConfigurationKt();
- }
-
- public enum SemanticsFlag {
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag HasCheckedState;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag HasEnabledState;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag HasImplicitScrolling;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag HasToggledState;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag IsButton;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag IsChecked;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag IsEnabled;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag IsFocused;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag IsHeader;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag IsHidden;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag IsImage;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag IsInMutuallyExclusiveGroup;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag IsLiveRegion;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag IsObscured;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag IsSelected;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag IsTextField;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag IsToggled;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag NamesRoute;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag ScopesRoute;
- field public static final androidx.ui.core.semantics.SemanticsFlag.Companion! Companion;
- }
-
- public static final class SemanticsFlag.Companion {
- method public java.util.Map<java.lang.Integer,androidx.ui.core.semantics.SemanticsFlag> getValues();
- property public final java.util.Map<java.lang.Integer,androidx.ui.core.semantics.SemanticsFlag> values;
+ method public static <T> T? getOrNull(androidx.ui.core.semantics.SemanticsConfiguration, androidx.ui.semantics.SemanticsPropertyKey<T> key);
}
public final class SemanticsHintOverrides {
@@ -517,7 +341,6 @@
method public androidx.ui.engine.geometry.Rect? getParentPaintClipRect();
method public androidx.ui.engine.geometry.Rect? getParentSemanticsClipRect();
method public androidx.ui.engine.geometry.Rect getRect();
- method public boolean isDifferentFromCurrentSemanticAnnotation(androidx.ui.core.semantics.SemanticsConfiguration config);
method public boolean isInvisible();
method public boolean isMergedIntoParent();
method public boolean isPartOfNodeMerging();
@@ -557,7 +380,6 @@
public final class SemanticsOwner {
ctor public SemanticsOwner();
- method public void performAction(int id, androidx.ui.core.semantics.SemanticsActionType<?> action, Object? args = null);
}
}
@@ -571,3 +393,23 @@
}
+package androidx.ui.internal {
+
+ public final class Unicode {
+ field public static final String ALM = "\u061c";
+ field public static final String FSI = "\u2068";
+ field public static final androidx.ui.internal.Unicode! INSTANCE;
+ field public static final String LRE = "\u202a";
+ field public static final String LRI = "\u2066";
+ field public static final String LRM = "\u200e";
+ field public static final String LRO = "\u202d";
+ field public static final String PDF = "\u202c";
+ field public static final String PDI = "\u2069";
+ field public static final String RLE = "\u202b";
+ field public static final String RLI = "\u2067";
+ field public static final String RLM = "\u200f";
+ field public static final String RLO = "\u202e";
+ }
+
+}
+
diff --git a/ui/ui-platform/api/restricted_current.txt b/ui/ui-platform/api/restricted_current.txt
index 5fb4d01..1e7cf19 100644
--- a/ui/ui-platform/api/restricted_current.txt
+++ b/ui/ui-platform/api/restricted_current.txt
@@ -206,70 +206,17 @@
}
public final class SemanticsComponentNode extends androidx.ui.core.ComponentNode {
- ctor public SemanticsComponentNode(boolean container, boolean explicitChildNodes, Boolean? enabled, Boolean? checked, Boolean? selected, Boolean? button, Boolean? header, Boolean? textField, Boolean? focused, Boolean? inMutuallyExclusiveGroup, Boolean? obscured, Boolean? scopesRoute, Boolean? namesRoute, Boolean? hidden, String? label, String? value, String? hint, androidx.ui.text.style.TextDirection? textDirection, String? testTag, java.util.List<? extends androidx.ui.core.semantics.SemanticsAction<?>> actions);
+ ctor public SemanticsComponentNode(androidx.ui.core.semantics.SemanticsConfiguration semanticsConfiguration, boolean container, boolean explicitChildNodes);
ctor public SemanticsComponentNode();
- method public java.util.List<androidx.ui.core.semantics.SemanticsAction<?>> getActions();
- method public Boolean? getButton();
- method public Boolean? getChecked();
method public boolean getContainer();
- method public Boolean? getEnabled();
method public boolean getExplicitChildNodes();
- method public Boolean? getFocused();
- method public Boolean? getHeader();
- method public Boolean? getHidden();
- method public String? getHint();
- method public Boolean? getInMutuallyExclusiveGroup();
- method public String? getLabel();
- method public Boolean? getNamesRoute();
- method public Boolean? getObscured();
- method public Boolean? getScopesRoute();
- method public Boolean? getSelected();
method public androidx.ui.core.semantics.SemanticsConfiguration getSemanticsConfiguration();
- method public String? getTestTag();
- method public androidx.ui.text.style.TextDirection? getTextDirection();
- method public Boolean? getTextField();
- method public String? getValue();
- method public void setActions(java.util.List<? extends androidx.ui.core.semantics.SemanticsAction<?>> p);
- method public void setButton(Boolean? p);
- method public void setChecked(Boolean? p);
method public void setContainer(boolean p);
- method public void setEnabled(Boolean? p);
method public void setExplicitChildNodes(boolean p);
- method public void setFocused(Boolean? p);
- method public void setHeader(Boolean? p);
- method public void setHidden(Boolean? p);
- method public void setHint(String? p);
- method public void setInMutuallyExclusiveGroup(Boolean? p);
- method public void setLabel(String? p);
- method public void setNamesRoute(Boolean? p);
- method public void setObscured(Boolean? p);
- method public void setScopesRoute(Boolean? p);
- method public void setSelected(Boolean? p);
- method public void setTestTag(String? p);
- method public void setTextDirection(androidx.ui.text.style.TextDirection? p);
- method public void setTextField(Boolean? p);
- method public void setValue(String? p);
- property public final java.util.List<androidx.ui.core.semantics.SemanticsAction<?>> actions;
- property public final Boolean? button;
- property public final Boolean? checked;
+ method public void setSemanticsConfiguration(androidx.ui.core.semantics.SemanticsConfiguration p);
property public final boolean container;
- property public final Boolean? enabled;
property public final boolean explicitChildNodes;
- property public final Boolean? focused;
- property public final Boolean? header;
- property public final Boolean? hidden;
- property public final String? hint;
- property public final Boolean? inMutuallyExclusiveGroup;
- property public final String? label;
- property public final Boolean? namesRoute;
- property public final Boolean? obscured;
- property public final Boolean? scopesRoute;
- property public final Boolean? selected;
property public final androidx.ui.core.semantics.SemanticsConfiguration semanticsConfiguration;
- property public final String? testTag;
- property public final androidx.ui.text.style.TextDirection? textDirection;
- property public final Boolean? textField;
- property public final String? value;
}
public interface SemanticsTreeNode {
@@ -304,22 +251,6 @@
method public void sendEvent(android.view.MotionEvent event);
}
- public final class Unicode {
- field public static final String ALM = "\u061c";
- field public static final String FSI = "\u2068";
- field public static final androidx.ui.core.Unicode! INSTANCE;
- field public static final String LRE = "\u202a";
- field public static final String LRI = "\u2066";
- field public static final String LRM = "\u200e";
- field public static final String LRO = "\u202d";
- field public static final String PDF = "\u202c";
- field public static final String PDI = "\u2069";
- field public static final String RLE = "\u202b";
- field public static final String RLI = "\u2067";
- field public static final String RLM = "\u200f";
- field public static final String RLO = "\u202e";
- }
-
}
package androidx.ui.core.input {
@@ -357,138 +288,31 @@
package androidx.ui.core.semantics {
- public final class SemanticsConfiguration {
+ public final class SemanticsConfiguration implements java.lang.Iterable<java.util.Map.Entry<? extends androidx.ui.semantics.SemanticsPropertyKey<?>,?>> kotlin.jvm.internal.markers.KMappedMarker androidx.ui.semantics.SemanticsPropertyReceiver {
ctor public SemanticsConfiguration();
+ method public operator <T> boolean contains(androidx.ui.semantics.SemanticsPropertyKey<T> key);
method public androidx.ui.core.semantics.SemanticsConfiguration copy();
- method public java.util.List<androidx.ui.core.semantics.SemanticsAction<?>> getActions();
+ method public operator <T> T! get(androidx.ui.semantics.SemanticsPropertyKey<T> key);
method public boolean getExplicitChildNodes();
method public boolean getHasBeenAnnotated();
- method public boolean getHasImplicitScrolling();
- method public String? getHint();
- method public androidx.ui.core.semantics.SemanticsHintOverrides? getHintOverrides();
- method public String? getLabel();
- method public boolean getLiveRegion();
- method public boolean getNamesRoute();
- method public boolean getScopesRoute();
- method public Float? getScrollExtentMax();
- method public Float? getScrollExtentMin();
- method public Float? getScrollPosition();
- method public String? getTestTag();
- method public androidx.ui.text.style.TextDirection? getTextDirection();
- method public androidx.ui.text.TextSelection? getTextSelection();
- method public String? getValue();
- method public boolean isBlockingSemanticsOfPreviouslyPaintedNodes();
- method public boolean isButton();
- method public Boolean? isChecked();
- method public boolean isCompatibleWith(androidx.ui.core.semantics.SemanticsConfiguration? other);
- method public Boolean? isEnabled();
- method public boolean isFocused();
- method public boolean isHeader();
- method public boolean isHidden();
- method public boolean isImage();
- method public boolean isInMutuallyExclusiveGroup();
+ method public <T> T! getOrElse(androidx.ui.semantics.SemanticsPropertyKey<T> key, kotlin.jvm.functions.Function0<? extends T> defaultValue);
+ method public <T> T? getOrElseNullable(androidx.ui.semantics.SemanticsPropertyKey<T> key, kotlin.jvm.functions.Function0<? extends T> defaultValue);
method public boolean isMergingSemanticsOfDescendants();
- method public boolean isObscured();
- method public boolean isSelected();
method public boolean isSemanticBoundary();
- method public boolean isTextField();
- method public Boolean? isToggled();
- method public void setActions(java.util.List<? extends androidx.ui.core.semantics.SemanticsAction<?>> value);
- method public void setBlockingSemanticsOfPreviouslyPaintedNodes(boolean p);
- method public void setButton(boolean p);
- method public void setChecked(Boolean? value);
- method public void setEnabled(Boolean? value);
+ method public java.util.Iterator<java.util.Map.Entry<androidx.ui.semantics.SemanticsPropertyKey<?>,java.lang.Object>> iterator();
+ method public <T> void set(androidx.ui.semantics.SemanticsPropertyKey<T> key, T? value);
method public void setExplicitChildNodes(boolean p);
- method public void setFocused(boolean p);
- method public void setHasBeenAnnotated(boolean p);
- method public void setHasImplicitScrolling(boolean p);
- method public void setHeader(boolean p);
- method public void setHidden(boolean p);
- method public void setHint(String? p);
- method public void setHintOverrides(androidx.ui.core.semantics.SemanticsHintOverrides? p);
- method public void setImage(boolean p);
- method public void setInMutuallyExclusiveGroup(boolean p);
- method public void setLabel(String? p);
- method public void setLiveRegion(boolean p);
method public void setMergingSemanticsOfDescendants(boolean value);
- method public void setNamesRoute(boolean p);
- method public void setObscured(boolean p);
- method public void setScopesRoute(boolean p);
- method public void setScrollExtentMax(Float? p);
- method public void setScrollExtentMin(Float? p);
- method public void setScrollPosition(Float? p);
- method public void setSelected(boolean p);
method public void setSemanticBoundary(boolean value);
- method public void setTestTag(String? p);
- method public void setTextDirection(androidx.ui.text.style.TextDirection? p);
- method public void setTextField(boolean p);
- method public void setTextSelection(androidx.ui.text.TextSelection? p);
- method public void setToggled(Boolean? value);
- method public void setValue(String? p);
- property public final java.util.List<androidx.ui.core.semantics.SemanticsAction<?>> actions;
property public final boolean explicitChildNodes;
property public final boolean hasBeenAnnotated;
- property public final boolean hasImplicitScrolling;
- property public final String? hint;
- property public final androidx.ui.core.semantics.SemanticsHintOverrides? hintOverrides;
- property public final boolean isBlockingSemanticsOfPreviouslyPaintedNodes;
- property public final boolean isButton;
- property public final Boolean? isChecked;
- property public final Boolean? isEnabled;
- property public final boolean isFocused;
- property public final boolean isHeader;
- property public final boolean isHidden;
- property public final boolean isImage;
- property public final boolean isInMutuallyExclusiveGroup;
property public final boolean isMergingSemanticsOfDescendants;
- property public final boolean isObscured;
- property public final boolean isSelected;
property public final boolean isSemanticBoundary;
- property public final boolean isTextField;
- property public final Boolean? isToggled;
- property public final String? label;
- property public final boolean liveRegion;
- property public final boolean namesRoute;
- property public final boolean scopesRoute;
- property public final Float? scrollExtentMax;
- property public final Float? scrollExtentMin;
- property public final Float? scrollPosition;
- property public final String? testTag;
- property public final androidx.ui.text.style.TextDirection? textDirection;
- property public final androidx.ui.text.TextSelection? textSelection;
- property public final String? value;
}
public final class SemanticsConfigurationKt {
ctor public SemanticsConfigurationKt();
- }
-
- public enum SemanticsFlag {
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag HasCheckedState;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag HasEnabledState;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag HasImplicitScrolling;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag HasToggledState;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag IsButton;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag IsChecked;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag IsEnabled;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag IsFocused;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag IsHeader;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag IsHidden;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag IsImage;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag IsInMutuallyExclusiveGroup;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag IsLiveRegion;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag IsObscured;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag IsSelected;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag IsTextField;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag IsToggled;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag NamesRoute;
- enum_constant public static final androidx.ui.core.semantics.SemanticsFlag ScopesRoute;
- field public static final androidx.ui.core.semantics.SemanticsFlag.Companion! Companion;
- }
-
- public static final class SemanticsFlag.Companion {
- method public java.util.Map<java.lang.Integer,androidx.ui.core.semantics.SemanticsFlag> getValues();
- property public final java.util.Map<java.lang.Integer,androidx.ui.core.semantics.SemanticsFlag> values;
+ method public static <T> T? getOrNull(androidx.ui.core.semantics.SemanticsConfiguration, androidx.ui.semantics.SemanticsPropertyKey<T> key);
}
public final class SemanticsHintOverrides {
@@ -517,7 +341,6 @@
method public androidx.ui.engine.geometry.Rect? getParentPaintClipRect();
method public androidx.ui.engine.geometry.Rect? getParentSemanticsClipRect();
method public androidx.ui.engine.geometry.Rect getRect();
- method public boolean isDifferentFromCurrentSemanticAnnotation(androidx.ui.core.semantics.SemanticsConfiguration config);
method public boolean isInvisible();
method public boolean isMergedIntoParent();
method public boolean isPartOfNodeMerging();
@@ -557,7 +380,6 @@
public final class SemanticsOwner {
ctor public SemanticsOwner();
- method public void performAction(int id, androidx.ui.core.semantics.SemanticsActionType<?> action, Object? args = null);
}
}
@@ -571,3 +393,23 @@
}
+package androidx.ui.internal {
+
+ public final class Unicode {
+ field public static final String ALM = "\u061c";
+ field public static final String FSI = "\u2068";
+ field public static final androidx.ui.internal.Unicode! INSTANCE;
+ field public static final String LRE = "\u202a";
+ field public static final String LRI = "\u2066";
+ field public static final String LRM = "\u200e";
+ field public static final String LRO = "\u202d";
+ field public static final String PDF = "\u202c";
+ field public static final String PDI = "\u2069";
+ field public static final String RLE = "\u202b";
+ field public static final String RLI = "\u2067";
+ field public static final String RLM = "\u200f";
+ field public static final String RLO = "\u202e";
+ }
+
+}
+
diff --git a/ui/ui-platform/build.gradle b/ui/ui-platform/build.gradle
index b6a9b9a..651fc72 100644
--- a/ui/ui-platform/build.gradle
+++ b/ui/ui-platform/build.gradle
@@ -54,6 +54,8 @@
androidTestImplementation MOCKITO_KOTLIN, {
exclude group: 'org.mockito' // to keep control on the mockito version
}
+
+ implementation "androidx.core:core:1.0.0"
}
androidx {
diff --git a/ui/ui-platform/src/main/java/androidx/ui/core/ComponentNodes.kt b/ui/ui-platform/src/main/java/androidx/ui/core/ComponentNodes.kt
index 1bdfbc3..39b987c 100644
--- a/ui/ui-platform/src/main/java/androidx/ui/core/ComponentNodes.kt
+++ b/ui/ui-platform/src/main/java/androidx/ui/core/ComponentNodes.kt
@@ -15,11 +15,9 @@
*/
package androidx.ui.core
-import androidx.ui.core.semantics.SemanticsAction
-import androidx.ui.core.semantics.SemanticsConfiguration
-import androidx.ui.text.style.TextDirection
-import androidx.ui.painting.Canvas
import androidx.compose.Emittable
+import androidx.ui.core.semantics.SemanticsConfiguration
+import androidx.ui.painting.Canvas
import androidx.ui.engine.geometry.Shape
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
@@ -569,6 +567,8 @@
}
class SemanticsComponentNode(
+ // TODO(ryanmentley): probably take away these default values
+ semanticsConfiguration: SemanticsConfiguration = SemanticsConfiguration(),
/**
* If [container] is true, this widget will introduce a new
* node in the semantics tree. Otherwise, the semantics will be
@@ -597,166 +597,19 @@
* This setting is often used in combination with [SemanticsConfiguration.isSemanticBoundary]
* to create semantic boundaries that are either writable or not for children.
*/
- explicitChildNodes: Boolean = false,
- enabled: Boolean? = null,
- checked: Boolean? = null,
- selected: Boolean? = null,
- button: Boolean? = null,
- header: Boolean? = null,
- textField: Boolean? = null,
- focused: Boolean? = null,
- inMutuallyExclusiveGroup: Boolean? = null,
- obscured: Boolean? = null,
- scopesRoute: Boolean? = null,
- namesRoute: Boolean? = null,
- hidden: Boolean? = null,
- label: String? = null,
- value: String? = null,
- hint: String? = null,
- textDirection: TextDirection? = null,
- testTag: String? = null,
- actions: List<SemanticsAction<*>> = emptyList()
-
+ explicitChildNodes: Boolean = false
) : ComponentNode() {
private var needsSemanticsUpdate = true
- private var cachedSemanticsConfiguration: SemanticsConfiguration? = null
- val semanticsConfiguration: SemanticsConfiguration
- get() {
- if (cachedSemanticsConfiguration == null) {
- cachedSemanticsConfiguration = generateNodeLocalSemanticsConfiguration()
- }
- return cachedSemanticsConfiguration!!
- }
-
- private fun generateNodeLocalSemanticsConfiguration(): SemanticsConfiguration? {
- return SemanticsConfiguration().also { config ->
- // TODO(ryanmentley): add more once we enable them in the API
- enabled?.let { enabled ->
- config.isEnabled = enabled
- }
- checked?.let { checked ->
- config.isChecked = checked
- }
- selected?.let { selected ->
- config.isSelected = selected
- }
- button?.let { button ->
- config.isButton = button
- }
- inMutuallyExclusiveGroup?.let { inMutuallyExclusiveGroup ->
- config.isInMutuallyExclusiveGroup = inMutuallyExclusiveGroup
- }
- hidden?.let { hidden ->
- config.isHidden = hidden
- }
- label?.let { label ->
- config.label = label
- }
- value?.let { value ->
- config.value = value
- }
- textDirection?.let { textDirection ->
- config.textDirection = textDirection
- }
- testTag?.let { testTag ->
- config.testTag = testTag
- }
- config.actions = actions
- }
- }
var container: Boolean by InvalidatingProperty(container)
var explicitChildNodes: Boolean by InvalidatingProperty(explicitChildNodes)
- /**
- * If non-null, sets the [SemanticsNode.hasCheckedState] semantic to true and
- * the [SemanticsNode.isChecked] semantic to the given value.
- */
- var checked: Boolean? by InvalidatingProperty(checked)
-
- /**
- * If non-null, sets the [SemanticsNode.hasEnabledState] semantic to true and
- * the [SemanticsNode.isEnabled] semantic to the given value.
- */
- var enabled: Boolean? by InvalidatingProperty(enabled)
-
- /**
- * If non-null, sets the [SemanticsNode.isSelected] semantic to the given
- * value.
- */
- var selected: Boolean? by InvalidatingProperty(selected)
-
- /** If non-null, sets the [SemanticsNode.isButton] semantic to the given value. */
- var button: Boolean? by InvalidatingProperty(button)
-
- /** If non-null, sets the [SemanticsNode.isHeader] semantic to the given value. */
- var header: Boolean? by InvalidatingProperty(header)
-
- /** If non-null, sets the [SemanticsNode.isTextField] semantic to the given value. */
- var textField: Boolean? by InvalidatingProperty(textField)
-
- /** If non-null, sets the [SemanticsNode.isFocused] semantic to the given value. */
- var focused: Boolean? by InvalidatingProperty(focused)
-
- /**
- * If non-null, sets the [SemanticsNode.isInMutuallyExclusiveGroup] semantic
- * to the given value.
- */
- var inMutuallyExclusiveGroup: Boolean? by InvalidatingProperty(inMutuallyExclusiveGroup)
-
- /**
- * If non-null, sets the [SemanticsNode.isObscured] semantic to the given
- * value.
- */
- var obscured: Boolean? by InvalidatingProperty(obscured)
-
- /** If non-null, sets the [SemanticsNode.scopesRoute] semantic to the give value. */
- var scopesRoute: Boolean? by InvalidatingProperty(scopesRoute)
-
- /** If non-null, sets the [SemanticsNode.namesRoute] semantic to the give value. */
- var namesRoute: Boolean? by InvalidatingProperty(namesRoute)
-
- /**
- * If non-null, sets the [SemanticsNode.isHidden] semantic to the given
- * value.
- */
- var hidden: Boolean? by InvalidatingProperty(hidden)
-
- /**
- * If non-null, sets the [SemanticsNode.label] semantic to the given value.
- *
- * The reading direction is given by [textDirection].
- */
- var label: String? by InvalidatingProperty(label)
-
- /**
- * If non-null, sets the [SemanticsNode.value] semantic to the given value.
- *
- * The reading direction is given by [textDirection].
- */
- var value: String? by InvalidatingProperty(value)
-
- /**
- * If non-null, sets the [SemanticsNode.hint] semantic to the given value.
- *
- * The reading direction is given by [textDirection].
- */
- var hint: String? by InvalidatingProperty(hint)
-
- /**
- * If non-null, sets the [SemanticsNode.textDirection] semantic to the given value.
- *
- * This must not be null if [label], [hint], or [value] are not null.
- */
- var textDirection: TextDirection? by InvalidatingProperty(textDirection)
-
- var testTag: String? by InvalidatingProperty(testTag)
-
- var actions: List<SemanticsAction<*>> by InvalidatingProperty(actions)
+ // TODO(ryanmentley): this should be smarter and invalidate less
+ var semanticsConfiguration: SemanticsConfiguration
+ by InvalidatingProperty(semanticsConfiguration)
internal fun markNeedsSemanticsUpdate() {
- cachedSemanticsConfiguration = null
needsSemanticsUpdate = true
}
}
@@ -777,7 +630,12 @@
* @property key The key object used to identify the key
* @property value The value of the data being stored in the hierarchy
*/
-class DataNode<T>(val key: DataNodeKey<T>, var value: T) : ComponentNode()
+class DataNode<T>(val key: DataNodeKey<T>, var value: T) : ComponentNode() {
+ override fun attach(owner: Owner) {
+ super.attach(owner)
+ parentLayoutNode?.requestRemeasure()
+ }
+}
/**
* Returns [ComponentNode.owner] or throws if it is null.
diff --git a/ui/ui-platform/src/main/java/androidx/ui/core/input/TextInputServiceAndroid.kt b/ui/ui-platform/src/main/java/androidx/ui/core/input/TextInputServiceAndroid.kt
index 0989de0..e77e070 100644
--- a/ui/ui-platform/src/main/java/androidx/ui/core/input/TextInputServiceAndroid.kt
+++ b/ui/ui-platform/src/main/java/androidx/ui/core/input/TextInputServiceAndroid.kt
@@ -24,7 +24,7 @@
import android.view.inputmethod.InputMethodManager
import androidx.ui.engine.geometry.Rect
import androidx.ui.input.EditOperation
-import androidx.ui.input.EditorState
+import androidx.ui.input.EditorModel
import androidx.ui.input.ImeAction
import androidx.ui.input.InputEventListener
import androidx.ui.input.KeyboardType
@@ -86,14 +86,14 @@
fun isEditorFocused(): Boolean = editorHasFocus
override fun startInput(
- initState: EditorState,
+ initModel: EditorModel,
keyboardType: KeyboardType,
imeAction: ImeAction,
onEditCommand: (List<EditOperation>) -> Unit,
onImeActionPerformed: (ImeAction) -> Unit
) {
editorHasFocus = true
- state = initState.toInputState()
+ state = initModel.toInputState()
this.keyboardType = keyboardType
this.imeAction = imeAction
this.onEditCommand = onEditCommand
@@ -118,8 +118,8 @@
imm.showSoftInput(view, 0)
}
- override fun onStateUpdated(state: EditorState) {
- this.state = state.toInputState()
+ override fun onStateUpdated(model: EditorModel) {
+ this.state = model.toInputState()
ic?.updateInputState(this.state, imm, view)
}
@@ -178,7 +178,7 @@
}
}
-private fun EditorState.toInputState(): InputState =
+private fun EditorModel.toInputState(): InputState =
InputState(
text = text, // TODO(nona): call toString once AnnotatedString is in use.
selection = selection,
diff --git a/ui/ui-platform/src/main/java/androidx/ui/core/semantics/SemanticsConfiguration.kt b/ui/ui-platform/src/main/java/androidx/ui/core/semantics/SemanticsConfiguration.kt
index dc320d9..b5daf47 100644
--- a/ui/ui-platform/src/main/java/androidx/ui/core/semantics/SemanticsConfiguration.kt
+++ b/ui/ui-platform/src/main/java/androidx/ui/core/semantics/SemanticsConfiguration.kt
@@ -16,11 +16,8 @@
package androidx.ui.core.semantics
-import androidx.ui.core.Unicode
-import androidx.ui.text.TextSelection
-import androidx.ui.text.style.TextDirection
-import kotlin.properties.ReadWriteProperty
-import kotlin.reflect.KProperty
+import androidx.ui.semantics.SemanticsPropertyKey
+import androidx.ui.semantics.SemanticsPropertyReceiver
/**
* Describes the semantic information associated with the owning component
@@ -28,7 +25,47 @@
* The information provided in the configuration is used to to generate the
* semantics tree.
*/
-class SemanticsConfiguration {
+class SemanticsConfiguration : SemanticsPropertyReceiver,
+ Iterable<Map.Entry<SemanticsPropertyKey<*>, Any?>> {
+
+ private val props: MutableMap<SemanticsPropertyKey<*>, Any?> = mutableMapOf()
+
+ /**
+ * Retrieves the value for the given property, if one has been set.
+ * If a value has not been set, throws [IllegalStateException]
+ */
+ // Unavoidable, guaranteed by [set]
+ @Suppress("UNCHECKED_CAST")
+ operator fun <T> get(key: SemanticsPropertyKey<T>): T {
+ return props.getOrElse(key) {
+ throw IllegalStateException("Key not present: $key - consider getOrElse or getOrNull")
+ } as T
+ }
+
+ // Unavoidable, guaranteed by [set]
+ @Suppress("UNCHECKED_CAST")
+ fun <T> getOrElse(key: SemanticsPropertyKey<T>, defaultValue: () -> T): T {
+ return props.getOrElse(key, defaultValue) as T
+ }
+
+ // Unavoidable, guaranteed by [set]
+ @Suppress("UNCHECKED_CAST")
+ fun <T> getOrElseNullable(key: SemanticsPropertyKey<T>, defaultValue: () -> T?): T? {
+ return props.getOrElse(key, defaultValue) as T?
+ }
+
+ override fun iterator(): Iterator<Map.Entry<SemanticsPropertyKey<*>, Any?>> {
+ return props.iterator()
+ }
+
+ override fun <T> set(key: SemanticsPropertyKey<T>, value: T) {
+ props[key] = value
+ }
+
+ operator fun <T> contains(key: SemanticsPropertyKey<T>): Boolean {
+ return props.containsKey(key)
+ }
+
// SEMANTIC BOUNDARY BEHAVIOR
/**
@@ -70,90 +107,6 @@
var explicitChildNodes = false
/**
- * Whether the owning component makes other components previously
- * painted within the same semantic boundary unreachable for accessibility
- * purposes.
- *
- * If set to true, the semantic information for all siblings and cousins of
- * this node, that are earlier in a depth-first pre-order traversal, are
- * dropped from the semantics tree up until a semantic boundary (as defined
- * by [isSemanticBoundary]) is reached.
- *
- * If [isSemanticBoundary] and [isBlockingSemanticsOfPreviouslyPaintedNodes]
- * is set on the same node, all previously painted siblings and cousins up
- * until the next ancestor that is a semantic boundary are dropped.
- *
- * Paint order as established by [visitChildrenForSemantics] is used to
- * determine if a node is previous to this one.
- */
- var isBlockingSemanticsOfPreviouslyPaintedNodes = false
-
- // SEMANTIC ANNOTATIONS
- // These will end up on [SemanticNode]s generated from [SemanticsConfiguration]s.
-
- /**
- * Whether this configuration is empty.
- *
- * An empty configuration doesn't contain any semantic information that it
- * wants to contribute to the semantics tree.
- */
- var hasBeenAnnotated: Boolean = false
-
- /**
- * The actions (with associated action handlers) that this configuration
- * would like to contribute to the semantics tree.
- *
- * See also:
- *
- * * [addAction] to add an action.
- */
- internal var _actions: MutableMap<SemanticsActionType<*>,
- SemanticsAction<*>> = mutableMapOf()
-
- var actions: List<SemanticsAction<*>>
- get() = _actions.values.toList()
- set(value) {
- // TODO(ryanmentley): This is naive, we could be smarter
- actionsAsBits = 0
- _actions.clear()
- for (action in value) {
- addAction(action)
- }
- }
-
- private var actionsAsBits: Int = 0
-
- /**
- * Adds an `action` to the semantics tree.
- *
- * The provided `handler` is called to respond to the user triggered
- * `action`.
- */
- private fun addAction(action: SemanticsAction<*>) {
- _actions[action.type] = action
- actionsAsBits = actionsAsBits or action.type.bitmask
- hasBeenAnnotated = true
- }
-
- /**
- * A property that marks the configuration as having been annotated
- * (i.e., containing information)
- */
- private class AnnotationProperty<T>(initialValue: T) :
- ReadWriteProperty<SemanticsConfiguration, T> {
- private var value = initialValue
-
- override fun getValue(thisRef: SemanticsConfiguration, property: KProperty<*>): T {
- return value
- }
-
- override fun setValue(thisRef: SemanticsConfiguration, property: KProperty<*>, value: T) {
- this.value = value
- thisRef.hasBeenAnnotated = true
- }
- }
-
- /**
* Whether the semantic information provided by the owning component and
* all of its descendants should be treated as one logical entity.
*
@@ -171,337 +124,49 @@
}
field = value
- hasBeenAnnotated = true
}
- var label: String? by AnnotationProperty("")
-
- var value: String? by AnnotationProperty("")
-
- // TODO(ryanmentley): Added in API 26 and backported via androidx, integrate this
- /**
- * A brief description of the result of performing an action on this node.
- *
- * On iOS this is used for the `accessibilityHint` property defined in the
- * `UIAccessibility` Protocol. On Android it is concatenated together with
- * [label] and [value] in the following order: [value], [label], [hint].
- * The concatenated value is then used as the `Text` description.
- *
- * The reading direction is given by [textDirection].
- */
- var hint: String? by AnnotationProperty("")
+ // SEMANTIC ANNOTATIONS
+ // These will end up on [SemanticNode]s generated from [SemanticsConfiguration]s.
/**
- * Provides hint values which override the default hints on supported
- * platforms.
+ * Whether this configuration is empty.
+ *
+ * An empty configuration doesn't contain any semantic information that it
+ * wants to contribute to the semantics tree.
*/
- var hintOverrides: SemanticsHintOverrides? by AnnotationProperty(null)
-
- /**
- * Whether the semantics node is the root of a subtree for which values
- * should be announced.
- *
- * See also:
- * * [SemanticsFlag.ScopesRoute], for a full description of route scoping.
- */
- var scopesRoute: Boolean by SimpleFlagProperty(SemanticsFlag.ScopesRoute)
-
- /**
- * Whether the semantics node contains the label of a route.
- *
- * See also:
- * * [SemanticsFlag.NamesRoute], for a full description of route naming.
- */
- var namesRoute: Boolean by SimpleFlagProperty(SemanticsFlag.NamesRoute)
-
- /** Whether the semantics node represents an image. */
- var isImage: Boolean by SimpleFlagProperty(SemanticsFlag.IsImage)
-
- /**
- * Whether the semantics node is a live region.
- *
- * On Android, when a live region semantics node is first created TalkBack
- * will make a polite announcement of the current label. This announcement
- * occurs even if the node is not focused. Subsequent polite announcements
- * can be made by sending a [UpdateLiveRegionEvent] semantics event. The
- * announcement will only be made if the node's label has changed since the
- * last update.
- *
- * An example of a live region is the [Snackbar] widget. When it appears
- * on the screen it may be difficult to focus to read the label. A live
- * region causes an initial polite announcement to be generated
- * automatically.
- *
- * See also:
- *
- * * [SemanticsFlag.IsLiveRegion], the semantics flag that this setting controls.
- */
- var liveRegion: Boolean by SimpleFlagProperty(SemanticsFlag.IsLiveRegion)
-
- var textDirection: TextDirection? by AnnotationProperty(null)
-
- var isSelected: Boolean by SimpleFlagProperty(
- SemanticsFlag.IsSelected
- )
-
- var isEnabled: Boolean?
- get() {
- return if (hasFlag(SemanticsFlag.HasEnabledState)) {
- hasFlag(SemanticsFlag.IsEnabled)
- } else {
- null
- }
- }
- set(value) {
- setFlag(SemanticsFlag.HasEnabledState, value != null)
- setFlag(SemanticsFlag.IsEnabled, value ?: false)
- }
-
- var isChecked: Boolean?
- get() {
- return if (hasFlag(SemanticsFlag.HasCheckedState)) {
- hasFlag(SemanticsFlag.IsChecked)
- } else {
- null
- }
- }
- set(value) {
- setFlag(SemanticsFlag.HasCheckedState, value != null)
- setFlag(SemanticsFlag.IsChecked, value ?: false)
- }
-
- /**
- * If this node has Boolean state that can be controlled by the user, whether
- * that state is on or off, corresponding to true and false, respectively.
- *
- * Do not call the setter for this field if the owning component doesn't
- * have on/off state that can be controlled by the user.
- *
- * The getter returns null if the owning component does not have
- * on/off state.
- */
- var isToggled: Boolean?
- get() {
- return if (hasFlag(SemanticsFlag.HasToggledState)) {
- hasFlag(SemanticsFlag.IsToggled)
- } else {
- null
- }
- }
- set(value) {
- setFlag(SemanticsFlag.HasToggledState, value != null)
- setFlag(SemanticsFlag.IsToggled, value ?: false)
- }
-
- var isInMutuallyExclusiveGroup: Boolean
- by SimpleFlagProperty(SemanticsFlag.IsInMutuallyExclusiveGroup)
-
- /** Whether the component represented by this configuration currently holds the user's focus. */
- var isFocused: Boolean by SimpleFlagProperty(SemanticsFlag.IsFocused)
-
- var isButton: Boolean by SimpleFlagProperty(SemanticsFlag.IsButton)
-
- /** Whether the component represented by this configuration is a header (true) or not (false). */
- var isHeader: Boolean by SimpleFlagProperty(SemanticsFlag.IsHeader)
-
- /**
- * Whether the the component represented by this configuration is considered hidden.
- *
- * Hidden elements are currently not visible on screen. They may be covered
- * by other elements or positioned outside of the visible area of a viewport.
- *
- * Hidden elements cannot gain accessibility focus though regular touch. The
- * only way they can be focused is by moving the focus to them via linear
- * navigation.
- *
- * Platforms are free to completely ignore hidden elements and new platforms
- * are encouraged to do so.
- *
- * Instead of marking an element as hidden it should usually be excluded from
- * the semantics tree altogether. Hidden elements are only included in the
- * semantics tree to work around platform limitations and they are mainly
- * used to implement accessibility scrolling on iOS.
- */
- var isHidden: Boolean by SimpleFlagProperty(SemanticsFlag.IsHidden)
-
- /** Whether the owning component is a text field. */
- var isTextField: Boolean by SimpleFlagProperty(SemanticsFlag.IsTextField)
-
- /**
- * Whether the [value] should be obscured.
- *
- * This option is usually set in combination with [isTextField] to indicate
- * that the text field contains a password (or other sensitive information).
- * Doing so instructs screen readers to not read out the [value].
- */
- var isObscured: Boolean by SimpleFlagProperty(SemanticsFlag.IsObscured)
-
- /**
- * Whether the platform can scroll the semantics node when the user attempts
- * to move focus to an offscreen child.
- *
- * For example, a [ListView] widget has implicit scrolling so that users can
- * easily move to the next visible set of children. A [TabBar] widget does
- * not have implicit scrolling, so that users can navigate into the tab
- * body when reaching the end of the tab bar.
- */
- var hasImplicitScrolling: Boolean by SimpleFlagProperty(SemanticsFlag.HasImplicitScrolling)
-
- /**
- * The currently selected text (or the position of the cursor) within [value]
- * if this node represents a text field.
- */
- var textSelection: TextSelection? by AnnotationProperty(null)
-
- /**
- * Indicates the current scrolling position in logical pixels if the node is
- * scrollable.
- *
- * The properties [scrollExtentMin] and [scrollExtentMax] indicate the valid
- * in-range values for this property. The value for [scrollPosition] may
- * (temporarily) be outside that range, e.g. during an overscroll.
- *
- * See also:
- *
- * * [ScrollPosition.pixels], from where this value is usually taken.
- */
- var scrollPosition: Float? by AnnotationProperty(null)
-
- /**
- * Indicates the maximum in-range value for [scrollPosition] if the node is
- * scrollable.
- *
- * This value may be infinity if the scroll is unbound.
- *
- * See also:
- *
- * * [ScrollPosition.maxScrollExtent], from where this value is usually taken.
- */
- var scrollExtentMax: Float? by AnnotationProperty(null)
-
- /**
- * Indicates the minimum in-range value for [scrollPosition] if the node is
- * scrollable.
- *
- * This value may be infinity if the scroll is unbound.
- *
- * See also:
- *
- * * [ScrollPosition.minScrollExtent], from where this value is usually taken.
- */
- var scrollExtentMin: Float? by AnnotationProperty(null)
-
- var testTag: String? by AnnotationProperty(null)
-
- // INTERNAL FLAG MANAGEMENT
-
- private var flags = 0
-
- private fun setFlag(flag: SemanticsFlag, value: Boolean) {
- flags = if (value) {
- flags or flag.bitmask
- } else {
- flags and flag.bitmask.inv()
- }
- hasBeenAnnotated = true
- }
-
- private fun hasFlag(flag: SemanticsFlag): Boolean = (flags and flag.bitmask) != 0
-
- /**
- * A property that provides an abstraction over [setFlag] and [hasFlag]
- */
- private class SimpleFlagProperty(
- val flag: SemanticsFlag
- ) : ReadWriteProperty<SemanticsConfiguration, Boolean> {
- override fun getValue(
- thisRef: SemanticsConfiguration,
- property: KProperty<*>
- ): Boolean {
- return thisRef.hasFlag(flag)
- }
-
- override fun setValue(
- thisRef: SemanticsConfiguration,
- property: KProperty<*>,
- value: Boolean
- ) {
- thisRef.setFlag(flag, value)
- }
- }
+ val hasBeenAnnotated: Boolean
+ get() = props.isEmpty()
// CONFIGURATION COMBINATION LOGIC
/**
- * Whether this configuration is compatible with the provided `other` configuration.
- *
- * Two configurations are said to be compatible if they can be added to the same [SemanticsNode]
- * without losing any semantics information.
- */
- fun isCompatibleWith(other: SemanticsConfiguration?): Boolean {
- if (other == null || !other.hasBeenAnnotated || !hasBeenAnnotated) {
- return true
- }
- if (actionsAsBits and other.actionsAsBits != 0) {
- return false
- }
- if ((flags and other.flags) != 0) {
- return false
- }
- if (!value.isNullOrEmpty() && !value.isNullOrEmpty()) {
- return false
- }
- return true
- }
-
- /**
* Absorb the semantic information from `other` into this configuration.
*
* This adds the semantic information of both configurations and saves the result in this
* configuration.
*
* Only configurations that have [explicitChildNodes] set to false can absorb other
- * configurations. The [other] configuration must be compatible as determined by
- * [isCompatibleWith].
+ * configurations. The [other] configuration must not contain any properties that cannot be
+ * merged into this configuration.
*/
internal fun absorb(other: SemanticsConfiguration) {
assert(!explicitChildNodes)
- assert(isCompatibleWith((other)))
if (!other.hasBeenAnnotated) {
return
}
- _actions.putAll(other._actions)
- actionsAsBits = actionsAsBits or other.actionsAsBits
- flags = flags or other.flags
-
- textSelection = textSelection ?: other.textSelection
- scrollPosition = scrollPosition ?: other.scrollPosition
- scrollExtentMax = scrollExtentMax ?: other.scrollExtentMax
- scrollExtentMin = scrollExtentMin ?: other.scrollExtentMin
- hintOverrides = hintOverrides ?: other.hintOverrides
-
- textDirection = textDirection ?: other.textDirection
-
- label = concatStrings(
- thisString = label,
- thisTextDirection = textDirection,
- otherString = other.label,
- otherTextDirection = other.textDirection
- )
- if (value.isNullOrEmpty()) {
- value = other.value
+ for (entry in other.props) {
+ val key = entry.key
+ if (props.containsKey(key)) {
+ @Suppress("UNCHECKED_CAST")
+ key as SemanticsPropertyKey<Any?>
+ props[key] = key.merge(props[key], entry.value)
+ } else {
+ props[key] = entry.value
+ }
}
-
- hint = concatStrings(
- thisString = hint,
- thisTextDirection = textDirection,
- otherString = other.hint,
- otherTextDirection = other.textDirection
- )
-
- hasBeenAnnotated = hasBeenAnnotated or other.hasBeenAnnotated
}
/** Returns an exact copy of this configuration. */
@@ -509,74 +174,12 @@
val copy = SemanticsConfiguration()
copy.isSemanticBoundary = isSemanticBoundary
copy.explicitChildNodes = explicitChildNodes
- copy.isBlockingSemanticsOfPreviouslyPaintedNodes =
- isBlockingSemanticsOfPreviouslyPaintedNodes
copy.isMergingSemanticsOfDescendants = isMergingSemanticsOfDescendants
- copy.textDirection = textDirection
- copy.label = label
- copy.value = value
- copy.hint = hint
- copy.hintOverrides = hintOverrides
- copy.flags = flags
- copy.textSelection = textSelection
- copy.scrollPosition = scrollPosition
- copy.scrollExtentMax = scrollExtentMax
- copy.scrollExtentMin = scrollExtentMin
- copy.actionsAsBits = actionsAsBits
- copy._actions.putAll(_actions)
- // Do this last so it's not overwritten by setting the other properties
- copy.hasBeenAnnotated = hasBeenAnnotated
+ copy.props.putAll(props)
return copy
}
-
- /**
- * Checks that all properties are the same and that the set of actions (though not necessarily
- * the exact functions implementing them) are the same
- */
- internal fun isSemanticallyDifferentFrom(other: SemanticsConfiguration): Boolean {
- return this.label != other.label ||
- this.hint != other.hint ||
- this.value != other.value ||
- this.flags != other.flags ||
- this.textDirection != other.textDirection ||
- this.textSelection != other.textSelection ||
- this.scrollPosition != other.scrollPosition ||
- this.scrollExtentMax != other.scrollExtentMax ||
- this.scrollExtentMin != other.scrollExtentMin ||
- this.actionsAsBits != other.actionsAsBits
- }
-
- internal fun updateWith(sourceConfig: SemanticsConfiguration) {
- this.label = sourceConfig.label
- this.value = sourceConfig.value
- this.hint = sourceConfig.hint
- this.flags = sourceConfig.flags
- this.textDirection = sourceConfig.textDirection
- this._actions = sourceConfig._actions
- this.actionsAsBits = sourceConfig.actionsAsBits
- this.textSelection = sourceConfig.textSelection
- this.scrollPosition = sourceConfig.scrollPosition
- this.scrollExtentMax = sourceConfig.scrollExtentMax
- this.scrollExtentMin = sourceConfig.scrollExtentMin
- }
}
-private fun concatStrings(
- thisString: String?,
- otherString: String?,
- thisTextDirection: TextDirection?,
- otherTextDirection: TextDirection?
-): String? {
- if (otherString.isNullOrEmpty())
- return thisString
- var nestedLabel = otherString
- if (thisTextDirection != otherTextDirection && otherTextDirection != null) {
- nestedLabel = when (otherTextDirection) {
- TextDirection.Rtl -> "${Unicode.RLE}$nestedLabel${Unicode.PDF}"
- TextDirection.Ltr -> "${Unicode.LRE}$nestedLabel${Unicode.PDF}"
- }
- }
- if (thisString.isNullOrEmpty())
- return nestedLabel
- return "$thisString\n$nestedLabel"
-}
\ No newline at end of file
+fun <T> SemanticsConfiguration.getOrNull(key: SemanticsPropertyKey<T>): T? {
+ return getOrElseNullable(key) { null }
+}
diff --git a/ui/ui-platform/src/main/java/androidx/ui/core/semantics/SemanticsFlag.kt b/ui/ui-platform/src/main/java/androidx/ui/core/semantics/SemanticsFlag.kt
deleted file mode 100644
index 24f72e7..0000000
--- a/ui/ui-platform/src/main/java/androidx/ui/core/semantics/SemanticsFlag.kt
+++ /dev/null
@@ -1,275 +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.ui.core.semantics
-
-// TODO(ryanmentley): b/129900044: Could this be an inline class and eliminate a lot of the
-// properties on SemanticsConfiguration?
-/** A Boolean value that can be associated with a [SemanticsNode]. */
-enum class SemanticsFlag {
- /**
- * The semantics node has the quality of either being "checked" or "unchecked".
- *
- * This flag is mutually exclusive with [HasToggledState].
- *
- * For example, a checkbox or a radio button widget has checked state.
- *
- * See also:
- *
- * * [SemanticsFlag.IsChecked], which controls whether the node is "checked" or "unchecked".
- */
- HasCheckedState,
-
- /**
- * Whether a semantics node that [HasCheckedState] is checked.
- *
- * If true, the semantics node is "checked". If false, the semantics node is
- * "unchecked".
- *
- * For example, if a checkbox has a visible checkmark, [IsChecked] is true.
- *
- * See also:
- *
- * * [SemanticsFlag.HasCheckedState], which enables a checked state.
- */
- IsChecked,
-
- /**
- * Whether a semantics node is selected.
- *
- * If true, the semantics node is "selected". If false, the semantics node is
- * "unselected".
- *
- * For example, the active tab in a tab bar has [IsSelected] set to true.
- */
- IsSelected,
-
- /**
- * Whether the semantic node represents a button.
- *
- * Some platforms have special handling for buttons. For example, iOS's VoiceOver provides an
- * additional hint when the focused object is a button.
- */
- // TODO(ryanmentley): Should we remove this?
- IsButton,
-
- /**
- * Whether the semantic node represents a text field.
- *
- * Text fields are announced as such and allow text input via accessibility
- * affordances.
- */
- IsTextField,
-
- /**
- * Whether the semantic node currently holds the user's focus.
- *
- * The focused element is usually the current receiver of keyboard inputs.
- */
- IsFocused,
-
- /**
- * The semantics node has the quality of either being "enabled" or
- * "disabled".
- *
- * For example, a button can be enabled or disabled and therefore has an
- * "enabled" state. Static text is usually neither enabled nor disabled and
- * therefore does not have an "enabled" state.
- */
- HasEnabledState,
-
- /**
- * Whether a semantic node that [HasEnabledState] is currently enabled.
- *
- * A disabled element does not respond to user interaction. For example, a
- * button that currently does not respond to user interaction should be
- * marked as disabled.
- */
- IsEnabled,
-
- /**
- * Whether a semantic node is in a mutually exclusive group.
- *
- * For example, a radio button is in a mutually exclusive group because
- * only one radio button in that group can be marked as [IsChecked].
- */
- IsInMutuallyExclusiveGroup,
-
- /**
- * Whether a semantic node is a header that divides content into sections.
- *
- * For example, headers can be used to divide a list of alphabetically
- * sorted words into the sections A, B, C, etc. as can be found in many
- * address book applications.
- */
- IsHeader,
-
- /**
- * Whether the value of the semantics node is obscured.
- *
- * This is usually used for text fields to indicate that its content
- * is a password or contains other sensitive information.
- */
- IsObscured,
-
- /**
- * Whether the semantics node is the root of a subtree for which a route name
- * should be announced.
- *
- * When a node with this flag is removed from the semantics tree, the
- * framework will select the last in depth-first, paint order node with this
- * flag. When a node with this flag is added to the semantics tree, it is
- * selected automatically, unless there were multiple nodes with this flag
- * added. In this case, the last added node in depth-first, paint order
- * will be selected.
- *
- * From this selected node, the framework will search in depth-first, paint
- * order for the first node with a [NamesRoute] flag and a non-null,
- * non-empty label. The [NamesRoute] and [ScopesRoute] flags may be on the
- * same node. The label of the found node will be announced as an edge
- * transition. If no non-empty, non-null label is found then:
- *
- * * VoiceOver will make a chime announcement.
- * * TalkBack will make no announcement
- *
- * Semantic nodes annotated with this flag are generally not a11y focusable.
- *
- * This is used in widgets such as Routes, Drawers, and Dialogs to
- * communicate significant changes in the visible screen.
- */
- ScopesRoute,
-
- /**
- * Whether the semantics node label is the name of a visually distinct
- * route.
- *
- * This is used by certain widgets like Drawers and Dialogs, to indicate
- * that the node's semantic label can be used to announce an edge triggered
- * semantics update.
- *
- * Semantic nodes annotated with this flag will still recieve a11y focus.
- *
- * Updating this label within the same active route subtree will not cause
- * additional announcements.
- */
- NamesRoute,
-
- /**
- * Whether the semantics node is considered hidden.
- *
- * Hidden elements are currently not visible on screen. They may be covered
- * by other elements or positioned outside of the visible area of a viewport.
- *
- * Hidden elements cannot gain accessibility focus though regular touch. The
- * only way they can be focused is by moving the focus to them via linear
- * navigation.
- *
- * Platforms are free to completely ignore hidden elements and new platforms
- * are encouraged to do so.
- *
- * Instead of marking an element as hidden it should usually be excluded from
- * the semantics tree altogether. Hidden elements are only included in the
- * semantics tree to work around platform limitations and they are mainly
- * used to implement accessibility scrolling on iOS.
- */
- IsHidden,
-
- /**
- * Whether the semantics node represents an image.
- *
- * Both TalkBack and VoiceOver will inform the user the the semantics node
- * represents an image.
- */
- IsImage,
-
- /**
- * Whether the semantics node is a live region.
- *
- * A live region indicates that updates to semantics node are important.
- * Platforms may use this information to make polite announcements to the
- * user to inform them of updates to this node.
- *
- * An example of a live region is a [SnackBar] widget. On Android, A live
- * region causes a polite announcement to be generated automatically, even
- * if the user does not have focus of the widget.
- */
- IsLiveRegion,
-
- /**
- * The semantics node has the quality of either being "on" or "off".
- *
- * This flag is mutually exclusive with [hasCheckedState].
- *
- * For example, a switch has toggled state.
- *
- * See also:
- *
- * * [SemanticsFlag.isToggled], which controls whether the node is "on" or "off".
- */
- HasToggledState,
-
- /**
- * If true, the semantics node is "on". If false, the semantics node is
- * "off".
- *
- * For example, if a switch is in the on position, [IsToggled] is true.
- *
- * See also:
- *
- * * [SemanticsFlag.HasToggledState], which enables a toggled state.
- */
- IsToggled,
-
- /**
- * Whether the platform can scroll the semantics node when the user attempts
- * to move focus to an offscreen child.
- *
- * For example, a [ListView] widget has implicit scrolling so that users can
- * easily move to the next visible set of children. A [TabBar] widget does
- * not have implicit scrolling, so that users can navigate into the tab
- * body when reaching the end of the tab bar.
- */
- HasImplicitScrolling;
-
- companion object {
-
- /**
- * The possible semantics flags.
- *
- * The map's key is the [bitmask] of the flag and the value is the flag itself.
- */
- val values: Map<Int, SemanticsFlag>
-
- init {
- this.values = mutableMapOf()
- for (flag in SemanticsFlag.values()) {
- values[flag.bitmask] = flag
- }
- }
- }
-
- /**
- * The numerical value for this flag.
- *
- * Each flag has one bit set in this bit field.
- */
- internal val bitmask: Int
- get() = 1 shl ordinal
-
- override fun toString(): String {
- return "SemanticsFlag.$name"
- }
-}
\ No newline at end of file
diff --git a/ui/ui-platform/src/main/java/androidx/ui/core/semantics/SemanticsNode.kt b/ui/ui-platform/src/main/java/androidx/ui/core/semantics/SemanticsNode.kt
index 6b413d5..21a27e1 100644
--- a/ui/ui-platform/src/main/java/androidx/ui/core/semantics/SemanticsNode.kt
+++ b/ui/ui-platform/src/main/java/androidx/ui/core/semantics/SemanticsNode.kt
@@ -18,6 +18,8 @@
import androidx.ui.core.ifDebug
import androidx.ui.engine.geometry.Rect
+import androidx.ui.semantics.AccessibilityAction
+import androidx.ui.semantics.SemanticsPropertyKey
/**
* Signature for a function that is called for each [SemanticsNode].
@@ -56,6 +58,7 @@
fun root(
owner: SemanticsOwner
): SemanticsNode {
+ // TODO(ryanmentley): Reconsider whether this should be non-zero, especially for multiple Compose hierachies
val node = SemanticsNode(0)
node.attach(owner)
return node
@@ -349,11 +352,8 @@
}
}
- fun isDifferentFromCurrentSemanticAnnotation(config: SemanticsConfiguration): Boolean {
- return this.config.isSemanticallyDifferentFrom(config) ||
- mergeAllDescendantsIntoThisNode != config.isMergingSemanticsOfDescendants
- }
-
- internal fun canPerformAction(action: SemanticsActionType<*>) =
- this.config._actions.containsKey(action)
+ internal fun <T : Function<Unit>> canPerformAction(
+ action: SemanticsPropertyKey<AccessibilityAction<T>>
+ ) =
+ this.config.contains(action)
}
diff --git a/ui/ui-platform/src/main/java/androidx/ui/core/semantics/SemanticsOwner.kt b/ui/ui-platform/src/main/java/androidx/ui/core/semantics/SemanticsOwner.kt
index 7ad1184..3ac9846 100644
--- a/ui/ui-platform/src/main/java/androidx/ui/core/semantics/SemanticsOwner.kt
+++ b/ui/ui-platform/src/main/java/androidx/ui/core/semantics/SemanticsOwner.kt
@@ -16,6 +16,9 @@
package androidx.ui.core.semantics
+import androidx.ui.semantics.AccessibilityAction
+import androidx.ui.semantics.SemanticsPropertyKey
+
// TODO(ryanmentley): Clean up and integrate this (probably with AndroidCraneView)
/**
@@ -41,10 +44,10 @@
detachedNodes.clear()
}
- private fun getSemanticsActionHandlerForId(
+ private fun <T : Function<Unit>> getSemanticsActionHandlerForId(
id: Int,
- action: SemanticsActionType<*>
- ): SemanticsAction<*>? {
+ action: SemanticsPropertyKey<AccessibilityAction<T>>
+ ): AccessibilityAction<*>? {
var result: SemanticsNode? = nodes[id]
if (result != null && result.isPartOfNodeMerging && !result.canPerformAction(action)) {
result.visitDescendants { node: SemanticsNode ->
@@ -58,19 +61,6 @@
if (result?.canPerformAction(action) != true) {
return null
}
- return result!!.config._actions[action]
- }
-
- /**
- * Asks the [SemanticsNode] with the given id to perform the given action.
- *
- * If the [SemanticsNode] has not indicated that it can perform the action,
- * this function does nothing.
- *
- * If the given `action` requires arguments they need to be passed in via
- * the `args` parameter.
- */
- fun performAction(id: Int, action: SemanticsActionType<*>, args: Any? = null) {
- getSemanticsActionHandlerForId(id, action)?.invokeHandler(args)
+ return result!!.config.getOrNull(action)
}
}
\ No newline at end of file
diff --git a/ui/ui-platform/src/main/java/androidx/ui/core/Unicode.kt b/ui/ui-platform/src/main/java/androidx/ui/internal/Unicode.kt
similarity index 94%
rename from ui/ui-platform/src/main/java/androidx/ui/core/Unicode.kt
rename to ui/ui-platform/src/main/java/androidx/ui/internal/Unicode.kt
index bddf65e..f20fb1b 100644
--- a/ui/ui-platform/src/main/java/androidx/ui/core/Unicode.kt
+++ b/ui/ui-platform/src/main/java/androidx/ui/internal/Unicode.kt
@@ -14,10 +14,8 @@
* limitations under the License.
*/
-package androidx.ui.core
-
-// TODO(ryanmentley): This should be in platform, except we need it from Semantics,
-// and that puts a loop in the dependency graph. It should probably not be exposed as public API.
+package androidx.ui.internal
+// TODO(ryanmentley): This should probably not be exposed as public API.
/**
* Constants for useful Unicode characters.
*
diff --git a/ui/ui-platform/src/test/java/androidx/ui/core/input/TextInputServiceAndroidTest.kt b/ui/ui-platform/src/test/java/androidx/ui/core/input/TextInputServiceAndroidTest.kt
index 575460d..0d32cf82 100644
--- a/ui/ui-platform/src/test/java/androidx/ui/core/input/TextInputServiceAndroidTest.kt
+++ b/ui/ui-platform/src/test/java/androidx/ui/core/input/TextInputServiceAndroidTest.kt
@@ -22,7 +22,7 @@
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import androidx.test.filters.SmallTest
-import androidx.ui.input.EditorState
+import androidx.ui.input.EditorModel
import androidx.ui.input.ImeAction
import androidx.ui.input.KeyboardType
import com.nhaarman.mockitokotlin2.eq
@@ -54,7 +54,7 @@
@Test
fun test_fill_editor_info_text() {
textInputService.startInput(
- EditorState(""),
+ EditorModel(""),
KeyboardType.Text,
ImeAction.Unspecified,
onEditCommand = {},
@@ -71,7 +71,7 @@
@Test
fun test_fill_editor_info_ascii() {
textInputService.startInput(
- EditorState(""),
+ EditorModel(""),
KeyboardType.Ascii,
ImeAction.Unspecified,
onEditCommand = {},
@@ -89,7 +89,7 @@
@Test
fun test_fill_editor_info_number() {
textInputService.startInput(
- EditorState(""),
+ EditorModel(""),
KeyboardType.Number,
ImeAction.Unspecified,
onEditCommand = {},
@@ -106,7 +106,7 @@
@Test
fun test_fill_editor_info_phone() {
textInputService.startInput(
- EditorState(""),
+ EditorModel(""),
KeyboardType.Phone,
ImeAction.Unspecified,
onEditCommand = {},
@@ -123,7 +123,7 @@
@Test
fun test_fill_editor_info_uri() {
textInputService.startInput(
- EditorState(""),
+ EditorModel(""),
KeyboardType.Uri,
ImeAction.Unspecified,
onEditCommand = {},
@@ -141,7 +141,7 @@
@Test
fun test_fill_editor_info_email() {
textInputService.startInput(
- EditorState(""),
+ EditorModel(""),
KeyboardType.Email,
ImeAction.Unspecified,
onEditCommand = {},
@@ -159,7 +159,7 @@
@Test
fun test_fill_editor_info_password() {
textInputService.startInput(
- EditorState(""),
+ EditorModel(""),
KeyboardType.Password,
ImeAction.Unspecified,
onEditCommand = {},
@@ -177,7 +177,7 @@
@Test
fun test_fill_editor_info_number_password() {
textInputService.startInput(
- EditorState(""),
+ EditorModel(""),
KeyboardType.NumberPassword,
ImeAction.Unspecified,
onEditCommand = {},
@@ -195,7 +195,7 @@
@Test
fun test_fill_editor_info_action_none() {
textInputService.startInput(
- EditorState(""),
+ EditorModel(""),
KeyboardType.Ascii,
ImeAction.NoAction,
onEditCommand = {},
@@ -213,7 +213,7 @@
@Test
fun test_fill_editor_info_action_go() {
textInputService.startInput(
- EditorState(""),
+ EditorModel(""),
KeyboardType.Ascii,
ImeAction.Go,
onEditCommand = {},
@@ -231,7 +231,7 @@
@Test
fun test_fill_editor_info_action_next() {
textInputService.startInput(
- EditorState(""),
+ EditorModel(""),
KeyboardType.Ascii,
ImeAction.Next,
onEditCommand = {},
@@ -249,7 +249,7 @@
@Test
fun test_fill_editor_info_action_previous() {
textInputService.startInput(
- EditorState(""),
+ EditorModel(""),
KeyboardType.Ascii,
ImeAction.Previous,
onEditCommand = {},
@@ -267,7 +267,7 @@
@Test
fun test_fill_editor_info_action_search() {
textInputService.startInput(
- EditorState(""),
+ EditorModel(""),
KeyboardType.Ascii,
ImeAction.Search,
onEditCommand = {},
@@ -285,7 +285,7 @@
@Test
fun test_fill_editor_info_action_send() {
textInputService.startInput(
- EditorState(""),
+ EditorModel(""),
KeyboardType.Ascii,
ImeAction.Send,
onEditCommand = {},
@@ -303,7 +303,7 @@
@Test
fun test_fill_editor_info_action_done() {
textInputService.startInput(
- EditorState(""),
+ EditorModel(""),
KeyboardType.Ascii,
ImeAction.Done,
onEditCommand = {},
diff --git a/ui/ui-test/api/1.0.0-alpha01.txt b/ui/ui-test/api/1.0.0-alpha01.txt
index 89f2433..9fd8bf1 100644
--- a/ui/ui-test/api/1.0.0-alpha01.txt
+++ b/ui/ui-test/api/1.0.0-alpha01.txt
@@ -13,14 +13,15 @@
method public static androidx.ui.test.SemanticsNodeInteraction assertIsChecked(androidx.ui.test.SemanticsNodeInteraction);
method public static androidx.ui.test.SemanticsNodeInteraction assertIsHidden(androidx.ui.test.SemanticsNodeInteraction);
method public static androidx.ui.test.SemanticsNodeInteraction assertIsInMutuallyExclusiveGroup(androidx.ui.test.SemanticsNodeInteraction);
- method public static androidx.ui.test.SemanticsNodeInteraction assertIsNotChecked(androidx.ui.test.SemanticsNodeInteraction);
- method public static androidx.ui.test.SemanticsNodeInteraction assertIsNotSelected(androidx.ui.test.SemanticsNodeInteraction);
method public static androidx.ui.test.SemanticsNodeInteraction assertIsSelected(androidx.ui.test.SemanticsNodeInteraction);
+ method public static androidx.ui.test.SemanticsNodeInteraction assertIsUnchecked(androidx.ui.test.SemanticsNodeInteraction);
+ method public static androidx.ui.test.SemanticsNodeInteraction assertIsUnselected(androidx.ui.test.SemanticsNodeInteraction);
method public static androidx.ui.test.SemanticsNodeInteraction assertIsVisible(androidx.ui.test.SemanticsNodeInteraction);
method public static void assertNoLongerExists(androidx.ui.test.SemanticsNodeInteraction);
method public static androidx.ui.test.SemanticsNodeInteraction assertSemanticsIsEqualTo(androidx.ui.test.SemanticsNodeInteraction, androidx.ui.core.semantics.SemanticsConfiguration expectedProperties);
method public static void assertStillExists(androidx.ui.test.SemanticsNodeInteraction);
method public static androidx.ui.test.SemanticsNodeInteraction assertValueEquals(androidx.ui.test.SemanticsNodeInteraction, String value);
+ method public static void verify(androidx.ui.test.SemanticsNodeInteraction, kotlin.jvm.functions.Function1<? super androidx.ui.core.semantics.SemanticsConfiguration,java.lang.String> assertionMessage, kotlin.jvm.functions.Function1<? super androidx.ui.core.semantics.SemanticsConfiguration,java.lang.Boolean> condition);
}
public final class CollectedSizes {
@@ -59,13 +60,15 @@
public final class FiltersKt {
ctor public FiltersKt();
- method public static boolean isCheckable(androidx.ui.core.semantics.SemanticsConfiguration);
+ method public static boolean isInMutuallyExclusiveGroup(androidx.ui.core.semantics.SemanticsConfiguration);
+ method public static boolean isToggleable(androidx.ui.core.semantics.SemanticsConfiguration);
}
public final class FindersKt {
ctor public FindersKt();
method public static androidx.ui.test.SemanticsNodeInteraction find(kotlin.jvm.functions.Function1<? super androidx.ui.core.semantics.SemanticsConfiguration,java.lang.Boolean> selector);
method public static java.util.List<androidx.ui.test.SemanticsNodeInteraction> findAll(kotlin.jvm.functions.Function1<? super androidx.ui.core.semantics.SemanticsConfiguration,java.lang.Boolean> selector);
+ method public static java.util.List<androidx.ui.test.SemanticsNodeInteraction> findAllByTag(String testTag);
method public static androidx.ui.test.SemanticsNodeInteraction findByTag(String testTag);
method public static androidx.ui.test.SemanticsNodeInteraction findByText(String text, boolean ignoreCase = false);
}
@@ -74,7 +77,7 @@
ctor public GoldenSemanticsKt();
method public static void assertEquals(androidx.ui.core.semantics.SemanticsConfiguration, androidx.ui.core.semantics.SemanticsConfiguration expected);
method public static androidx.ui.core.semantics.SemanticsConfiguration copyWith(androidx.ui.core.semantics.SemanticsConfiguration, kotlin.jvm.functions.Function1<? super androidx.ui.core.semantics.SemanticsConfiguration,kotlin.Unit> diff);
- method public static androidx.ui.core.semantics.SemanticsConfiguration createFullSemantics(Boolean? isEnabled = null, Boolean? isChecked = null, boolean isSelected = false, boolean isButton = false, boolean inMutuallyExclusiveGroup = false);
+ method public static androidx.ui.core.semantics.SemanticsConfiguration createFullSemantics(Boolean? isEnabled = null, String? value = null, androidx.ui.foundation.selection.ToggleableState? toggleableState = null, Boolean? inMutuallyExclusiveGroup = null, Boolean? isSelected = null);
}
public final class OutputKt {
diff --git a/ui/ui-test/api/current.txt b/ui/ui-test/api/current.txt
index 89f2433..9fd8bf1 100644
--- a/ui/ui-test/api/current.txt
+++ b/ui/ui-test/api/current.txt
@@ -13,14 +13,15 @@
method public static androidx.ui.test.SemanticsNodeInteraction assertIsChecked(androidx.ui.test.SemanticsNodeInteraction);
method public static androidx.ui.test.SemanticsNodeInteraction assertIsHidden(androidx.ui.test.SemanticsNodeInteraction);
method public static androidx.ui.test.SemanticsNodeInteraction assertIsInMutuallyExclusiveGroup(androidx.ui.test.SemanticsNodeInteraction);
- method public static androidx.ui.test.SemanticsNodeInteraction assertIsNotChecked(androidx.ui.test.SemanticsNodeInteraction);
- method public static androidx.ui.test.SemanticsNodeInteraction assertIsNotSelected(androidx.ui.test.SemanticsNodeInteraction);
method public static androidx.ui.test.SemanticsNodeInteraction assertIsSelected(androidx.ui.test.SemanticsNodeInteraction);
+ method public static androidx.ui.test.SemanticsNodeInteraction assertIsUnchecked(androidx.ui.test.SemanticsNodeInteraction);
+ method public static androidx.ui.test.SemanticsNodeInteraction assertIsUnselected(androidx.ui.test.SemanticsNodeInteraction);
method public static androidx.ui.test.SemanticsNodeInteraction assertIsVisible(androidx.ui.test.SemanticsNodeInteraction);
method public static void assertNoLongerExists(androidx.ui.test.SemanticsNodeInteraction);
method public static androidx.ui.test.SemanticsNodeInteraction assertSemanticsIsEqualTo(androidx.ui.test.SemanticsNodeInteraction, androidx.ui.core.semantics.SemanticsConfiguration expectedProperties);
method public static void assertStillExists(androidx.ui.test.SemanticsNodeInteraction);
method public static androidx.ui.test.SemanticsNodeInteraction assertValueEquals(androidx.ui.test.SemanticsNodeInteraction, String value);
+ method public static void verify(androidx.ui.test.SemanticsNodeInteraction, kotlin.jvm.functions.Function1<? super androidx.ui.core.semantics.SemanticsConfiguration,java.lang.String> assertionMessage, kotlin.jvm.functions.Function1<? super androidx.ui.core.semantics.SemanticsConfiguration,java.lang.Boolean> condition);
}
public final class CollectedSizes {
@@ -59,13 +60,15 @@
public final class FiltersKt {
ctor public FiltersKt();
- method public static boolean isCheckable(androidx.ui.core.semantics.SemanticsConfiguration);
+ method public static boolean isInMutuallyExclusiveGroup(androidx.ui.core.semantics.SemanticsConfiguration);
+ method public static boolean isToggleable(androidx.ui.core.semantics.SemanticsConfiguration);
}
public final class FindersKt {
ctor public FindersKt();
method public static androidx.ui.test.SemanticsNodeInteraction find(kotlin.jvm.functions.Function1<? super androidx.ui.core.semantics.SemanticsConfiguration,java.lang.Boolean> selector);
method public static java.util.List<androidx.ui.test.SemanticsNodeInteraction> findAll(kotlin.jvm.functions.Function1<? super androidx.ui.core.semantics.SemanticsConfiguration,java.lang.Boolean> selector);
+ method public static java.util.List<androidx.ui.test.SemanticsNodeInteraction> findAllByTag(String testTag);
method public static androidx.ui.test.SemanticsNodeInteraction findByTag(String testTag);
method public static androidx.ui.test.SemanticsNodeInteraction findByText(String text, boolean ignoreCase = false);
}
@@ -74,7 +77,7 @@
ctor public GoldenSemanticsKt();
method public static void assertEquals(androidx.ui.core.semantics.SemanticsConfiguration, androidx.ui.core.semantics.SemanticsConfiguration expected);
method public static androidx.ui.core.semantics.SemanticsConfiguration copyWith(androidx.ui.core.semantics.SemanticsConfiguration, kotlin.jvm.functions.Function1<? super androidx.ui.core.semantics.SemanticsConfiguration,kotlin.Unit> diff);
- method public static androidx.ui.core.semantics.SemanticsConfiguration createFullSemantics(Boolean? isEnabled = null, Boolean? isChecked = null, boolean isSelected = false, boolean isButton = false, boolean inMutuallyExclusiveGroup = false);
+ method public static androidx.ui.core.semantics.SemanticsConfiguration createFullSemantics(Boolean? isEnabled = null, String? value = null, androidx.ui.foundation.selection.ToggleableState? toggleableState = null, Boolean? inMutuallyExclusiveGroup = null, Boolean? isSelected = null);
}
public final class OutputKt {
diff --git a/ui/ui-test/api/restricted_1.0.0-alpha01.txt b/ui/ui-test/api/restricted_1.0.0-alpha01.txt
index 89f2433..9fd8bf1 100644
--- a/ui/ui-test/api/restricted_1.0.0-alpha01.txt
+++ b/ui/ui-test/api/restricted_1.0.0-alpha01.txt
@@ -13,14 +13,15 @@
method public static androidx.ui.test.SemanticsNodeInteraction assertIsChecked(androidx.ui.test.SemanticsNodeInteraction);
method public static androidx.ui.test.SemanticsNodeInteraction assertIsHidden(androidx.ui.test.SemanticsNodeInteraction);
method public static androidx.ui.test.SemanticsNodeInteraction assertIsInMutuallyExclusiveGroup(androidx.ui.test.SemanticsNodeInteraction);
- method public static androidx.ui.test.SemanticsNodeInteraction assertIsNotChecked(androidx.ui.test.SemanticsNodeInteraction);
- method public static androidx.ui.test.SemanticsNodeInteraction assertIsNotSelected(androidx.ui.test.SemanticsNodeInteraction);
method public static androidx.ui.test.SemanticsNodeInteraction assertIsSelected(androidx.ui.test.SemanticsNodeInteraction);
+ method public static androidx.ui.test.SemanticsNodeInteraction assertIsUnchecked(androidx.ui.test.SemanticsNodeInteraction);
+ method public static androidx.ui.test.SemanticsNodeInteraction assertIsUnselected(androidx.ui.test.SemanticsNodeInteraction);
method public static androidx.ui.test.SemanticsNodeInteraction assertIsVisible(androidx.ui.test.SemanticsNodeInteraction);
method public static void assertNoLongerExists(androidx.ui.test.SemanticsNodeInteraction);
method public static androidx.ui.test.SemanticsNodeInteraction assertSemanticsIsEqualTo(androidx.ui.test.SemanticsNodeInteraction, androidx.ui.core.semantics.SemanticsConfiguration expectedProperties);
method public static void assertStillExists(androidx.ui.test.SemanticsNodeInteraction);
method public static androidx.ui.test.SemanticsNodeInteraction assertValueEquals(androidx.ui.test.SemanticsNodeInteraction, String value);
+ method public static void verify(androidx.ui.test.SemanticsNodeInteraction, kotlin.jvm.functions.Function1<? super androidx.ui.core.semantics.SemanticsConfiguration,java.lang.String> assertionMessage, kotlin.jvm.functions.Function1<? super androidx.ui.core.semantics.SemanticsConfiguration,java.lang.Boolean> condition);
}
public final class CollectedSizes {
@@ -59,13 +60,15 @@
public final class FiltersKt {
ctor public FiltersKt();
- method public static boolean isCheckable(androidx.ui.core.semantics.SemanticsConfiguration);
+ method public static boolean isInMutuallyExclusiveGroup(androidx.ui.core.semantics.SemanticsConfiguration);
+ method public static boolean isToggleable(androidx.ui.core.semantics.SemanticsConfiguration);
}
public final class FindersKt {
ctor public FindersKt();
method public static androidx.ui.test.SemanticsNodeInteraction find(kotlin.jvm.functions.Function1<? super androidx.ui.core.semantics.SemanticsConfiguration,java.lang.Boolean> selector);
method public static java.util.List<androidx.ui.test.SemanticsNodeInteraction> findAll(kotlin.jvm.functions.Function1<? super androidx.ui.core.semantics.SemanticsConfiguration,java.lang.Boolean> selector);
+ method public static java.util.List<androidx.ui.test.SemanticsNodeInteraction> findAllByTag(String testTag);
method public static androidx.ui.test.SemanticsNodeInteraction findByTag(String testTag);
method public static androidx.ui.test.SemanticsNodeInteraction findByText(String text, boolean ignoreCase = false);
}
@@ -74,7 +77,7 @@
ctor public GoldenSemanticsKt();
method public static void assertEquals(androidx.ui.core.semantics.SemanticsConfiguration, androidx.ui.core.semantics.SemanticsConfiguration expected);
method public static androidx.ui.core.semantics.SemanticsConfiguration copyWith(androidx.ui.core.semantics.SemanticsConfiguration, kotlin.jvm.functions.Function1<? super androidx.ui.core.semantics.SemanticsConfiguration,kotlin.Unit> diff);
- method public static androidx.ui.core.semantics.SemanticsConfiguration createFullSemantics(Boolean? isEnabled = null, Boolean? isChecked = null, boolean isSelected = false, boolean isButton = false, boolean inMutuallyExclusiveGroup = false);
+ method public static androidx.ui.core.semantics.SemanticsConfiguration createFullSemantics(Boolean? isEnabled = null, String? value = null, androidx.ui.foundation.selection.ToggleableState? toggleableState = null, Boolean? inMutuallyExclusiveGroup = null, Boolean? isSelected = null);
}
public final class OutputKt {
diff --git a/ui/ui-test/api/restricted_current.txt b/ui/ui-test/api/restricted_current.txt
index 89f2433..9fd8bf1 100644
--- a/ui/ui-test/api/restricted_current.txt
+++ b/ui/ui-test/api/restricted_current.txt
@@ -13,14 +13,15 @@
method public static androidx.ui.test.SemanticsNodeInteraction assertIsChecked(androidx.ui.test.SemanticsNodeInteraction);
method public static androidx.ui.test.SemanticsNodeInteraction assertIsHidden(androidx.ui.test.SemanticsNodeInteraction);
method public static androidx.ui.test.SemanticsNodeInteraction assertIsInMutuallyExclusiveGroup(androidx.ui.test.SemanticsNodeInteraction);
- method public static androidx.ui.test.SemanticsNodeInteraction assertIsNotChecked(androidx.ui.test.SemanticsNodeInteraction);
- method public static androidx.ui.test.SemanticsNodeInteraction assertIsNotSelected(androidx.ui.test.SemanticsNodeInteraction);
method public static androidx.ui.test.SemanticsNodeInteraction assertIsSelected(androidx.ui.test.SemanticsNodeInteraction);
+ method public static androidx.ui.test.SemanticsNodeInteraction assertIsUnchecked(androidx.ui.test.SemanticsNodeInteraction);
+ method public static androidx.ui.test.SemanticsNodeInteraction assertIsUnselected(androidx.ui.test.SemanticsNodeInteraction);
method public static androidx.ui.test.SemanticsNodeInteraction assertIsVisible(androidx.ui.test.SemanticsNodeInteraction);
method public static void assertNoLongerExists(androidx.ui.test.SemanticsNodeInteraction);
method public static androidx.ui.test.SemanticsNodeInteraction assertSemanticsIsEqualTo(androidx.ui.test.SemanticsNodeInteraction, androidx.ui.core.semantics.SemanticsConfiguration expectedProperties);
method public static void assertStillExists(androidx.ui.test.SemanticsNodeInteraction);
method public static androidx.ui.test.SemanticsNodeInteraction assertValueEquals(androidx.ui.test.SemanticsNodeInteraction, String value);
+ method public static void verify(androidx.ui.test.SemanticsNodeInteraction, kotlin.jvm.functions.Function1<? super androidx.ui.core.semantics.SemanticsConfiguration,java.lang.String> assertionMessage, kotlin.jvm.functions.Function1<? super androidx.ui.core.semantics.SemanticsConfiguration,java.lang.Boolean> condition);
}
public final class CollectedSizes {
@@ -59,13 +60,15 @@
public final class FiltersKt {
ctor public FiltersKt();
- method public static boolean isCheckable(androidx.ui.core.semantics.SemanticsConfiguration);
+ method public static boolean isInMutuallyExclusiveGroup(androidx.ui.core.semantics.SemanticsConfiguration);
+ method public static boolean isToggleable(androidx.ui.core.semantics.SemanticsConfiguration);
}
public final class FindersKt {
ctor public FindersKt();
method public static androidx.ui.test.SemanticsNodeInteraction find(kotlin.jvm.functions.Function1<? super androidx.ui.core.semantics.SemanticsConfiguration,java.lang.Boolean> selector);
method public static java.util.List<androidx.ui.test.SemanticsNodeInteraction> findAll(kotlin.jvm.functions.Function1<? super androidx.ui.core.semantics.SemanticsConfiguration,java.lang.Boolean> selector);
+ method public static java.util.List<androidx.ui.test.SemanticsNodeInteraction> findAllByTag(String testTag);
method public static androidx.ui.test.SemanticsNodeInteraction findByTag(String testTag);
method public static androidx.ui.test.SemanticsNodeInteraction findByText(String text, boolean ignoreCase = false);
}
@@ -74,7 +77,7 @@
ctor public GoldenSemanticsKt();
method public static void assertEquals(androidx.ui.core.semantics.SemanticsConfiguration, androidx.ui.core.semantics.SemanticsConfiguration expected);
method public static androidx.ui.core.semantics.SemanticsConfiguration copyWith(androidx.ui.core.semantics.SemanticsConfiguration, kotlin.jvm.functions.Function1<? super androidx.ui.core.semantics.SemanticsConfiguration,kotlin.Unit> diff);
- method public static androidx.ui.core.semantics.SemanticsConfiguration createFullSemantics(Boolean? isEnabled = null, Boolean? isChecked = null, boolean isSelected = false, boolean isButton = false, boolean inMutuallyExclusiveGroup = false);
+ method public static androidx.ui.core.semantics.SemanticsConfiguration createFullSemantics(Boolean? isEnabled = null, String? value = null, androidx.ui.foundation.selection.ToggleableState? toggleableState = null, Boolean? inMutuallyExclusiveGroup = null, Boolean? isSelected = null);
}
public final class OutputKt {
diff --git a/ui/ui-test/src/androidTest/java/androidx/ui/test/FindAllTest.kt b/ui/ui-test/src/androidTest/java/androidx/ui/test/FindAllTest.kt
index a3f0036..188d2786 100644
--- a/ui/ui-test/src/androidTest/java/androidx/ui/test/FindAllTest.kt
+++ b/ui/ui-test/src/androidTest/java/androidx/ui/test/FindAllTest.kt
@@ -20,6 +20,9 @@
import androidx.compose.state
import androidx.compose.unaryPlus
import androidx.test.filters.MediumTest
+import androidx.ui.core.semantics.getOrNull
+import androidx.ui.foundation.selection.ToggleableState
+import androidx.ui.foundation.semantics.FoundationSemanticsProperties
import androidx.ui.layout.Column
import androidx.ui.material.Checkbox
import androidx.ui.material.MaterialTheme
@@ -49,7 +52,10 @@
}
}
- findAll { isCheckable() && isChecked == true }
+ findAll {
+ getOrNull(FoundationSemanticsProperties.ToggleableState) == ToggleableState.Checked
+ }
+ .assertCountEquals(2)
.forEach {
it.assertIsChecked()
}
@@ -76,7 +82,8 @@
}
}
- findAll { isCheckable() }
+ findAll { isToggleable }
+ .assertCountEquals(2)
.forEach {
it.doClick()
it.assertIsChecked()
@@ -84,7 +91,7 @@
}
@Test
- fun findAllTest_noCheckedComponent() {
+ fun findAllTest_noNonCheckedComponent() {
composeTestRule.setContent {
MaterialTheme {
Surface {
@@ -96,7 +103,9 @@
}
}
- findAll { isCheckable() && isChecked == false }
+ findAll {
+ getOrNull(FoundationSemanticsProperties.ToggleableState) != ToggleableState.Checked
+ }
.assertCountEquals(0)
}
@@ -122,12 +131,12 @@
}
}
- findAll { isCheckable() }.apply {
+ findAll { isToggleable }.apply {
get(0)
.doClick()
.assertIsChecked()
get(1)
- .assertIsNotChecked()
+ .assertIsUnchecked()
}.assertCountEquals(2)
}
@@ -161,16 +170,16 @@
}
}
- findAll { isCheckable() }.apply {
+ findAll { isToggleable }.assertCountEquals(2).apply {
get(0)
- .assertIsNotChecked()
+ .assertIsUnchecked()
.doClick()
.assertIsChecked()
}
- findAll { isCheckable() }.apply {
+ findAll { isToggleable }.assertCountEquals(3).apply {
get(2)
- .assertIsNotChecked()
+ .assertIsUnchecked()
}
}
@@ -199,9 +208,9 @@
}
}
- findAll { isCheckable() }.apply {
+ findAll { isToggleable }.assertCountEquals(2).apply {
get(0)
- .assertIsNotChecked()
+ .assertIsUnchecked()
.doClick()
.assertIsChecked()
get(1)
diff --git a/ui/ui-test/src/androidTest/java/androidx/ui/test/MultipleComposeRootsTest.kt b/ui/ui-test/src/androidTest/java/androidx/ui/test/MultipleComposeRootsTest.kt
index 9f77236..f8684d7 100644
--- a/ui/ui-test/src/androidTest/java/androidx/ui/test/MultipleComposeRootsTest.kt
+++ b/ui/ui-test/src/androidTest/java/androidx/ui/test/MultipleComposeRootsTest.kt
@@ -19,8 +19,8 @@
import android.widget.FrameLayout
import android.widget.LinearLayout
import android.widget.TextView
-import androidx.compose.composer
import androidx.compose.Model
+import androidx.compose.composer
import androidx.test.espresso.Espresso
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
@@ -157,14 +157,14 @@
.assertIsChecked()
findByTag("checkbox2")
- .assertIsNotChecked()
+ .assertIsUnchecked()
Espresso.onView(withText("Compose 1 - Checked")).check(matches(isDisplayed()))
Espresso.onView(withText("Compose 2 - Unchecked")).check(matches(isDisplayed()))
findByTag("checkbox2")
.doClick()
- .assertIsNotChecked()
+ .assertIsUnchecked()
findByTag("checkbox1")
.assertIsChecked()
diff --git a/ui/ui-test/src/main/java/androidx/ui/test/Assertions.kt b/ui/ui-test/src/main/java/androidx/ui/test/Assertions.kt
index 4e44b90..7f0debd 100644
--- a/ui/ui-test/src/main/java/androidx/ui/test/Assertions.kt
+++ b/ui/ui-test/src/main/java/androidx/ui/test/Assertions.kt
@@ -17,6 +17,11 @@
package androidx.ui.test
import androidx.ui.core.semantics.SemanticsConfiguration
+import androidx.ui.core.semantics.getOrNull
+import androidx.ui.foundation.selection.ToggleableState
+import androidx.ui.foundation.semantics.FoundationSemanticsProperties
+import androidx.ui.semantics.SemanticsProperties
+import androidx.ui.semantics.accessibilityValue
/**
* Asserts no items found given a criteria, throws [AssertionError] otherwise.
@@ -38,7 +43,7 @@
// TODO(b/123702531): Provide guarantees of being visible VS being actually displayed
fun SemanticsNodeInteraction.assertIsVisible(): SemanticsNodeInteraction {
verify({ "The component is not visible!" }) {
- !it.isHidden
+ it.getOrNull(SemanticsProperties.Hidden) != true
}
return this
}
@@ -50,7 +55,7 @@
*/
fun SemanticsNodeInteraction.assertIsHidden(): SemanticsNodeInteraction {
verify({ "The component is visible!" }) {
- it.isHidden
+ it.getOrNull(SemanticsProperties.Hidden) == true
}
return this
@@ -77,46 +82,57 @@
/**
* Asserts that the current component is checked.
+ *
+ * Throws [AssertionError] if the component is not unchecked, indeterminate, or not toggleable.
*/
fun SemanticsNodeInteraction.assertIsChecked(): SemanticsNodeInteraction {
- // TODO(pavlis): Throw exception if component is not checkable
- verify({ "The component is not checked!" }) {
- it.isChecked == true
+ verify({ "Component is toggled off, expected it to be toggled on" }) {
+ it.getOrElse(FoundationSemanticsProperties.ToggleableState) {
+ throw AssertionError("Component is not toggleable")
+ } == ToggleableState.Checked
}
return this
}
/**
- * Asserts that the current component is not checked.
+ * Asserts that the current component is unchecked.
+ *
+ * Throws [AssertionError] if the component is checked, indeterminate, or not toggleable.
*/
-fun SemanticsNodeInteraction.assertIsNotChecked(): SemanticsNodeInteraction {
- // TODO(pavlis): Throw exception if component is not checkable
- verify({ "The component is checked!" }) {
- it.isChecked != true
+fun SemanticsNodeInteraction.assertIsUnchecked(): SemanticsNodeInteraction {
+ verify({ "Component is toggled on, expected it to be toggled off" }) {
+ it.getOrElse(FoundationSemanticsProperties.ToggleableState) {
+ throw AssertionError("Component is not toggleable")
+ } == ToggleableState.Unchecked
}
+
return this
}
/**
* Asserts that the current component is selected.
+ *
+ * Throws [AssertionError] if the component is unselected or not selectable.
*/
fun SemanticsNodeInteraction.assertIsSelected(): SemanticsNodeInteraction {
- // TODO(pavlis): Throw exception if component is not selectable
- verify(
- { "The component is expected to be selected" }) {
- it.isSelected == true
+ verify({ "Component is unselected, expected it to be selected" }) {
+ it.getOrElse(FoundationSemanticsProperties.Selected) {
+ throw AssertionError("Component is not selectable")
+ }
}
return this
}
/**
- * Asserts that the current component is not selected.
+ * Asserts that the current component is unselected.
+ *
+ * Throws [AssertionError] if the component is selected or not selectable.
*/
-fun SemanticsNodeInteraction.assertIsNotSelected(): SemanticsNodeInteraction {
- // TODO(pavlis): Throw exception if component is not selectable
- verify(
- { "The component is expected to not be selected!" }) {
- it.isSelected == false
+fun SemanticsNodeInteraction.assertIsUnselected(): SemanticsNodeInteraction {
+ verify({ "Component is selected, expected it to be unselected" }) {
+ !it.getOrElse(FoundationSemanticsProperties.Selected) {
+ throw AssertionError("Component is not selectable")
+ }
}
return this
}
@@ -129,7 +145,7 @@
fun SemanticsNodeInteraction.assertIsInMutuallyExclusiveGroup(): SemanticsNodeInteraction {
// TODO(pavlis): Throw exception if component is not selectable
verify(
- { "The component is expected to be mutually exclusive group, but it's not!" }) {
+ { "The component is expected to be in a mutually exclusive group, but it's not!" }) {
it.isInMutuallyExclusiveGroup
}
return this
@@ -138,11 +154,14 @@
/**
* Asserts the component's value equals the given value. This is used by
* [CircularProgressIndicator] to check progress.
- * For further details please check [SemanticsConfiguration.value].
+ * For further details please check [SemanticsConfiguration.accessibilityValue].
+ * Throws [AssertionError] if the node's value is not equal to `value`, or if the node has no value
*/
fun SemanticsNodeInteraction.assertValueEquals(value: String): SemanticsNodeInteraction {
- verify({ node -> "Expected value: $value Actual value: ${node.value}" }) {
- it.value == value
+ verify({ node -> "Expected value: $value, Actual value: ${node.accessibilityValue}" }) {
+ it.getOrElse(SemanticsProperties.AccessibilityValue) {
+ throw AssertionError("Expected value: $value, but had none")
+ } == value
}
return this
}
@@ -174,7 +193,11 @@
return this
}
-internal fun SemanticsNodeInteraction.verify(
+/**
+ * Verifies that the provided condition is true.
+ * Throws [AssertionError] if it is not.
+ */
+fun SemanticsNodeInteraction.verify(
assertionMessage: (SemanticsConfiguration) -> String,
condition: (SemanticsConfiguration) -> Boolean
) {
@@ -184,4 +207,4 @@
// TODO(b/133217292)
throw AssertionError("Assert failed: ${assertionMessage(semanticsTreeNode.data)}")
}
-}
\ No newline at end of file
+}
diff --git a/ui/ui-test/src/main/java/androidx/ui/test/Filters.kt b/ui/ui-test/src/main/java/androidx/ui/test/Filters.kt
index 428708a..3f3d8cb 100644
--- a/ui/ui-test/src/main/java/androidx/ui/test/Filters.kt
+++ b/ui/ui-test/src/main/java/androidx/ui/test/Filters.kt
@@ -17,10 +17,20 @@
package androidx.ui.test
import androidx.ui.core.semantics.SemanticsConfiguration
+import androidx.ui.core.semantics.getOrNull
+import androidx.ui.foundation.semantics.FoundationSemanticsProperties
/**
* Verifies that a component is checkable.
*/
-fun SemanticsConfiguration.isCheckable(): Boolean {
- return isChecked != null
-}
\ No newline at end of file
+val SemanticsConfiguration.isToggleable: Boolean
+ get() = contains(FoundationSemanticsProperties.ToggleableState)
+
+// TODO(ryanmentley/pavlis): Do we want these convenience functions?
+/**
+ * Verifies that a component is in a mutually exclusive group - that is,
+ * that [FoundationSemanticsProperties.InMutuallyExclusiveGroup] is set to true
+ *
+ */
+val SemanticsConfiguration.isInMutuallyExclusiveGroup: Boolean
+ get() = getOrNull(FoundationSemanticsProperties.InMutuallyExclusiveGroup) == true
\ No newline at end of file
diff --git a/ui/ui-test/src/main/java/androidx/ui/test/Finders.kt b/ui/ui-test/src/main/java/androidx/ui/test/Finders.kt
index 12f0c78..fdb1cf8 100644
--- a/ui/ui-test/src/main/java/androidx/ui/test/Finders.kt
+++ b/ui/ui-test/src/main/java/androidx/ui/test/Finders.kt
@@ -17,6 +17,8 @@
package androidx.ui.test
import androidx.ui.core.semantics.SemanticsConfiguration
+import androidx.ui.core.semantics.getOrNull
+import androidx.ui.semantics.SemanticsProperties
/**
* Extension methods that provide the entry point for the testing APIs.
@@ -29,7 +31,18 @@
*/
fun findByTag(testTag: String): SemanticsNodeInteraction {
return find {
- this.testTag == testTag
+ getOrNull(SemanticsProperties.TestTag) == testTag
+ }
+}
+
+/**
+ * Finds all components identified by the given tag.
+ *
+ * For usage patterns see [SemanticsNodeInteraction]
+ */
+fun findAllByTag(testTag: String): List<SemanticsNodeInteraction> {
+ return findAll {
+ getOrNull(SemanticsProperties.TestTag) == testTag
}
}
@@ -40,7 +53,7 @@
*/
fun findByText(text: String, ignoreCase: Boolean = false): SemanticsNodeInteraction {
return find {
- label.equals(text, ignoreCase)
+ getOrNull(SemanticsProperties.AccessibilityLabel).equals(text, ignoreCase)
}
}
diff --git a/ui/ui-test/src/main/java/androidx/ui/test/GoldenSemantics.kt b/ui/ui-test/src/main/java/androidx/ui/test/GoldenSemantics.kt
index 87c1cba..85e1411 100644
--- a/ui/ui-test/src/main/java/androidx/ui/test/GoldenSemantics.kt
+++ b/ui/ui-test/src/main/java/androidx/ui/test/GoldenSemantics.kt
@@ -17,6 +17,12 @@
package androidx.ui.test
import androidx.ui.core.semantics.SemanticsConfiguration
+import androidx.ui.foundation.selection.ToggleableState
+import androidx.ui.foundation.semantics.inMutuallyExclusiveGroup
+import androidx.ui.foundation.semantics.selected
+import androidx.ui.foundation.semantics.toggleableState
+import androidx.ui.semantics.enabled
+import androidx.ui.semantics.accessibilityValue
/**
* Ensures the created [SemanticsConfiguration] object doesn't have any default values set.
@@ -27,17 +33,19 @@
// TODO(b/131309551): investigate the structure of this API
fun createFullSemantics(
isEnabled: Boolean? = null,
- isChecked: Boolean? = null,
- isSelected: Boolean = false,
- isButton: Boolean = false,
- inMutuallyExclusiveGroup: Boolean = false
+ value: String? = null,
+ toggleableState: ToggleableState? = null,
+ inMutuallyExclusiveGroup: Boolean? = null,
+ isSelected: Boolean? = null
): SemanticsConfiguration {
return SemanticsConfiguration().also {
- it.isEnabled = isEnabled
- it.isChecked = isChecked
- it.isInMutuallyExclusiveGroup = inMutuallyExclusiveGroup
- it.isSelected = isSelected
- it.isButton = isButton
+ isEnabled?.also { isEnabled -> it.enabled = isEnabled }
+ value?.also { value -> it.accessibilityValue = value }
+ toggleableState?.also { toggleableState -> it.toggleableState = toggleableState }
+ inMutuallyExclusiveGroup?.also {
+ inMutuallyExclusiveGroup -> it.inMutuallyExclusiveGroup = inMutuallyExclusiveGroup
+ }
+ isSelected?.also { selected -> it.selected = selected }
}
}
@@ -55,32 +63,22 @@
fun SemanticsConfiguration.assertEquals(expected: SemanticsConfiguration) {
val assertMessage = StringBuilder()
- if (isEnabled != expected.isEnabled) {
- assertMessage.append("\n- expected 'isEnabled' = ${expected.isEnabled} but was $isEnabled")
+ for ((key, expectedValue) in expected) {
+ if (this.contains(key)) {
+ val thisValue = this[key]
+ if (thisValue == expectedValue) {
+ continue
+ } else {
+ assertMessage.append("\n- expected ${key.name}" +
+ " = '$expectedValue', but was $thisValue")
+ }
+ } else {
+ assertMessage.append("\n- expected ${key.name} = '$expectedValue', but was missing")
+ }
}
- if (isChecked != expected.isChecked) {
- assertMessage.append("\n- expected 'isChecked' = ${expected.isChecked} but was $isChecked")
- }
-
- if (isInMutuallyExclusiveGroup != expected.isInMutuallyExclusiveGroup) {
- assertMessage.append(
- "\n- expected 'inMutuallyExclusiveGroup' = ${expected.isInMutuallyExclusiveGroup} " +
- "but was $isInMutuallyExclusiveGroup"
- )
- }
-
- if (isSelected != expected.isSelected) {
- assertMessage.append(
- "\n- expected 'isSelected' = ${expected.isSelected} but was $isSelected"
- )
- }
-
- if (isButton != expected.isButton) {
- assertMessage.append(
- "\n- expected 'isButton' = ${expected.isButton} but was $isButton"
- )
- }
+ // TODO(pavlis/ryanmentley): Should we also check that there are no _extra_ semantics?
+ // I think the correct answer here is "no", but we should confirm
if (assertMessage.isNotEmpty()) {
throw AssertionError(
diff --git a/ui/ui-test/src/main/java/androidx/ui/test/Output.kt b/ui/ui-test/src/main/java/androidx/ui/test/Output.kt
index c702554..31e9fea 100644
--- a/ui/ui-test/src/main/java/androidx/ui/test/Output.kt
+++ b/ui/ui-test/src/main/java/androidx/ui/test/Output.kt
@@ -16,6 +16,7 @@
package androidx.ui.test
+import androidx.ui.semantics.testTag
// TODO(b/133217292)
fun SemanticsNodeInteraction.dumpSemantics(consumer: (String) -> Unit) {
val builder = StringBuilder()
diff --git a/ui/ui-test/src/main/java/androidx/ui/test/SemanticsNodeInteraction.kt b/ui/ui-test/src/main/java/androidx/ui/test/SemanticsNodeInteraction.kt
index fd29f23..64fcc18 100644
--- a/ui/ui-test/src/main/java/androidx/ui/test/SemanticsNodeInteraction.kt
+++ b/ui/ui-test/src/main/java/androidx/ui/test/SemanticsNodeInteraction.kt
@@ -20,7 +20,7 @@
/**
* Represents a component with which one can interact with the hierarchy.
- * Examples of interactions include [findByTag], [isCheckable], [assertIsChecked], [doClick]
+ * Examples of interactions include [findByTag], [isToggleable], [assertIsChecked], [doClick]
*
* Example usage:
* findByTag("myCheckbox")
diff --git a/ui/ui-test/src/test/java/androidx/ui/test/AssertsTests.kt b/ui/ui-test/src/test/java/androidx/ui/test/AssertsTests.kt
index 9ea85c0..c3da838 100644
--- a/ui/ui-test/src/test/java/androidx/ui/test/AssertsTests.kt
+++ b/ui/ui-test/src/test/java/androidx/ui/test/AssertsTests.kt
@@ -21,6 +21,12 @@
import androidx.ui.core.dp
import androidx.ui.core.ipx
import androidx.ui.core.semantics.SemanticsConfiguration
+import androidx.ui.foundation.selection.ToggleableState
+import androidx.ui.foundation.semantics.inMutuallyExclusiveGroup
+import androidx.ui.foundation.semantics.selected
+import androidx.ui.foundation.semantics.toggleableState
+import androidx.ui.semantics.hidden
+import androidx.ui.semantics.testTag
import androidx.ui.test.helpers.FakeSemanticsTreeInteraction
import org.junit.Test
@@ -32,7 +38,7 @@
FakeSemanticsTreeInteraction(selector)
.withProperties(SemanticsConfiguration().also {
it.testTag = "test"
- it.isHidden = false
+ it.hidden = false
})
}
@@ -46,7 +52,7 @@
FakeSemanticsTreeInteraction(selector)
.withProperties(SemanticsConfiguration().also {
it.testTag = "test"
- it.isHidden = true
+ it.hidden = true
})
}
@@ -60,7 +66,7 @@
FakeSemanticsTreeInteraction(selector)
.withProperties(SemanticsConfiguration().also {
it.testTag = "test"
- it.isHidden = true
+ it.hidden = true
})
}
@@ -74,7 +80,7 @@
FakeSemanticsTreeInteraction(selector)
.withProperties(SemanticsConfiguration().also {
it.testTag = "test"
- it.isHidden = false
+ it.hidden = false
})
}
@@ -88,7 +94,7 @@
FakeSemanticsTreeInteraction(selector)
.withProperties(SemanticsConfiguration().also {
it.testTag = "test"
- it.isChecked = true
+ it.toggleableState = ToggleableState.Checked
})
}
@@ -97,17 +103,71 @@
}
@Test(expected = AssertionError::class)
- fun assertIsChecked_forNotCheckedElement_throwsError() {
+ fun assertIsChecked_forUncheckedElement_throwsError() {
semanticsTreeInteractionFactory = { selector ->
FakeSemanticsTreeInteraction(selector)
.withProperties(SemanticsConfiguration().also {
it.testTag = "test"
- it.isChecked = false
+ it.toggleableState = ToggleableState.Unchecked
})
}
findByTag("test")
- .assertIsHidden()
+ .assertIsChecked()
+ }
+
+ @Test(expected = AssertionError::class)
+ fun assertIsChecked_forNotToggleableElement_throwsError() {
+ semanticsTreeInteractionFactory = { selector ->
+ FakeSemanticsTreeInteraction(selector)
+ .withProperties(SemanticsConfiguration().also {
+ it.testTag = "test"
+ })
+ }
+
+ findByTag("test")
+ .assertIsChecked()
+ }
+
+ @Test(expected = AssertionError::class)
+ fun assertIsUnchecked_forCheckedElement_throwsError() {
+ semanticsTreeInteractionFactory = { selector ->
+ FakeSemanticsTreeInteraction(selector)
+ .withProperties(SemanticsConfiguration().also {
+ it.testTag = "test"
+ it.toggleableState = ToggleableState.Checked
+ })
+ }
+
+ findByTag("test")
+ .assertIsUnchecked()
+ }
+
+ @Test
+ fun assertIsUnchecked_forUncheckedElement_isOk() {
+ semanticsTreeInteractionFactory = { selector ->
+ FakeSemanticsTreeInteraction(selector)
+ .withProperties(SemanticsConfiguration().also {
+ it.testTag = "test"
+ it.toggleableState = ToggleableState.Unchecked
+ })
+ }
+
+ findByTag("test")
+ .assertIsUnchecked()
+ }
+
+ @Test(expected = AssertionError::class)
+ fun assertIsUnchecked_forNotToggleableElement_throwsError() {
+ semanticsTreeInteractionFactory = { selector ->
+ FakeSemanticsTreeInteraction(selector)
+ .withProperties(SemanticsConfiguration().also {
+ it.testTag = "test"
+ })
+ }
+
+ findByTag("test")
+ .assertIsUnchecked()
}
@Test(expected = AssertionError::class)
@@ -116,7 +176,7 @@
FakeSemanticsTreeInteraction(selector)
.withProperties(SemanticsConfiguration().also {
it.testTag = "test"
- it.isSelected = false
+ it.selected = false
})
}
@@ -130,7 +190,7 @@
FakeSemanticsTreeInteraction(selector)
.withProperties(SemanticsConfiguration().also {
it.testTag = "test"
- it.isSelected = true
+ it.selected = true
})
}
@@ -139,31 +199,57 @@
}
@Test(expected = AssertionError::class)
- fun assertIsNotSelected_forSelectedElement_throwsError() {
+ fun assertIsSelected_forNotSelectableElement_throwsError() {
semanticsTreeInteractionFactory = { selector ->
FakeSemanticsTreeInteraction(selector)
.withProperties(SemanticsConfiguration().also {
it.testTag = "test"
- it.isSelected = true
})
}
findByTag("test")
- .assertIsNotSelected()
+ .assertIsSelected()
+ }
+
+ @Test(expected = AssertionError::class)
+ fun assertIsUnselected_forSelectedElement_throwsError() {
+ semanticsTreeInteractionFactory = { selector ->
+ FakeSemanticsTreeInteraction(selector)
+ .withProperties(SemanticsConfiguration().also {
+ it.testTag = "test"
+ it.selected = true
+ })
+ }
+
+ findByTag("test")
+ .assertIsUnselected()
}
@Test
- fun assertIsNotSelected_forNotSelectedElement_isOk() {
+ fun assertIsUnselected_forUnselectedElement_isOk() {
semanticsTreeInteractionFactory = { selector ->
FakeSemanticsTreeInteraction(selector)
.withProperties(SemanticsConfiguration().also {
it.testTag = "test"
- it.isSelected = false
+ it.selected = false
})
}
findByTag("test")
- .assertIsNotSelected()
+ .assertIsUnselected()
+ }
+
+ @Test(expected = AssertionError::class)
+ fun assertIsUnselected_forNotSelectableElement_throwsError() {
+ semanticsTreeInteractionFactory = { selector ->
+ FakeSemanticsTreeInteraction(selector)
+ .withProperties(SemanticsConfiguration().also {
+ it.testTag = "test"
+ })
+ }
+
+ findByTag("test")
+ .assertIsUnselected()
}
@Test(expected = AssertionError::class)
@@ -172,7 +258,20 @@
FakeSemanticsTreeInteraction(selector)
.withProperties(SemanticsConfiguration().also {
it.testTag = "test"
- it.isInMutuallyExclusiveGroup = false
+ it.inMutuallyExclusiveGroup = false
+ })
+ }
+
+ findByTag("test")
+ .assertIsInMutuallyExclusiveGroup()
+ }
+
+ @Test(expected = AssertionError::class)
+ fun assertItemInExclusiveGroup_forItemWithoutProperty_throwsError() {
+ semanticsTreeInteractionFactory = { selector ->
+ FakeSemanticsTreeInteraction(selector)
+ .withProperties(SemanticsConfiguration().also {
+ it.testTag = "test"
})
}
@@ -186,7 +285,7 @@
FakeSemanticsTreeInteraction(selector)
.withProperties(SemanticsConfiguration().also {
it.testTag = "test"
- it.isInMutuallyExclusiveGroup = true
+ it.inMutuallyExclusiveGroup = true
})
}
diff --git a/ui/ui-test/src/test/java/androidx/ui/test/FindersTests.kt b/ui/ui-test/src/test/java/androidx/ui/test/FindersTests.kt
index 9fc5aca..deb6f9c 100644
--- a/ui/ui-test/src/test/java/androidx/ui/test/FindersTests.kt
+++ b/ui/ui-test/src/test/java/androidx/ui/test/FindersTests.kt
@@ -18,10 +18,14 @@
import androidx.ui.core.SemanticsTreeNode
import androidx.ui.core.semantics.SemanticsConfiguration
+import androidx.ui.core.semantics.getOrNull
+import androidx.ui.semantics.SemanticsProperties
+import androidx.ui.semantics.testTag
import androidx.ui.test.helpers.FakeSemanticsTreeInteraction
import com.google.common.truth.Truth
import org.junit.Test
+// TODO(b/138167927): findByTag_* tests do not call findByTag
class FindersTests {
@Test
fun findByTag_zeroOutOfOne_findsNone() {
@@ -34,7 +38,7 @@
))
}
- val foundNodes = findAll { testTag == "myTestTag" }
+ val foundNodes = findAll { getOrNull(SemanticsProperties.TestTag) == "myTestTag" }
Truth.assertThat(foundNodes).isEmpty()
}
@@ -52,7 +56,7 @@
.withSemantics(node1, node2)
}
- val foundNodes = findAll { testTag == "myTestTag" }
+ val foundNodes = findAll { getOrNull(SemanticsProperties.TestTag) == "myTestTag" }
Truth.assertThat(foundNodes.map { it.semanticsTreeNode }).containsExactly(node1)
}
@@ -74,7 +78,7 @@
.withSemantics(node1, node2)
}
- val foundNodes = findAll { testTag == "myTestTag" }
+ val foundNodes = findAll { getOrNull(SemanticsProperties.TestTag) == "myTestTag" }
Truth.assertThat(foundNodes.map { it.semanticsTreeNode }).containsExactly(node1, node2)
}
diff --git a/ui/ui-text/api/1.0.0-alpha01.txt b/ui/ui-text/api/1.0.0-alpha01.txt
index bfcd48d..a37b2f2 100644
--- a/ui/ui-text/api/1.0.0-alpha01.txt
+++ b/ui/ui-text/api/1.0.0-alpha01.txt
@@ -1,8 +1,8 @@
// Signature format: 3.0
package androidx.ui.input {
- public final class EditorState {
- ctor public EditorState(String text, androidx.ui.text.TextRange selection);
+ public final class EditorModel {
+ ctor public EditorModel(String text, androidx.ui.text.TextRange selection);
method public androidx.ui.text.TextRange? getComposition();
method public androidx.ui.text.TextRange getSelection();
method public String getText();
@@ -38,10 +38,12 @@
package androidx.ui.text {
public final class AnnotatedString {
- ctor public AnnotatedString(String text, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> textStyles);
+ ctor public AnnotatedString(String text, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> textStyles, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.ParagraphStyle>> paragraphStyles);
method public String component1();
method public java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> component2();
- method public androidx.ui.text.AnnotatedString copy(String text, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> textStyles);
+ method public java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.ParagraphStyle>> component3();
+ method public androidx.ui.text.AnnotatedString copy(String text, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> textStyles, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.ParagraphStyle>> paragraphStyles);
+ method public java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.ParagraphStyle>> getParagraphStyles();
method public String getText();
method public java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> getTextStyles();
}
@@ -76,9 +78,12 @@
method public String _canonicalizeRegionCode(String regionCode);
}
+ public final class MultiParagraphKt {
+ ctor public MultiParagraphKt();
+ }
+
public interface Paragraph {
method public float getBaseline();
- method public androidx.ui.engine.geometry.Rect getBoundingBox(int offset);
method public androidx.ui.engine.geometry.Rect getCursorRect(int offset);
method public boolean getDidExceedMaxLines();
method public float getHeight();
@@ -89,12 +94,12 @@
method public float getLineWidth(int lineIndex);
method public float getMaxIntrinsicWidth();
method public float getMinIntrinsicWidth();
+ method public int getOffsetForPosition(androidx.ui.core.PxPosition position);
method public androidx.ui.painting.Path getPathForRange(int start, int end);
- method public int getPositionForOffset(androidx.ui.engine.geometry.Offset offset);
method public float getWidth();
method public androidx.ui.text.TextRange getWordBoundary(int offset);
method public void layout(androidx.ui.text.ParagraphConstraints constraints);
- method public void paint(androidx.ui.painting.Canvas canvas, float x, float y);
+ method public void paint(androidx.ui.painting.Canvas canvas);
property public abstract float baseline;
property public abstract boolean didExceedMaxLines;
property public abstract float height;
@@ -128,6 +133,7 @@
method public androidx.ui.text.style.TextAlign? getTextAlign();
method public androidx.ui.text.style.TextDirection? getTextDirection();
method public androidx.ui.text.style.TextIndent? getTextIndent();
+ method public androidx.ui.text.ParagraphStyle merge(androidx.ui.text.ParagraphStyle? other = null);
}
public final class TextPainter {
@@ -139,9 +145,9 @@
method public float getMaxIntrinsicWidth();
method public Integer? getMaxLines();
method public float getMinIntrinsicWidth();
+ method public int getOffsetForPosition(androidx.ui.core.PxPosition position);
method public androidx.ui.text.style.TextOverflow getOverflow();
method public androidx.ui.text.ParagraphStyle? getParagraphStyle();
- method public int getPositionForOffset(androidx.ui.engine.geometry.Offset offset);
method public float getPreferredLineHeight();
method public androidx.ui.text.font.Font.ResourceLoader getResourceLoader();
method public androidx.ui.engine.geometry.Size getSize();
@@ -149,10 +155,10 @@
method public androidx.ui.text.TextStyle? getStyle();
method public androidx.ui.text.AnnotatedString? getText();
method public float getWidth();
- method public androidx.ui.text.TextRange getWordBoundary(int position);
+ method public androidx.ui.text.TextRange getWordBoundary(int offset);
method public void layout(androidx.ui.core.Constraints constraints);
- method public void paint(androidx.ui.painting.Canvas canvas, androidx.ui.engine.geometry.Offset offset);
- method public void paintBackground(int start, int end, androidx.ui.graphics.Color color, androidx.ui.painting.Canvas canvas, androidx.ui.engine.geometry.Offset offset);
+ method public void paint(androidx.ui.painting.Canvas canvas);
+ method public void paintBackground(int start, int end, androidx.ui.graphics.Color color, androidx.ui.painting.Canvas canvas);
method public void paintCursor(int offset, androidx.ui.painting.Canvas canvas);
method public void setText(androidx.ui.text.AnnotatedString? value);
property public final boolean didExceedMaxLines;
diff --git a/ui/ui-text/api/api_lint.ignore b/ui/ui-text/api/api_lint.ignore
index 577c67f..26f0fc9 100644
--- a/ui/ui-text/api/api_lint.ignore
+++ b/ui/ui-text/api/api_lint.ignore
@@ -48,12 +48,8 @@
KotlinOperator: androidx.ui.text.font.FontFamily#contains(androidx.ui.text.font.Font):
- Method can be invoked as a "in" operator from Kotlin: `contains` (this is usually desirable; just make sure it makes sense for this type of object)
-KotlinOperator: androidx.ui.text.font.FontFamily#get(int):
- Method can be invoked with an indexing operator from Kotlin: `get` (this is usually desirable; just make sure it makes sense for this type of object)
+ Note that adding the `operator` keyword would allow calling this method using operator syntax
KotlinOperator: androidx.ui.text.font.FontFamilyList#contains(androidx.ui.text.font.FontFamily):
- Method can be invoked as a "in" operator from Kotlin: `contains` (this is usually desirable; just make sure it makes sense for this type of object)
-KotlinOperator: androidx.ui.text.font.FontFamilyList#get(int):
- Method can be invoked with an indexing operator from Kotlin: `get` (this is usually desirable; just make sure it makes sense for this type of object)
+ Note that adding the `operator` keyword would allow calling this method using operator syntax
KotlinOperator: androidx.ui.text.style.TextDecoration#contains(androidx.ui.text.style.TextDecoration):
- Method can be invoked as a "in" operator from Kotlin: `contains` (this is usually desirable; just make sure it makes sense for this type of object)
+ Note that adding the `operator` keyword would allow calling this method using operator syntax
diff --git a/ui/ui-text/api/current.txt b/ui/ui-text/api/current.txt
index bfcd48d..a37b2f2 100644
--- a/ui/ui-text/api/current.txt
+++ b/ui/ui-text/api/current.txt
@@ -1,8 +1,8 @@
// Signature format: 3.0
package androidx.ui.input {
- public final class EditorState {
- ctor public EditorState(String text, androidx.ui.text.TextRange selection);
+ public final class EditorModel {
+ ctor public EditorModel(String text, androidx.ui.text.TextRange selection);
method public androidx.ui.text.TextRange? getComposition();
method public androidx.ui.text.TextRange getSelection();
method public String getText();
@@ -38,10 +38,12 @@
package androidx.ui.text {
public final class AnnotatedString {
- ctor public AnnotatedString(String text, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> textStyles);
+ ctor public AnnotatedString(String text, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> textStyles, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.ParagraphStyle>> paragraphStyles);
method public String component1();
method public java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> component2();
- method public androidx.ui.text.AnnotatedString copy(String text, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> textStyles);
+ method public java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.ParagraphStyle>> component3();
+ method public androidx.ui.text.AnnotatedString copy(String text, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> textStyles, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.ParagraphStyle>> paragraphStyles);
+ method public java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.ParagraphStyle>> getParagraphStyles();
method public String getText();
method public java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> getTextStyles();
}
@@ -76,9 +78,12 @@
method public String _canonicalizeRegionCode(String regionCode);
}
+ public final class MultiParagraphKt {
+ ctor public MultiParagraphKt();
+ }
+
public interface Paragraph {
method public float getBaseline();
- method public androidx.ui.engine.geometry.Rect getBoundingBox(int offset);
method public androidx.ui.engine.geometry.Rect getCursorRect(int offset);
method public boolean getDidExceedMaxLines();
method public float getHeight();
@@ -89,12 +94,12 @@
method public float getLineWidth(int lineIndex);
method public float getMaxIntrinsicWidth();
method public float getMinIntrinsicWidth();
+ method public int getOffsetForPosition(androidx.ui.core.PxPosition position);
method public androidx.ui.painting.Path getPathForRange(int start, int end);
- method public int getPositionForOffset(androidx.ui.engine.geometry.Offset offset);
method public float getWidth();
method public androidx.ui.text.TextRange getWordBoundary(int offset);
method public void layout(androidx.ui.text.ParagraphConstraints constraints);
- method public void paint(androidx.ui.painting.Canvas canvas, float x, float y);
+ method public void paint(androidx.ui.painting.Canvas canvas);
property public abstract float baseline;
property public abstract boolean didExceedMaxLines;
property public abstract float height;
@@ -128,6 +133,7 @@
method public androidx.ui.text.style.TextAlign? getTextAlign();
method public androidx.ui.text.style.TextDirection? getTextDirection();
method public androidx.ui.text.style.TextIndent? getTextIndent();
+ method public androidx.ui.text.ParagraphStyle merge(androidx.ui.text.ParagraphStyle? other = null);
}
public final class TextPainter {
@@ -139,9 +145,9 @@
method public float getMaxIntrinsicWidth();
method public Integer? getMaxLines();
method public float getMinIntrinsicWidth();
+ method public int getOffsetForPosition(androidx.ui.core.PxPosition position);
method public androidx.ui.text.style.TextOverflow getOverflow();
method public androidx.ui.text.ParagraphStyle? getParagraphStyle();
- method public int getPositionForOffset(androidx.ui.engine.geometry.Offset offset);
method public float getPreferredLineHeight();
method public androidx.ui.text.font.Font.ResourceLoader getResourceLoader();
method public androidx.ui.engine.geometry.Size getSize();
@@ -149,10 +155,10 @@
method public androidx.ui.text.TextStyle? getStyle();
method public androidx.ui.text.AnnotatedString? getText();
method public float getWidth();
- method public androidx.ui.text.TextRange getWordBoundary(int position);
+ method public androidx.ui.text.TextRange getWordBoundary(int offset);
method public void layout(androidx.ui.core.Constraints constraints);
- method public void paint(androidx.ui.painting.Canvas canvas, androidx.ui.engine.geometry.Offset offset);
- method public void paintBackground(int start, int end, androidx.ui.graphics.Color color, androidx.ui.painting.Canvas canvas, androidx.ui.engine.geometry.Offset offset);
+ method public void paint(androidx.ui.painting.Canvas canvas);
+ method public void paintBackground(int start, int end, androidx.ui.graphics.Color color, androidx.ui.painting.Canvas canvas);
method public void paintCursor(int offset, androidx.ui.painting.Canvas canvas);
method public void setText(androidx.ui.text.AnnotatedString? value);
property public final boolean didExceedMaxLines;
diff --git a/ui/ui-text/api/restricted_1.0.0-alpha01.txt b/ui/ui-text/api/restricted_1.0.0-alpha01.txt
index 56044ed..d02f68726 100644
--- a/ui/ui-text/api/restricted_1.0.0-alpha01.txt
+++ b/ui/ui-text/api/restricted_1.0.0-alpha01.txt
@@ -8,8 +8,8 @@
- public final class EditorState {
- ctor public EditorState(String text, androidx.ui.text.TextRange selection);
+ public final class EditorModel {
+ ctor public EditorModel(String text, androidx.ui.text.TextRange selection);
method public androidx.ui.text.TextRange? getComposition();
method public androidx.ui.text.TextRange getSelection();
method public String getText();
@@ -52,10 +52,12 @@
package androidx.ui.text {
public final class AnnotatedString {
- ctor public AnnotatedString(String text, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> textStyles);
+ ctor public AnnotatedString(String text, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> textStyles, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.ParagraphStyle>> paragraphStyles);
method public String component1();
method public java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> component2();
- method public androidx.ui.text.AnnotatedString copy(String text, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> textStyles);
+ method public java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.ParagraphStyle>> component3();
+ method public androidx.ui.text.AnnotatedString copy(String text, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> textStyles, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.ParagraphStyle>> paragraphStyles);
+ method public java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.ParagraphStyle>> getParagraphStyles();
method public String getText();
method public java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> getTextStyles();
}
@@ -90,9 +92,12 @@
method public String _canonicalizeRegionCode(String regionCode);
}
+ public final class MultiParagraphKt {
+ ctor public MultiParagraphKt();
+ }
+
public interface Paragraph {
method public float getBaseline();
- method public androidx.ui.engine.geometry.Rect getBoundingBox(int offset);
method public androidx.ui.engine.geometry.Rect getCursorRect(int offset);
method public boolean getDidExceedMaxLines();
method public float getHeight();
@@ -103,12 +108,12 @@
method public float getLineWidth(int lineIndex);
method public float getMaxIntrinsicWidth();
method public float getMinIntrinsicWidth();
+ method public int getOffsetForPosition(androidx.ui.core.PxPosition position);
method public androidx.ui.painting.Path getPathForRange(int start, int end);
- method public int getPositionForOffset(androidx.ui.engine.geometry.Offset offset);
method public float getWidth();
method public androidx.ui.text.TextRange getWordBoundary(int offset);
method public void layout(androidx.ui.text.ParagraphConstraints constraints);
- method public void paint(androidx.ui.painting.Canvas canvas, float x, float y);
+ method public void paint(androidx.ui.painting.Canvas canvas);
property public abstract float baseline;
property public abstract boolean didExceedMaxLines;
property public abstract float height;
@@ -142,6 +147,7 @@
method public androidx.ui.text.style.TextAlign? getTextAlign();
method public androidx.ui.text.style.TextDirection? getTextDirection();
method public androidx.ui.text.style.TextIndent? getTextIndent();
+ method public androidx.ui.text.ParagraphStyle merge(androidx.ui.text.ParagraphStyle? other = null);
}
public final class TextPainter {
@@ -153,9 +159,9 @@
method public float getMaxIntrinsicWidth();
method public Integer? getMaxLines();
method public float getMinIntrinsicWidth();
+ method public int getOffsetForPosition(androidx.ui.core.PxPosition position);
method public androidx.ui.text.style.TextOverflow getOverflow();
method public androidx.ui.text.ParagraphStyle? getParagraphStyle();
- method public int getPositionForOffset(androidx.ui.engine.geometry.Offset offset);
method public float getPreferredLineHeight();
method public androidx.ui.text.font.Font.ResourceLoader getResourceLoader();
method public androidx.ui.engine.geometry.Size getSize();
@@ -163,10 +169,10 @@
method public androidx.ui.text.TextStyle? getStyle();
method public androidx.ui.text.AnnotatedString? getText();
method public float getWidth();
- method public androidx.ui.text.TextRange getWordBoundary(int position);
+ method public androidx.ui.text.TextRange getWordBoundary(int offset);
method public void layout(androidx.ui.core.Constraints constraints);
- method public void paint(androidx.ui.painting.Canvas canvas, androidx.ui.engine.geometry.Offset offset);
- method public void paintBackground(int start, int end, androidx.ui.graphics.Color color, androidx.ui.painting.Canvas canvas, androidx.ui.engine.geometry.Offset offset);
+ method public void paint(androidx.ui.painting.Canvas canvas);
+ method public void paintBackground(int start, int end, androidx.ui.graphics.Color color, androidx.ui.painting.Canvas canvas);
method public void paintCursor(int offset, androidx.ui.painting.Canvas canvas);
method public void setText(androidx.ui.text.AnnotatedString? value);
property public final boolean didExceedMaxLines;
diff --git a/ui/ui-text/api/restricted_current.txt b/ui/ui-text/api/restricted_current.txt
index 56044ed..d02f68726 100644
--- a/ui/ui-text/api/restricted_current.txt
+++ b/ui/ui-text/api/restricted_current.txt
@@ -8,8 +8,8 @@
- public final class EditorState {
- ctor public EditorState(String text, androidx.ui.text.TextRange selection);
+ public final class EditorModel {
+ ctor public EditorModel(String text, androidx.ui.text.TextRange selection);
method public androidx.ui.text.TextRange? getComposition();
method public androidx.ui.text.TextRange getSelection();
method public String getText();
@@ -52,10 +52,12 @@
package androidx.ui.text {
public final class AnnotatedString {
- ctor public AnnotatedString(String text, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> textStyles);
+ ctor public AnnotatedString(String text, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> textStyles, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.ParagraphStyle>> paragraphStyles);
method public String component1();
method public java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> component2();
- method public androidx.ui.text.AnnotatedString copy(String text, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> textStyles);
+ method public java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.ParagraphStyle>> component3();
+ method public androidx.ui.text.AnnotatedString copy(String text, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> textStyles, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.ParagraphStyle>> paragraphStyles);
+ method public java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.ParagraphStyle>> getParagraphStyles();
method public String getText();
method public java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> getTextStyles();
}
@@ -90,9 +92,12 @@
method public String _canonicalizeRegionCode(String regionCode);
}
+ public final class MultiParagraphKt {
+ ctor public MultiParagraphKt();
+ }
+
public interface Paragraph {
method public float getBaseline();
- method public androidx.ui.engine.geometry.Rect getBoundingBox(int offset);
method public androidx.ui.engine.geometry.Rect getCursorRect(int offset);
method public boolean getDidExceedMaxLines();
method public float getHeight();
@@ -103,12 +108,12 @@
method public float getLineWidth(int lineIndex);
method public float getMaxIntrinsicWidth();
method public float getMinIntrinsicWidth();
+ method public int getOffsetForPosition(androidx.ui.core.PxPosition position);
method public androidx.ui.painting.Path getPathForRange(int start, int end);
- method public int getPositionForOffset(androidx.ui.engine.geometry.Offset offset);
method public float getWidth();
method public androidx.ui.text.TextRange getWordBoundary(int offset);
method public void layout(androidx.ui.text.ParagraphConstraints constraints);
- method public void paint(androidx.ui.painting.Canvas canvas, float x, float y);
+ method public void paint(androidx.ui.painting.Canvas canvas);
property public abstract float baseline;
property public abstract boolean didExceedMaxLines;
property public abstract float height;
@@ -142,6 +147,7 @@
method public androidx.ui.text.style.TextAlign? getTextAlign();
method public androidx.ui.text.style.TextDirection? getTextDirection();
method public androidx.ui.text.style.TextIndent? getTextIndent();
+ method public androidx.ui.text.ParagraphStyle merge(androidx.ui.text.ParagraphStyle? other = null);
}
public final class TextPainter {
@@ -153,9 +159,9 @@
method public float getMaxIntrinsicWidth();
method public Integer? getMaxLines();
method public float getMinIntrinsicWidth();
+ method public int getOffsetForPosition(androidx.ui.core.PxPosition position);
method public androidx.ui.text.style.TextOverflow getOverflow();
method public androidx.ui.text.ParagraphStyle? getParagraphStyle();
- method public int getPositionForOffset(androidx.ui.engine.geometry.Offset offset);
method public float getPreferredLineHeight();
method public androidx.ui.text.font.Font.ResourceLoader getResourceLoader();
method public androidx.ui.engine.geometry.Size getSize();
@@ -163,10 +169,10 @@
method public androidx.ui.text.TextStyle? getStyle();
method public androidx.ui.text.AnnotatedString? getText();
method public float getWidth();
- method public androidx.ui.text.TextRange getWordBoundary(int position);
+ method public androidx.ui.text.TextRange getWordBoundary(int offset);
method public void layout(androidx.ui.core.Constraints constraints);
- method public void paint(androidx.ui.painting.Canvas canvas, androidx.ui.engine.geometry.Offset offset);
- method public void paintBackground(int start, int end, androidx.ui.graphics.Color color, androidx.ui.painting.Canvas canvas, androidx.ui.engine.geometry.Offset offset);
+ method public void paint(androidx.ui.painting.Canvas canvas);
+ method public void paintBackground(int start, int end, androidx.ui.graphics.Color color, androidx.ui.painting.Canvas canvas);
method public void paintCursor(int offset, androidx.ui.painting.Canvas canvas);
method public void setText(androidx.ui.text.AnnotatedString? value);
property public final boolean didExceedMaxLines;
diff --git a/ui/ui-text/build.gradle b/ui/ui-text/build.gradle
index feccef84..9772691 100644
--- a/ui/ui-text/build.gradle
+++ b/ui/ui-text/build.gradle
@@ -40,6 +40,7 @@
implementation project(":compose:compose-runtime")
implementation project(":ui:ui-android-text")
implementation project(":ui:ui-core")
+ implementation project(":ui:ui-vector")
testImplementation(ANDROIDX_TEST_RULES)
testImplementation(ANDROIDX_TEST_RUNNER)
diff --git a/ui/ui-text/integration-tests/text-demos/src/main/AndroidManifest.xml b/ui/ui-text/integration-tests/text-demos/src/main/AndroidManifest.xml
index 77e5d1b..66c0d73 100644
--- a/ui/ui-text/integration-tests/text-demos/src/main/AndroidManifest.xml
+++ b/ui/ui-text/integration-tests/text-demos/src/main/AndroidManifest.xml
@@ -30,7 +30,16 @@
<activity android:name=".CraneInputFieldActivity"
android:configChanges="orientation|screenSize"
- android:label="Text/Input Field Demo">
+ android:label="Text/Input Field/Input Field Demo">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="androidx.ui.demos.SAMPLE_CODE" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name=".CraneVariousInputFieldActivity"
+ android:configChanges="orientation|screenSize"
+ android:label="Text/Input Field/Various Input Field Demo">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="androidx.ui.demos.SAMPLE_CODE" />
diff --git a/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneInputField.kt b/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneInputField.kt
index c71370d..b345b40 100644
--- a/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneInputField.kt
+++ b/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneInputField.kt
@@ -21,9 +21,8 @@
import androidx.compose.state
import androidx.compose.unaryPlus
import androidx.ui.core.EditorStyle
-import androidx.ui.core.InputField
-import androidx.ui.text.TextRange
-import androidx.ui.input.EditorState
+import androidx.ui.core.TextField
+import androidx.ui.input.EditorModel
import androidx.ui.input.ImeAction
import androidx.ui.input.KeyboardType
import androidx.ui.layout.Column
@@ -79,8 +78,8 @@
keyboardType: KeyboardType = KeyboardType.Text,
imeAction: ImeAction = ImeAction.Unspecified
) {
- val state = +state { EditorState() }
- InputField(
+ val state = +state { EditorModel() }
+ TextField(
value = state.value,
keyboardType = keyboardType,
imeAction = imeAction,
diff --git a/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneText.kt b/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneText.kt
index faaf76d..a4f8e25 100644
--- a/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneText.kt
+++ b/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneText.kt
@@ -47,6 +47,8 @@
import androidx.ui.text.style.TextOverflow
import androidx.ui.core.Sp
import androidx.ui.core.sp
+import androidx.ui.text.AnnotatedString
+import androidx.ui.text.style.TextIndent
val displayText = "Text Demo"
val displayTextChinese = "文本演示"
@@ -99,6 +101,16 @@
TextDemoComposableTextSpan()
TagLine(tag = "fontSizeScale")
TextDemoFontSizeScale()
+ TagLine(tag = "multiple paragraphs basic")
+ TextDemoParagraph()
+ TagLine(tag = "multiple paragraphs TextAlign")
+ TextDemoParagraphTextAlign()
+ TagLine(tag = "multiple paragraphs line height")
+ TextDemoParagraphLineHeight()
+ TagLine(tag = "multiple paragraphs TextIndent")
+ TextDemoParagraphIndent()
+ TagLine(tag = "multiple paragraphs TextDirection")
+ TextDemoParagraphTextDirection()
}
}
}
@@ -268,10 +280,12 @@
// This group of text widgets show different letterSpacing.
Text {
Span(text = "$displayText ", style = TextStyle(fontSize = fontSize8))
- Span(text = displayText, style = TextStyle(
- fontSize = fontSize8,
- letterSpacing = 0.5f
- )
+ Span(
+ text = displayText,
+ style = TextStyle(
+ fontSize = fontSize8,
+ letterSpacing = 0.5f
+ )
)
}
}
@@ -280,7 +294,6 @@
fun TextDemoBaselineShift() {
Text {
Span(text = displayText, style = TextStyle(fontSize = fontSize8)) {
-
Span(
text = "superscript",
style = TextStyle(
@@ -390,7 +403,7 @@
fun TextDemoTextAlign() {
// This group of text widgets show different TextAligns: LEFT, RIGHT, CENTER, JUSTIFY, START for
// LTR and RTL, END for LTR and RTL.
- var text: String = ""
+ var text = ""
for (i in 1..10) {
text = "$text$displayText "
}
@@ -458,7 +471,7 @@
@Composable
fun TextDemoSoftWrap() {
// This group of text widgets show difference between softWrap is true and false.
- var text: String = ""
+ var text = ""
for (i in 1..10) {
text = "$text$displayText"
}
@@ -481,7 +494,7 @@
fun TexDemoTextOverflowFade() {
var text = ""
for (i in 1..15) {
- text = text + displayText
+ text += displayText
}
val textSytle =
TextStyle(fontSize = fontSize8, color = Color(0xFFFF0000.toInt()))
@@ -590,7 +603,7 @@
Color(0xFF0000FF.toInt()),
Color(0xFF00FF00.toInt()),
Color(0xFFFF0000.toInt())
- )
+ )
val selection = +state<Selection?> { null }
SelectionContainer(
@@ -639,7 +652,8 @@
SelectionContainer(
selection = selection.value,
onSelectionChange = { selection.value = it },
- mode = SelectionMode.Horizontal) {
+ mode = SelectionMode.Horizontal
+ ) {
Column {
for (i in 0..2) {
Row {
@@ -685,3 +699,131 @@
}
}
}
+
+@Composable
+fun TextDemoParagraph() {
+ val text1 = "paragraph1 paragraph1 paragraph1 paragraph1 paragraph1"
+ val text2 = "paragraph2 paragraph2 paragraph2 paragraph2 paragraph2"
+ Text(
+ text = AnnotatedString(
+ text = text1 + text2,
+ textStyles = listOf(),
+ paragraphStyles = listOf(
+ AnnotatedString.Item(ParagraphStyle(), text1.length, text1.length)
+ )
+ ),
+ style = TextStyle(fontSize = fontSize8)
+ )
+}
+
+@Composable
+fun TextDemoParagraphTextAlign() {
+ var text = ""
+ val paragraphStyles = mutableListOf<AnnotatedString.Item<ParagraphStyle>>()
+ TextAlign.values().map { textAlign ->
+ val str = List(4) { "TextAlign.$textAlign" }.joinToString(" ")
+ val paragraphStyle = ParagraphStyle(textAlign = textAlign)
+ Pair(str, paragraphStyle)
+ }.forEach { (str, paragraphStyle) ->
+ paragraphStyles.add(
+ AnnotatedString.Item(
+ paragraphStyle,
+ text.length,
+ text.length + str.length
+ )
+ )
+ text += str
+ }
+
+ Text(
+ text = AnnotatedString(
+ text = text,
+ textStyles = listOf(),
+ paragraphStyles = paragraphStyles
+ ),
+ style = TextStyle(fontSize = fontSize8)
+ )
+}
+
+@Composable
+fun TextDemoParagraphLineHeight() {
+ val text1 = "LineHeight=1.0f LineHeight=1.0f LineHeight=1.0f LineHeight=1.0f"
+ val text2 = "LineHeight=1.5f LineHeight=1.5f LineHeight=1.5f LineHeight=1.5f"
+ val text3 = "LineHeight=3.0f LineHeight=3.0f LineHeight=3.0f LineHeight=3.0f"
+
+ Text(
+ text = AnnotatedString(
+ text = text1 + text2 + text3,
+ textStyles = listOf(),
+ paragraphStyles = listOf(
+ AnnotatedString.Item(
+ ParagraphStyle(lineHeight = 1.0f),
+ 0,
+ text1.length
+ ),
+ AnnotatedString.Item(
+ ParagraphStyle(lineHeight = 1.5f),
+ text1.length,
+ text1.length + text2.length
+ ),
+ AnnotatedString.Item(
+ ParagraphStyle(lineHeight = 2f),
+ text1.length + text2.length,
+ text1.length + text2.length + text3.length
+ )
+ )
+ ),
+ style = TextStyle(fontSize = fontSize8)
+ )
+}
+
+@Composable
+fun TextDemoParagraphIndent() {
+ val text1 = "TextIndent firstLine TextIndent firstLine TextIndent firstLine"
+ val text2 = "TextIndent restLine TextIndent restLine TextIndent restLine"
+
+ Text(
+ text = AnnotatedString(
+ text = text1 + text2,
+ textStyles = listOf(),
+ paragraphStyles = listOf(
+ AnnotatedString.Item(
+ ParagraphStyle(textIndent = TextIndent(firstLine = 100.px)),
+ 0,
+ text1.length
+ ),
+ AnnotatedString.Item(
+ ParagraphStyle(textIndent = TextIndent(restLine = 100.px)),
+ text1.length,
+ text1.length + text2.length
+ )
+ )
+ ),
+ style = TextStyle(fontSize = fontSize8)
+ )
+}
+
+@Composable
+fun TextDemoParagraphTextDirection() {
+ val ltrText = "Hello World! Hello World! Hello World! Hello World! Hello World!"
+ val rtlText = "مرحبا بالعالم مرحبا بالعالم مرحبا بالعالم مرحبا بالعالم مرحبا بالعالم"
+ Text(
+ text = AnnotatedString(
+ text = ltrText + rtlText,
+ textStyles = listOf(),
+ paragraphStyles = listOf(
+ AnnotatedString.Item(
+ ParagraphStyle(textDirection = TextDirection.Ltr),
+ 0,
+ ltrText.length
+ ),
+ AnnotatedString.Item(
+ ParagraphStyle(textDirection = TextDirection.Rtl),
+ ltrText.length,
+ ltrText.length + rtlText.length
+ )
+ )
+ ),
+ style = TextStyle(fontSize = fontSize8)
+ )
+}
\ No newline at end of file
diff --git a/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneVariousInputField.kt b/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneVariousInputField.kt
new file mode 100644
index 0000000..47b3abb
--- /dev/null
+++ b/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneVariousInputField.kt
@@ -0,0 +1,287 @@
+/*
+ * 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.ui.text.demos
+
+import androidx.compose.composer
+import androidx.compose.Composable
+import androidx.compose.state
+import androidx.compose.unaryPlus
+import androidx.ui.core.EditorStyle
+import androidx.ui.core.Layout
+import androidx.ui.core.OffsetMap
+import androidx.ui.core.PasswordVisualTransformation
+import androidx.ui.core.Text
+import androidx.ui.core.TextField
+import androidx.ui.core.TransformedText
+import androidx.ui.core.VisualTransformation
+import androidx.ui.core.ipx
+import androidx.ui.graphics.Color
+import androidx.ui.input.EditorModel
+import androidx.ui.input.ImeAction
+import androidx.ui.input.KeyboardType
+import androidx.ui.layout.Column
+import androidx.ui.layout.CrossAxisAlignment
+import androidx.ui.layout.VerticalScroller
+import androidx.ui.text.AnnotatedString
+import androidx.ui.text.TextStyle
+import java.util.Locale
+
+/**
+ * The offset translater used for credit card input field.
+ *
+ * @see creditCardFilter
+ */
+private val creditCardOffsetTranslator = object : OffsetMap {
+ override fun originalToTransformed(offset: Int): Int {
+ if (offset <= 3) return offset
+ if (offset <= 7) return offset + 1
+ if (offset <= 11) return offset + 2
+ if (offset <= 16) return offset + 3
+ return 19
+ }
+
+ override fun transformedToOriginal(offset: Int): Int {
+ if (offset <= 4) return offset
+ if (offset <= 9) return offset - 1
+ if (offset <= 14) return offset - 2
+ if (offset <= 19) return offset - 3
+ return 16
+ }
+}
+
+/**
+ * The visual filter for credit card input field.
+ *
+ * This filter converts up to 16 digits to hyphen connected 4 digits string.
+ * For example, "1234567890123456" will be shown as "1234-5678-9012-3456".
+ */
+private val creditCardFilter = object : VisualTransformation {
+ override fun filter(text: AnnotatedString): TransformedText {
+ val trimmed = if (text.text.length >= 16) text.text.substring(0..15) else text.text
+ var out = ""
+ for (i in 0 until trimmed.length) {
+ out += trimmed[i]
+ if (i % 4 == 3 && i != 15) out += "-"
+ }
+ return TransformedText(AnnotatedString(out), creditCardOffsetTranslator)
+ }
+}
+
+/**
+ * The offset translator which works for all offset keep remains the same.
+ */
+private val identityTranslater = object : OffsetMap {
+ override fun originalToTransformed(offset: Int): Int = offset
+ override fun transformedToOriginal(offset: Int): Int = offset
+}
+
+/**
+ * The visual filter for capitalization.
+ *
+ * This filer converts ASCII characters to capital form.
+ */
+private class CapitalizeTransformation(val locale: Locale = Locale.US) : VisualTransformation {
+ override fun filter(text: AnnotatedString): TransformedText {
+ // TODO(nona): identityTranslater doesn't work for some locale, e.g. Turkish
+ return TransformedText(AnnotatedString(text.text.toUpperCase(locale)), identityTranslater)
+ }
+}
+
+/**
+ * The offset translator for phone number
+ *
+ * @see phoneNumberFilter
+ */
+private val phoneNumberOffsetTranslater = object : OffsetMap {
+ override fun originalToTransformed(offset: Int): Int {
+ return when (offset) {
+ 0 -> 1
+ 1 -> 2
+ 2 -> 3
+ 3 -> 6
+ 4 -> 7
+ 5 -> 8
+ 6 -> 10
+ 7 -> 11
+ 8 -> 12
+ 9 -> 13
+ else -> 14
+ }
+ }
+
+ override fun transformedToOriginal(offset: Int): Int {
+ return when (offset) {
+ 0 -> 0
+ 1 -> 0
+ 2 -> 1
+ 3 -> 2
+ 4 -> 3
+ 5 -> 3
+ 6 -> 3
+ 7 -> 4
+ 8 -> 5
+ 9 -> 6
+ 10 -> 6
+ 11 -> 7
+ 12 -> 8
+ 13 -> 9
+ else -> 10
+ }
+ }
+}
+
+/**
+ * The visual filter for phone number.
+ *
+ * This filter converts up to 10 digits to phone number form.
+ * For example, "1234567890" will be shown as "(123) 456-7890".
+ */
+private val phoneNumberFilter = object : VisualTransformation {
+ override fun filter(text: AnnotatedString): TransformedText {
+ val trimmed = if (text.text.length >= 10) text.text.substring(0..9) else text.text
+ val filled = trimmed + "_".repeat(10 - trimmed.length)
+ val res = "(" + filled.substring(0..2) + ") " + filled.substring(3..5) + "-" +
+ filled.substring(6..9)
+ return TransformedText(AnnotatedString(text = res), phoneNumberOffsetTranslater)
+ }
+}
+
+private val emailFilter = object : VisualTransformation {
+ override fun filter(text: AnnotatedString): TransformedText {
+ return if (text.text.indexOf("@") == -1) {
+ TransformedText(AnnotatedString(text = text.text + "@gmail.com"), identityTranslater)
+ } else {
+ TransformedText(text, identityTranslater)
+ }
+ }
+}
+
+@Composable
+fun VariousInputFieldDemo() {
+ VerticalScroller {
+ Column(crossAxisAlignment = CrossAxisAlignment.Start) {
+ TagLine(tag = "Capitalization")
+ VariousEditLine(
+ keyboardType = KeyboardType.Ascii,
+ onValueChange = { old, new ->
+ if (new.text.any { !it.isLetterOrDigit() }) old else new
+ },
+ visualTransformation = CapitalizeTransformation()
+ )
+
+ TagLine(tag = "Capitalization (Turkish)")
+ VariousEditLine(
+ keyboardType = KeyboardType.Ascii,
+ onValueChange = { old, new ->
+ if (new.text.any { !it.isLetterOrDigit() }) old else new
+ },
+ visualTransformation = CapitalizeTransformation(Locale.forLanguageTag("tr"))
+ )
+
+ TagLine(tag = "Password")
+ VariousEditLine(
+ keyboardType = KeyboardType.Password,
+ onValueChange = { old, new ->
+ if (new.text.any { !it.isLetterOrDigit() }) old else new
+ },
+ visualTransformation = PasswordVisualTransformation()
+ )
+
+ TagLine(tag = "Phone Number")
+ VariousEditLine(
+ keyboardType = KeyboardType.Number,
+ onValueChange = { old, new ->
+ if (new.text.length > 10 || new.text.any { !it.isDigit() }) old else new
+ },
+ visualTransformation = phoneNumberFilter
+ )
+
+ TagLine(tag = "Credit Card")
+ VariousEditLine(
+ keyboardType = KeyboardType.Number,
+ onValueChange = { old, new ->
+ if (new.text.length > 16 || new.text.any { !it.isDigit() }) old else new
+ },
+ visualTransformation = creditCardFilter
+ )
+
+ TagLine(tag = "Email Suggestion")
+ VariousEditLine(
+ keyboardType = KeyboardType.Email,
+ visualTransformation = emailFilter
+ )
+
+ TagLine(tag = "Editfield with Hint Text")
+ HintEditText @Composable {
+ Text(
+ text = "Hint Text",
+ style = TextStyle(
+ color = Color(0xFF888888.toInt()),
+ fontSize = fontSize8
+ )
+ )
+ }
+ }
+ }
+}
+
+@Composable
+fun VariousEditLine(
+ keyboardType: KeyboardType = KeyboardType.Text,
+ imeAction: ImeAction = ImeAction.Unspecified,
+ onValueChange: (EditorModel, EditorModel) -> EditorModel = { _, new -> new },
+ visualTransformation: VisualTransformation
+) {
+ val state = +state { EditorModel() }
+ TextField(
+ value = state.value,
+ keyboardType = keyboardType,
+ imeAction = imeAction,
+ visualTransformation = visualTransformation,
+ onValueChange = { state.value = onValueChange(state.value, it) },
+ editorStyle = EditorStyle(textStyle = TextStyle(fontSize = fontSize8))
+ )
+}
+
+@Composable
+fun HintEditText(hintText: @Composable() () -> Unit) {
+ val state = +state { EditorModel() }
+
+ val inputField = @Composable {
+ TextField(
+ value = state.value,
+ onValueChange = { state.value = it },
+ editorStyle = EditorStyle(textStyle = TextStyle(fontSize = fontSize8))
+ )
+ }
+
+ if (state.value.text.isNotEmpty()) {
+ inputField()
+ } else {
+ Layout(
+ childrenArray = arrayOf(inputField, hintText),
+ layoutBlock = { measurable, constraints ->
+ val inputfieldPlacable = measurable[inputField].first().measure(constraints)
+ val hintTextPlacable = measurable[hintText].first().measure(constraints)
+ layout(inputfieldPlacable.width, inputfieldPlacable.height) {
+ inputfieldPlacable.place(0.ipx, 0.ipx)
+ hintTextPlacable.place(0.ipx, 0.ipx)
+ }
+ }
+ )
+ }
+}
\ No newline at end of file
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/Trace.kt b/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneVariousInputFieldActivity.kt
similarity index 63%
copy from compose/compose-runtime/src/main/java/androidx/compose/Trace.kt
copy to ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneVariousInputFieldActivity.kt
index e451296..0aae714 100644
--- a/compose/compose-runtime/src/main/java/androidx/compose/Trace.kt
+++ b/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneVariousInputFieldActivity.kt
@@ -14,19 +14,16 @@
* limitations under the License.
*/
-package androidx.compose
+package androidx.ui.text.demos
-import android.os.Trace
+import android.app.Activity
+import android.os.Bundle
+import androidx.compose.composer
+import androidx.ui.core.setContent
-/**
- * Wrap the specified [block] in calls to [Trace.beginSection] (with the supplied [sectionName])
- * and [Trace.endSection].
- */
-inline fun <T> trace(sectionName: String, block: () -> T): T {
- Trace.beginSection(sectionName)
- try {
- return block()
- } finally {
- Trace.endSection()
+class CraneVariousInputFieldActivity : Activity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContent { VariousInputFieldDemo() }
}
-}
\ No newline at end of file
+}
diff --git a/ui/ui-text/src/androidTest/java/androidx/ui/text/MultiParagraphIntegrationTest.kt b/ui/ui-text/src/androidTest/java/androidx/ui/text/MultiParagraphIntegrationTest.kt
new file mode 100644
index 0000000..75c21b6
--- /dev/null
+++ b/ui/ui-text/src/androidTest/java/androidx/ui/text/MultiParagraphIntegrationTest.kt
@@ -0,0 +1,1925 @@
+/*
+ * 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.ui.text
+
+import androidx.test.filters.SdkSuppress
+import androidx.test.filters.SmallTest
+import androidx.test.filters.Suppress
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.ui.core.Density
+import androidx.ui.core.PxPosition
+import androidx.ui.core.Sp
+import androidx.ui.core.px
+import androidx.ui.core.sp
+import androidx.ui.core.withDensity
+import androidx.ui.engine.geometry.Rect
+import androidx.ui.text.style.TextDirection
+import androidx.ui.text.FontTestData.Companion.BASIC_MEASURE_FONT
+import androidx.ui.text.font.FontFamily
+import androidx.ui.text.font.asFontFamily
+import androidx.ui.painting.Path
+import androidx.ui.painting.PathOperation
+import androidx.ui.text.style.TextAlign
+import androidx.ui.text.style.TextIndent
+import com.nhaarman.mockitokotlin2.mock
+import org.hamcrest.Matchers.equalTo
+import org.junit.Assert.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+@SmallTest
+class MultiParagraphIntegrationTest {
+ private val fontFamilyMeasureFont = BASIC_MEASURE_FONT.asFontFamily()
+ private val context = InstrumentationRegistry.getInstrumentation().context
+ private val defaultDensity = Density(density = 1f)
+ private val cursorWidth = 4f
+
+ @Test
+ fun empty_string() {
+ withDensity(defaultDensity) {
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val text = ""
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+
+ paragraph.layout(ParagraphConstraints(width = 100.0f))
+
+ assertThat(paragraph.width, equalTo(100.0f))
+
+ assertThat(paragraph.height, equalTo(fontSizeInPx))
+ // defined in sample_font
+ assertThat(paragraph.baseline, equalTo(fontSizeInPx * 0.8f))
+ assertThat(paragraph.maxIntrinsicWidth, equalTo(0.0f))
+ assertThat(paragraph.minIntrinsicWidth, equalTo(0.0f))
+ // TODO(Migration/siyamed): no baseline query per line?
+ // TODO(Migration/siyamed): no line count?
+ }
+ }
+
+ @Test
+ fun single_line_default_values() {
+ withDensity(defaultDensity) {
+ val fontSize = 50.sp
+ val fontSizeInpx = fontSize.toPx().value
+
+ for (text in arrayOf("xyz", "\u05D0\u05D1\u05D2")) {
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+
+ // width greater than text width - 150
+ paragraph.layout(ParagraphConstraints(width = 200.0f))
+
+ assertThat(text, paragraph.width, equalTo(200.0f))
+ assertThat(text, paragraph.height, equalTo(fontSizeInpx))
+ // defined in sample_font
+ assertThat(text, paragraph.baseline, equalTo(fontSizeInpx * 0.8f))
+ assertThat(
+ text,
+ paragraph.maxIntrinsicWidth,
+ equalTo(fontSizeInpx * text.length)
+ )
+ assertThat(text, paragraph.minIntrinsicWidth, equalTo(0.0f))
+ }
+ }
+ }
+
+ @Test
+ fun line_break_default_values() {
+ withDensity(defaultDensity) {
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+
+ for (text in arrayOf("abcdef", "\u05D0\u05D1\u05D2\u05D3\u05D4\u05D5")) {
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+
+ // 3 chars width
+ paragraph.layout(ParagraphConstraints(width = 3 * fontSizeInPx))
+
+ // 3 chars
+ assertThat(text, paragraph.width, equalTo(3 * fontSizeInPx))
+ // 2 lines, 1 line gap
+ assertThat(
+ text,
+ paragraph.height,
+ equalTo(2 * fontSizeInPx + fontSizeInPx / 5.0f)
+ )
+ // defined in sample_font
+ assertThat(text, paragraph.baseline, equalTo(fontSizeInPx * 0.8f))
+ assertThat(
+ text,
+ paragraph.maxIntrinsicWidth,
+ equalTo(fontSizeInPx * text.length)
+ )
+ assertThat(text, paragraph.minIntrinsicWidth, equalTo(0.0f))
+ }
+ }
+ }
+
+ @Test
+ fun didExceedMaxLines_withMaxLinesSmallerThanTextLines_returnsTrue() {
+ val text = "aaa\naa"
+ val maxLines = text.lines().size - 1
+ val paragraph = simpleMultiParagraph(text = text, maxLines = maxLines)
+
+ paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
+ assertThat(paragraph.didExceedMaxLines, equalTo(true))
+ }
+
+ @Test
+ fun didExceedMaxLines_withMaxLinesEqualToTextLines_returnsFalse() {
+ val text = "aaa\naa"
+ val maxLines = text.lines().size
+ val paragraph = simpleMultiParagraph(text = text, maxLines = maxLines)
+
+ paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
+ assertThat(paragraph.didExceedMaxLines, equalTo(false))
+ }
+
+ @Test
+ fun didExceedMaxLines_withMaxLinesGreaterThanTextLines_returnsFalse() {
+ val text = "aaa\naa"
+ val maxLines = text.lines().size + 1
+ val paragraph = simpleMultiParagraph(text = text, maxLines = maxLines)
+
+ paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
+ assertThat(paragraph.didExceedMaxLines, equalTo(false))
+ }
+
+ @Test
+ fun didExceedMaxLines_withMaxLinesSmallerThanTextLines_withLineWrap_returnsTrue() {
+ withDensity(defaultDensity) {
+ val text = "aa"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val maxLines = 1
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontSize = fontSize,
+ maxLines = maxLines
+ )
+
+ // One line can only contain 1 character
+ paragraph.layout(ParagraphConstraints(width = fontSizeInPx))
+ assertThat(paragraph.didExceedMaxLines, equalTo(true))
+ }
+ }
+
+ @Test
+ fun didExceedMaxLines_withMaxLinesEqualToTextLines_withLineWrap_returnsFalse() {
+ val text = "a"
+ val maxLines = text.lines().size
+ val paragraph = simpleMultiParagraph(text = text, fontSize = 50.sp, maxLines = maxLines)
+
+ paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
+ assertThat(paragraph.didExceedMaxLines, equalTo(false))
+ }
+
+ @Test
+ fun didExceedMaxLines_withMaxLinesGreaterThanTextLines_withLineWrap_returnsFalse() {
+ withDensity(defaultDensity) {
+ val text = "aa"
+ val maxLines = 3
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontSize = fontSize,
+ maxLines = maxLines
+ )
+
+ // One line can only contain 1 character
+ paragraph.layout(ParagraphConstraints(width = fontSizeInPx))
+ assertThat(paragraph.didExceedMaxLines, equalTo(false))
+ }
+ }
+
+ @Test
+ fun getOffsetForPosition_ltr() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+
+ paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+ // test positions that are 1, fontSize+1, 2fontSize+1 which maps to chars 0, 1, 2 ...
+ for (i in 0..text.length) {
+ val position = PxPosition((i * fontSizeInPx + 1).px, (fontSizeInPx / 2).px)
+ val offset = paragraph.getOffsetForPosition(position)
+ assertThat(
+ "offset at index $i, position $position does not match",
+ offset,
+ equalTo(i)
+ )
+ }
+ }
+ }
+
+ @Test
+ fun getOffsetForPosition_rtl() {
+ withDensity(defaultDensity) {
+ val text = "\u05D0\u05D1\u05D2"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+
+ paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+
+ // test positions that are 1, fontSize+1, 2fontSize+1 which maps to chars .., 2, 1, 0
+ for (i in 0..text.length) {
+ val position = PxPosition((i * fontSizeInPx + 1).px, (fontSizeInPx / 2).px)
+ val offset = paragraph.getOffsetForPosition(position)
+ assertThat(
+ "offset at index $i, position $position does not match",
+ offset,
+ equalTo(text.length - i)
+ )
+ }
+ }
+ }
+
+ @Test
+ fun getOffsetForPosition_ltr_multiline() {
+ withDensity(defaultDensity) {
+ val firstLine = "abc"
+ val secondLine = "def"
+ val text = firstLine + secondLine
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+
+ paragraph.layout(ParagraphConstraints(width = firstLine.length * fontSizeInPx))
+
+ // test positions are 1, fontSize+1, 2fontSize+1 and always on the second line
+ // which maps to chars 3, 4, 5
+ for (i in 0..secondLine.length) {
+ val position = PxPosition((i * fontSizeInPx + 1).px, (fontSizeInPx * 1.5f).px)
+ val offset = paragraph.getOffsetForPosition(position)
+ assertThat(
+ "offset at index $i, position $position, second line does not match",
+ offset,
+ equalTo(i + firstLine.length)
+ )
+ }
+ }
+ }
+
+ @Test
+ fun getOffsetForPosition_rtl_multiline() {
+ withDensity(defaultDensity) {
+ val firstLine = "\u05D0\u05D1\u05D2"
+ val secondLine = "\u05D3\u05D4\u05D5"
+ val text = firstLine + secondLine
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+
+ paragraph.layout(ParagraphConstraints(width = firstLine.length * fontSizeInPx))
+
+ // test positions are 1, fontSize+1, 2fontSize+1 and always on the second line
+ // which maps to chars 5, 4, 3
+ for (i in 0..secondLine.length) {
+ val position = PxPosition((i * fontSizeInPx + 1).px, (fontSizeInPx * 1.5f).px)
+ val offset = paragraph.getOffsetForPosition(position)
+ assertThat(
+ "offset at index $i, position $position, second line does not match",
+ offset,
+ equalTo(text.length - i)
+ )
+ }
+ }
+ }
+
+ @Test
+ fun getOffsetForPosition_ltr_width_outOfBounds() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+
+ paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+
+ // greater than width
+ var position = PxPosition((fontSizeInPx * text.length * 2).px, (fontSizeInPx / 2).px)
+ var offset = paragraph.getOffsetForPosition(position)
+ assertThat(offset, equalTo(text.length))
+
+ // negative
+ position = PxPosition((-1 * fontSizeInPx).px, (fontSizeInPx / 2).px)
+ offset = paragraph.getOffsetForPosition(position)
+ assertThat(offset, equalTo(0))
+ }
+ }
+
+ @Test
+ fun getOffsetForPosition_ltr_height_outOfBounds() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+
+ paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+
+ // greater than height
+ var position = PxPosition((fontSizeInPx / 2).px, (fontSizeInPx * text.length * 2).px)
+ var offset = paragraph.getOffsetForPosition(position)
+ assertThat(offset, equalTo(0))
+
+ // negative
+ position = PxPosition((fontSizeInPx / 2).px, (-1 * fontSizeInPx).px)
+ offset = paragraph.getOffsetForPosition(position)
+ assertThat(offset, equalTo(0))
+ }
+ }
+
+ @Test
+ fun getOffsetForPosition_lineBreak() {
+ withDensity(defaultDensity) {
+ val text = "abc\n"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+
+ paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+
+ assertThat(
+ paragraph.getOffsetForPosition(PxPosition((3 * fontSizeInPx).px, 0.px)),
+ equalTo(3)
+ )
+
+ assertThat(
+ paragraph.getOffsetForPosition(PxPosition(0.px, (fontSizeInPx * 1.5f).px)),
+ equalTo(4)
+ )
+ }
+ }
+
+ @Test
+ fun getOffsetForPosition_multiple_paragraph() {
+ withDensity(defaultDensity) {
+ val text = "abcdef"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontSize = fontSize,
+ paragraphStyles = listOf(
+ AnnotatedString.Item(ParagraphStyle(), 0, 3)
+ )
+ )
+
+ paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+
+ for (i in 0 until 3) {
+ assertThat(
+ paragraph.getOffsetForPosition(PxPosition((i * fontSizeInPx).px, 0.px)),
+ equalTo(i)
+ )
+ }
+
+ for (i in 3 until 6) {
+ assertThat(
+ paragraph.getOffsetForPosition(
+ PxPosition(((i - 3) * fontSizeInPx).px, fontSizeInPx.px)
+ ),
+ equalTo(i)
+ )
+ }
+ }
+ }
+
+ @Test
+ fun getBoundingBox_ltr_singleLine() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+
+ paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+ for (i in 0 until text.length) {
+ val box = paragraph.getBoundingBox(i)
+ assertThat(box.left, equalTo(i * fontSizeInPx))
+ assertThat(box.right, equalTo((i + 1) * fontSizeInPx))
+ assertThat(box.top, equalTo(0f))
+ assertThat(box.bottom, equalTo(fontSizeInPx))
+ }
+ }
+ }
+
+ @Test
+ fun getBoundingBox_ltr_multiLines() {
+ withDensity(defaultDensity) {
+ val firstLine = "abc"
+ val secondLine = "def"
+ val text = firstLine + secondLine
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+
+ paragraph.layout(ParagraphConstraints(width = firstLine.length * fontSizeInPx))
+
+ // test positions are 3, 4, 5 and always on the second line
+ // which maps to chars 3, 4, 5
+ for (i in 0..secondLine.length - 1) {
+ val textPosition = i + firstLine.length
+ val box = paragraph.getBoundingBox(textPosition)
+ assertThat(box.left, equalTo(i * fontSizeInPx))
+ assertThat(box.right, equalTo((i + 1) * fontSizeInPx))
+ assertThat(box.top, equalTo(fontSizeInPx))
+ assertThat(box.bottom, equalTo((2f + 1 / 5f) * fontSizeInPx))
+ }
+ }
+ }
+
+ @Test(expected = java.lang.IndexOutOfBoundsException::class)
+ fun getBoundingBox_ltr_textPosition_negative() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+ paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+
+ paragraph.getBoundingBox(-1)
+ }
+ }
+
+ @Suppress
+ @Test(expected = java.lang.IndexOutOfBoundsException::class)
+ fun getBoundingBox_ltr_textPosition_larger_than_length_throw_exception() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+
+ paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+
+ val textPosition = text.length + 1
+ paragraph.getBoundingBox(textPosition)
+ }
+ }
+
+ @Test(expected = java.lang.AssertionError::class)
+ fun getCursorRect_larger_than_length_throw_exception() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+
+ paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+
+ paragraph.getCursorRect(text.length + 1)
+ }
+ }
+
+ @Test(expected = java.lang.AssertionError::class)
+ fun getCursorRect_negative_throw_exception() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+
+ paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+
+ paragraph.getCursorRect(-1)
+ }
+ }
+
+ @Test
+ fun getCursorRect_ltr_singleLine() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+
+ paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+
+ for (i in 0 until text.length) {
+ val cursorRect = paragraph.getCursorRect(i)
+ val cursorXOffset = i * fontSizeInPx
+ assertThat(
+ cursorRect,
+ equalTo(Rect(
+ left = cursorXOffset - cursorWidth / 2,
+ top = 0f,
+ right = cursorXOffset + cursorWidth / 2,
+ bottom = fontSizeInPx
+ ))
+ )
+ }
+ }
+ }
+
+ @Test
+ fun getCursorRect_ltr_multiLines() {
+ withDensity(defaultDensity) {
+ val text = "abcdef"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+ val charsPerLine = 3
+
+ paragraph.layout(ParagraphConstraints(width = charsPerLine * fontSizeInPx))
+
+ for (i in 0 until charsPerLine) {
+ val cursorXOffset = i * fontSizeInPx
+ assertThat(
+ paragraph.getCursorRect(i),
+ equalTo(Rect(
+ left = cursorXOffset - cursorWidth / 2,
+ top = 0f,
+ right = cursorXOffset + cursorWidth / 2,
+ bottom = fontSizeInPx
+ ))
+ )
+ }
+
+ for (i in charsPerLine until text.length) {
+ val cursorXOffset = (i % charsPerLine) * fontSizeInPx
+ assertThat(
+ paragraph.getCursorRect(i),
+ equalTo(Rect(
+ left = cursorXOffset - cursorWidth / 2,
+ top = fontSizeInPx,
+ right = cursorXOffset + cursorWidth / 2,
+ bottom = fontSizeInPx * 2.2f
+ ))
+ )
+ }
+ }
+ }
+
+ @Test
+ fun getCursorRect_rtl_singleLine() {
+ withDensity(defaultDensity) {
+ val text = "\u05D0\u05D1\u05D2"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+
+ paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+
+ for (i in 0 until text.length) {
+ val cursorXOffset = (text.length - i) * fontSizeInPx
+ assertThat(
+ paragraph.getCursorRect(i),
+ equalTo(Rect(
+ left = cursorXOffset - cursorWidth / 2,
+ top = 0f,
+ right = cursorXOffset + cursorWidth / 2,
+ bottom = fontSizeInPx
+ ))
+ )
+ }
+ }
+ }
+
+ @Test
+ fun getCursorRect_rtl_multiLines() {
+ withDensity(defaultDensity) {
+ val text = "\u05D0\u05D1\u05D2\u05D0\u05D1\u05D2"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+ val charsPerLine = 3
+
+ paragraph.layout(ParagraphConstraints(width = charsPerLine * fontSizeInPx))
+
+ for (i in 0 until charsPerLine) {
+ val cursorXOffset = (charsPerLine - i) * fontSizeInPx
+ assertThat(
+ paragraph.getCursorRect(i),
+ equalTo(Rect(
+ left = cursorXOffset - cursorWidth / 2,
+ top = 0f,
+ right = cursorXOffset + cursorWidth / 2,
+ bottom = fontSizeInPx
+ ))
+ )
+ }
+
+ for (i in charsPerLine until text.length) {
+ val cursorXOffset = (charsPerLine - i % charsPerLine) * fontSizeInPx
+ assertThat(
+ paragraph.getCursorRect(i),
+ equalTo(Rect(
+ left = cursorXOffset - cursorWidth / 2,
+ top = fontSizeInPx,
+ right = cursorXOffset + cursorWidth / 2,
+ bottom = fontSizeInPx * 2.2f
+ ))
+ )
+ }
+ }
+ }
+
+ @Test
+ fun getPrimaryHorizontal_ltr_singleLine_textDirectionDefault() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+
+ paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+
+ for (i in 0..text.length) {
+ assertThat(
+ paragraph.getPrimaryHorizontal(i),
+ equalTo(fontSizeInPx * i)
+ )
+ }
+ }
+ }
+
+ @Test
+ fun getPrimaryHorizontal_rtl_singleLine_textDirectionDefault() {
+ withDensity(defaultDensity) {
+ val text = "\u05D0\u05D1\u05D2"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ for (i in 0..text.length) {
+ assertThat(
+ paragraph.getPrimaryHorizontal(i),
+ equalTo(width - fontSizeInPx * i)
+ )
+ }
+ }
+ }
+
+ @Test
+ fun getPrimaryHorizontal_bidi_singleLine_textDirectionDefault() {
+ withDensity(defaultDensity) {
+ val ltrText = "abc"
+ val rtlText = "\u05D0\u05D1\u05D2"
+ val text = ltrText + rtlText
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ for (i in 0..ltrText.length) {
+ assertThat(
+ paragraph.getPrimaryHorizontal(i),
+ equalTo(fontSizeInPx * i)
+ )
+ }
+
+ for (i in 1 until rtlText.length) {
+ assertThat(
+ paragraph.getPrimaryHorizontal(i + ltrText.length),
+ equalTo(width - fontSizeInPx * i)
+ )
+ }
+
+ assertThat(
+ paragraph.getPrimaryHorizontal(text.length),
+ equalTo(width)
+ )
+ }
+ }
+
+ @Test
+ fun getPrimaryHorizontal_ltr_singleLine_textDirectionRtl() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontSize = fontSize,
+ textDirection = TextDirection.Rtl
+ )
+ val width = text.length * fontSizeInPx
+ paragraph.layout(ParagraphConstraints(width))
+
+ assertThat(paragraph.getPrimaryHorizontal(0), equalTo(width))
+
+ for (i in 1 until text.length) {
+ assertThat(
+ paragraph.getPrimaryHorizontal(i),
+ equalTo(fontSizeInPx * i)
+ )
+ }
+
+ assertThat(paragraph.getPrimaryHorizontal(text.length), equalTo(0f))
+ }
+ }
+
+ @Test
+ fun getPrimaryHorizontal_rtl_singleLine_textDirectionLtr() {
+ withDensity(defaultDensity) {
+ val text = "\u05D0\u05D1\u05D2"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontSize = fontSize,
+ textDirection = TextDirection.Ltr
+ )
+ val width = text.length * fontSizeInPx
+ paragraph.layout(ParagraphConstraints(width))
+
+ assertThat(paragraph.getPrimaryHorizontal(0), equalTo(0f))
+
+ for (i in 1 until text.length) {
+ assertThat(
+ paragraph.getPrimaryHorizontal(i),
+ equalTo(width - fontSizeInPx * i)
+ )
+ }
+
+ assertThat(paragraph.getPrimaryHorizontal(text.length), equalTo(width))
+ }
+ }
+
+ @Test
+ fun getPrimaryHorizontal_bidi_singleLine_textDirectionLtr() {
+ withDensity(defaultDensity) {
+ val ltrText = "abc"
+ val rtlText = "\u05D0\u05D1\u05D2"
+ val text = ltrText + rtlText
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontSize = fontSize,
+ textDirection = TextDirection.Ltr
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ for (i in 0..ltrText.length) {
+ assertThat(
+ paragraph.getPrimaryHorizontal(i),
+ equalTo(fontSizeInPx * i)
+ )
+ }
+
+ for (i in 1 until rtlText.length) {
+ assertThat(
+ paragraph.getPrimaryHorizontal(i + ltrText.length),
+ equalTo(width - fontSizeInPx * i)
+ )
+ }
+
+ assertThat(
+ paragraph.getPrimaryHorizontal(text.length),
+ equalTo(width)
+ )
+ }
+ }
+
+ @Test
+ fun getPrimaryHorizontal_bidi_singleLine_textDirectionRtl() {
+ withDensity(defaultDensity) {
+ val ltrText = "abc"
+ val rtlText = "\u05D0\u05D1\u05D2"
+ val text = ltrText + rtlText
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontSize = fontSize,
+ textDirection = TextDirection.Rtl
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ assertThat(paragraph.getPrimaryHorizontal(0), equalTo(width))
+ // Notice that abc is
+ for (i in 1 until ltrText.length) {
+ assertThat(
+ paragraph.getPrimaryHorizontal(i),
+ equalTo(rtlText.length * fontSizeInPx + i * fontSizeInPx)
+ )
+ }
+
+ for (i in 0..rtlText.length) {
+ assertThat(
+ paragraph.getPrimaryHorizontal(i + ltrText.length),
+ equalTo(rtlText.length * fontSizeInPx - i * fontSizeInPx)
+ )
+ }
+ }
+ }
+
+ @Test
+ fun getPrimaryHorizontal_ltr_newLine_textDirectionDefault() {
+ withDensity(defaultDensity) {
+ val text = "abc\n"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ assertThat(paragraph.getPrimaryHorizontal(text.length), equalTo(0f))
+ }
+ }
+
+ @Test
+ fun getPrimaryHorizontal_rtl_newLine_textDirectionDefault() {
+ withDensity(defaultDensity) {
+ val text = "\u05D0\u05D1\u05D2\n"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ assertThat(paragraph.getPrimaryHorizontal(text.length), equalTo(0f))
+ }
+ }
+
+ @Test
+ fun getPrimaryHorizontal_ltr_newLine_textDirectionRtl() {
+ withDensity(defaultDensity) {
+ val text = "abc\n"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontSize = fontSize,
+ textDirection = TextDirection.Rtl
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ assertThat(paragraph.getPrimaryHorizontal(text.length), equalTo(width))
+ }
+ }
+
+ @Test
+ fun getPrimaryHorizontal_rtl_newLine_textDirectionLtr() {
+ withDensity(defaultDensity) {
+ val text = "\u05D0\u05D1\u05D2\n"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontSize = fontSize,
+ textDirection = TextDirection.Ltr
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ assertThat(paragraph.getPrimaryHorizontal(text.length), equalTo(0f))
+ }
+ }
+
+ @Test
+ fun getLineForOffset_singleLine() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ for (i in 0..text.lastIndex) {
+ assertThat(paragraph.getLineForOffset(i), equalTo(0))
+ }
+ }
+ }
+
+ @Test
+ fun getLineForOffset_multiLines() {
+ withDensity(defaultDensity) {
+ val text = "a\nb\nc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ for (i in 0..text.lastIndex) {
+ assertThat(paragraph.getLineForOffset(i), equalTo(i / 2))
+ }
+ }
+ }
+
+ @Test
+ fun getLineForOffset_multiParagraph() {
+ withDensity(defaultDensity) {
+ val text = "abcd"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontSize = fontSize,
+ paragraphStyles = listOf(AnnotatedString.Item(ParagraphStyle(), 0, 2))
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+ assertThat(paragraph.getLineForOffset(0), equalTo(0))
+ assertThat(paragraph.getLineForOffset(1), equalTo(0))
+ assertThat(paragraph.getLineForOffset(2), equalTo(1))
+ assertThat(paragraph.getLineForOffset(3), equalTo(1))
+ }
+ }
+
+ @Test
+ fun getLineForOffset_emptyParagraph() {
+ withDensity(defaultDensity) {
+ val text = "abcd"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontSize = fontSize,
+ paragraphStyles = listOf(AnnotatedString.Item(ParagraphStyle(), 2, 2))
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+ assertThat(paragraph.getLineForOffset(0), equalTo(0))
+ assertThat(paragraph.getLineForOffset(1), equalTo(0))
+ // The empty paragraph takes one line
+ assertThat(paragraph.getLineForOffset(2), equalTo(2))
+ assertThat(paragraph.getLineForOffset(3), equalTo(2))
+ }
+ }
+
+ @Test(expected = java.lang.IndexOutOfBoundsException::class)
+ fun getLineForOffset_negativeOffset() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ paragraph.getLineForOffset(-1)
+ }
+ }
+
+ @Test(expected = java.lang.IndexOutOfBoundsException::class)
+ fun getLineForOffset_outOfBoundary() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ paragraph.getLineForOffset(text.length)
+ }
+ }
+
+ @Test
+ fun testGetPathForRange_singleLine() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontFamily = fontFamilyMeasureFont,
+ fontSize = fontSize
+ )
+ paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
+
+ val expectedPath = Path()
+ val lineLeft = paragraph.getLineLeft(0)
+ val lineRight = paragraph.getLineRight(0)
+ expectedPath.addRect(
+ Rect(
+ lineLeft,
+ 0f,
+ lineRight - fontSizeInPx,
+ fontSizeInPx
+ )
+ )
+
+ // Select "ab"
+ val actualPath = paragraph.getPathForRange(0, 2)
+
+ val diff = Path.combine(PathOperation.difference, expectedPath, actualPath).getBounds()
+ assertThat(diff, equalTo(Rect.zero))
+ }
+ }
+
+ @Test
+ fun testGetPathForRange_multiLines() {
+ withDensity(defaultDensity) {
+ val text = "abc\nabc"
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontFamily = fontFamilyMeasureFont,
+ fontSize = fontSize
+ )
+ paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
+
+ val expectedPath = Path()
+ val firstLineLeft = paragraph.getLineLeft(0)
+ val secondLineLeft = paragraph.getLineLeft(1)
+ val firstLineRight = paragraph.getLineRight(0)
+ val secondLineRight = paragraph.getLineRight(1)
+ expectedPath.addRect(
+ Rect(
+ firstLineLeft + fontSizeInPx,
+ 0f,
+ firstLineRight,
+ fontSizeInPx
+ )
+ )
+ expectedPath.addRect(
+ Rect(
+ secondLineLeft,
+ fontSizeInPx,
+ secondLineRight - fontSizeInPx,
+ paragraph.height
+ )
+ )
+
+ // Select "bc\nab"
+ val actualPath = paragraph.getPathForRange(1, 6)
+
+ val diff = Path.combine(PathOperation.difference, expectedPath, actualPath).getBounds()
+ assertThat(diff, equalTo(Rect.zero))
+ }
+ }
+
+ @Test
+ fun testGetPathForRange_Bidi() {
+ withDensity(defaultDensity) {
+ val textLTR = "Hello"
+ val textRTL = "שלום"
+ val text = textLTR + textRTL
+ val selectionLTRStart = 2
+ val selectionRTLEnd = 2
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontFamily = fontFamilyMeasureFont,
+ fontSize = fontSize
+ )
+ paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
+
+ val expectedPath = Path()
+ val lineLeft = paragraph.getLineLeft(0)
+ val lineRight = paragraph.getLineRight(0)
+ expectedPath.addRect(
+ Rect(
+ lineLeft + selectionLTRStart * fontSizeInPx,
+ 0f,
+ lineLeft + textLTR.length * fontSizeInPx,
+ fontSizeInPx
+ )
+ )
+ expectedPath.addRect(
+ Rect(
+ lineRight - selectionRTLEnd * fontSizeInPx,
+ 0f,
+ lineRight,
+ fontSizeInPx
+ )
+ )
+
+ // Select "llo..של"
+ val actualPath =
+ paragraph.getPathForRange(selectionLTRStart, textLTR.length + selectionRTLEnd)
+
+ val diff = Path.combine(PathOperation.difference, expectedPath, actualPath).getBounds()
+ assertThat(diff, equalTo(Rect.zero))
+ }
+ }
+
+ @Test
+ fun testGetPathForRange_Start_Equals_End_Returns_Empty_Path() {
+ val text = "abc"
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontFamily = fontFamilyMeasureFont,
+ fontSize = 20.sp
+ )
+ paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
+
+ val actualPath = paragraph.getPathForRange(1, 1)
+
+ assertThat(actualPath.getBounds(), equalTo(Rect.zero))
+ }
+
+ @Test
+ fun testGetPathForRange_Empty_Text() {
+ val text = ""
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontFamily = fontFamilyMeasureFont,
+ fontSize = 20.sp
+ )
+ paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
+
+ val actualPath = paragraph.getPathForRange(0, 0)
+
+ assertThat(actualPath.getBounds(), equalTo(Rect.zero))
+ }
+
+ @Test
+ fun testGetPathForRange_Surrogate_Pair_Start_Middle_Second_Character_Selected() {
+ withDensity(defaultDensity) {
+ val text = "\uD834\uDD1E\uD834\uDD1F"
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontFamily = fontFamilyMeasureFont,
+ fontSize = fontSize
+ )
+ paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
+
+ val expectedPath = Path()
+ val lineRight = paragraph.getLineRight(0)
+ expectedPath.addRect(Rect(lineRight / 2, 0f, lineRight, fontSizeInPx))
+
+ // Try to select "\uDD1E\uD834\uDD1F", only "\uD834\uDD1F" is selected.
+ val actualPath = paragraph.getPathForRange(1, text.length)
+
+ val diff = Path.combine(PathOperation.difference, expectedPath, actualPath).getBounds()
+ assertThat(diff, equalTo(Rect.zero))
+ }
+ }
+
+ @Test
+ fun testGetPathForRange_Surrogate_Pair_End_Middle_Second_Character_Selected() {
+ withDensity(defaultDensity) {
+ val text = "\uD834\uDD1E\uD834\uDD1F"
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontFamily = fontFamilyMeasureFont,
+ fontSize = fontSize
+ )
+ paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
+
+ val expectedPath = Path()
+ val lineRight = paragraph.getLineRight(0)
+ expectedPath.addRect(Rect(lineRight / 2, 0f, lineRight, fontSizeInPx))
+
+ // Try to select "\uDD1E\uD834", actually "\uD834\uDD1F" is selected.
+ val actualPath = paragraph.getPathForRange(1, text.length - 1)
+
+ val diff = Path.combine(PathOperation.difference, expectedPath, actualPath).getBounds()
+ assertThat(diff, equalTo(Rect.zero))
+ }
+ }
+
+ @Test
+ fun testGetPathForRange_Surrogate_Pair_Start_Middle_End_Same_Character_Returns_Line_Segment() {
+ withDensity(defaultDensity) {
+ val text = "\uD834\uDD1E\uD834\uDD1F"
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontFamily = fontFamilyMeasureFont,
+ fontSize = fontSize
+ )
+ paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
+
+ val expectedPath = Path()
+ val lineRight = paragraph.getLineRight(0)
+ expectedPath.addRect(Rect(lineRight / 2, 0f, lineRight / 2, fontSizeInPx))
+
+ // Try to select "\uDD1E", get vertical line segment after this character.
+ val actualPath = paragraph.getPathForRange(1, 2)
+
+ val diff = Path.combine(PathOperation.difference, expectedPath, actualPath).getBounds()
+ assertThat(diff, equalTo(Rect.zero))
+ }
+ }
+
+ @Test
+ fun testGetPathForRange_Emoji_Sequence() {
+ withDensity(defaultDensity) {
+ val text = "\u1F600\u1F603\u1F604\u1F606"
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontFamily = fontFamilyMeasureFont,
+ fontSize = fontSize
+ )
+ paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
+
+ val expectedPath = Path()
+ val lineLeft = paragraph.getLineLeft(0)
+ val lineRight = paragraph.getLineRight(0)
+ expectedPath.addRect(
+ Rect(
+ lineLeft + fontSizeInPx,
+ 0f,
+ lineRight - fontSizeInPx,
+ fontSizeInPx
+ )
+ )
+
+ // Select "\u1F603\u1F604"
+ val actualPath = paragraph.getPathForRange(1, text.length - 1)
+
+ val diff = Path.combine(PathOperation.difference, expectedPath, actualPath).getBounds()
+ assertThat(diff, equalTo(Rect.zero))
+ }
+ }
+
+ @Test
+ fun testGetPathForRange_Unicode_200D_Return_Line_Segment() {
+ withDensity(defaultDensity) {
+ val text = "\u200D"
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontFamily = fontFamilyMeasureFont,
+ fontSize = fontSize
+ )
+ paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
+
+ val expectedPath = Path()
+ val lineLeft = paragraph.getLineLeft(0)
+ val lineRight = paragraph.getLineRight(0)
+ expectedPath.addRect(Rect(lineLeft, 0f, lineRight, fontSizeInPx))
+
+ val actualPath = paragraph.getPathForRange(0, 1)
+
+ assertThat(lineLeft, equalTo(lineRight))
+ val diff = Path.combine(PathOperation.difference, expectedPath, actualPath).getBounds()
+ assertThat(diff, equalTo(Rect.zero))
+ }
+ }
+
+ @Test
+ fun testGetPathForRange_Unicode_2066_Return_Line_Segment() {
+ withDensity(defaultDensity) {
+ val text = "\u2066"
+ val fontSize = 20f.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontFamily = fontFamilyMeasureFont,
+ fontSize = fontSize
+ )
+ paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
+
+ val expectedPath = Path()
+ val lineLeft = paragraph.getLineLeft(0)
+ val lineRight = paragraph.getLineRight(0)
+ expectedPath.addRect(Rect(lineLeft, 0f, lineRight, fontSizeInPx))
+
+ val actualPath = paragraph.getPathForRange(0, 1)
+
+ assertThat(lineLeft, equalTo(lineRight))
+ val diff = Path.combine(PathOperation.difference, expectedPath, actualPath).getBounds()
+ assertThat(diff, equalTo(Rect.zero))
+ }
+ }
+
+ @Test
+ fun testGetWordBoundary() {
+ val text = "abc def"
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontFamily = fontFamilyMeasureFont,
+ fontSize = 20.sp
+ )
+ paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
+
+ val result = paragraph.getWordBoundary(text.indexOf('a'))
+
+ assertThat(result.start, equalTo(text.indexOf('a')))
+ assertThat(result.end, equalTo(text.indexOf(' ')))
+ }
+
+ @Test
+ fun testGetWordBoundary_Bidi() {
+ val text = "abc \u05d0\u05d1\u05d2 def"
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontFamily = fontFamilyMeasureFont,
+ fontSize = 20.sp
+ )
+ paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
+
+ val resultEnglish = paragraph.getWordBoundary(text.indexOf('a'))
+ val resultHebrew = paragraph.getWordBoundary(text.indexOf('\u05d1'))
+
+ assertThat(resultEnglish.start, equalTo(text.indexOf('a')))
+ assertThat(resultEnglish.end, equalTo(text.indexOf(' ')))
+ assertThat(resultHebrew.start, equalTo(text.indexOf('\u05d0')))
+ assertThat(resultHebrew.end, equalTo(text.indexOf('\u05d2') + 1))
+ }
+
+ @Test
+ fun width_default_value() {
+ val paragraph = simpleMultiParagraph()
+
+ assertThat(paragraph.width, equalTo(0.0f))
+ }
+
+ @Test
+ fun height_default_value() {
+ val paragraph = simpleMultiParagraph()
+
+ assertThat(paragraph.height, equalTo(0.0f))
+ }
+
+ @Test
+ fun minIntrinsicWidth_default_value() {
+ val paragraph = simpleMultiParagraph()
+
+ assertThat(paragraph.minIntrinsicWidth, equalTo(0.0f))
+ }
+
+ @Test
+ fun maxIntrinsicWidth_default_value() {
+ val paragraph = simpleMultiParagraph()
+
+ assertThat(paragraph.maxIntrinsicWidth, equalTo(0.0f))
+ }
+
+ @Test
+ fun alphabeticBaseline_default_value() {
+ val paragraph = simpleMultiParagraph()
+
+ assertThat(paragraph.baseline, equalTo(0.0f))
+ }
+
+ @Test
+ fun didExceedMaxLines_default_value() {
+ val paragraph = simpleMultiParagraph()
+
+ assertThat(paragraph.didExceedMaxLines, equalTo(false))
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun paint_throws_exception_if_layout_is_not_called() {
+ val paragraph = simpleMultiParagraph()
+
+ paragraph.paint(mock())
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun getPositionForOffset_throws_exception_if_layout_is_not_called() {
+ val paragraph = simpleMultiParagraph()
+
+ paragraph.getOffsetForPosition(PxPosition.Origin)
+ }
+
+ @Test(expected = AssertionError::class)
+ fun getPathForRange_throws_exception_if_start_larger_than_end() {
+ val text = "ab"
+ val textStart = 0
+ val textEnd = text.length
+ val paragraph = simpleMultiParagraph(text = text)
+
+ paragraph.getPathForRange(textEnd, textStart)
+ }
+
+ @Test(expected = AssertionError::class)
+ fun getPathForRange_throws_exception_if_start_is_smaller_than_zero() {
+ val text = "ab"
+ val textStart = 0
+ val textEnd = text.length
+ val paragraph = simpleMultiParagraph(text = text)
+
+ paragraph.getPathForRange(textStart - 2, textEnd - 1)
+ }
+
+ @Test(expected = AssertionError::class)
+ fun getPathForRange_throws_exception_if_end_is_larger_than_text_length() {
+ val text = "ab"
+ val textStart = 0
+ val textEnd = text.length
+ val paragraph = simpleMultiParagraph(text = text)
+
+ paragraph.getPathForRange(textStart, textEnd + 1)
+ }
+
+ @Test
+ fun textAlign_defaultValue_alignsStart() {
+ withDensity(defaultDensity) {
+ val textLTR = "aa"
+ val textRTL = "\u05D0\u05D0"
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+
+ val paragraphLTR = simpleMultiParagraph(
+ text = textLTR,
+ fontSize = fontSize
+ )
+ val layoutLTRWidth = (textLTR.length + 2) * fontSizeInPx
+ paragraphLTR.layout(ParagraphConstraints(width = layoutLTRWidth))
+
+ val paragraphRTL = simpleMultiParagraph(
+ text = textRTL,
+ fontSize = fontSize
+ )
+ val layoutRTLWidth = (textRTL.length + 2) * fontSizeInPx
+ paragraphRTL.layout(ParagraphConstraints(width = layoutRTLWidth))
+
+ // When textAlign is TextAlign.start, LTR aligns to left, RTL aligns to right.
+ assertThat(paragraphLTR.getLineLeft(0), equalTo(0.0f))
+ assertThat(paragraphRTL.getLineRight(0), equalTo(layoutRTLWidth))
+ }
+ }
+
+ @Test
+ fun textAlign_whenAlignLeft_returnsZeroForGetLineLeft() {
+ withDensity(defaultDensity) {
+ val texts = listOf("aa", "\u05D0\u05D0")
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+
+ texts.map { text ->
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ textAlign = TextAlign.Left,
+ fontSize = fontSize
+ )
+ val layoutWidth = (text.length + 2) * fontSizeInPx
+ paragraph.layout(ParagraphConstraints(width = layoutWidth))
+
+ assertThat(paragraph.getLineLeft(0), equalTo(0.0f))
+ }
+ }
+ }
+
+ @Test
+ fun textAlign_whenAlignRight_returnsLayoutWidthForGetLineRight() {
+ withDensity(defaultDensity) {
+ val texts = listOf("aa", "\u05D0\u05D0")
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+
+ texts.map { text ->
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ textAlign = TextAlign.Right,
+ fontSize = fontSize
+ )
+ val layoutWidth = (text.length + 2) * fontSizeInPx
+ paragraph.layout(ParagraphConstraints(width = layoutWidth))
+
+ assertThat(paragraph.getLineRight(0), equalTo(layoutWidth))
+ }
+ }
+ }
+
+ @Test
+ fun textAlign_whenAlignCenter_textIsCentered() {
+ withDensity(defaultDensity) {
+ val texts = listOf("aa", "\u05D0\u05D0")
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+
+ texts.map { text ->
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ textAlign = TextAlign.Center,
+ fontSize = fontSize
+ )
+ val layoutWidth = (text.length + 2) * fontSizeInPx
+ paragraph.layout(ParagraphConstraints(width = layoutWidth))
+ val textWidth = text.length * fontSizeInPx
+
+ assertThat(
+ paragraph.getLineLeft(0),
+ equalTo(layoutWidth / 2 - textWidth / 2)
+ )
+ assertThat(
+ paragraph.getLineRight(0),
+ equalTo(layoutWidth / 2 + textWidth / 2)
+ )
+ }
+ }
+ }
+
+ @Test
+ fun textAlign_whenAlignStart_withLTR_returnsZeroForGetLineLeft() {
+ withDensity(defaultDensity) {
+ val text = "aa"
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val layoutWidth = (text.length + 2) * fontSizeInPx
+
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ textAlign = TextAlign.Start,
+ fontSize = fontSize
+ )
+ paragraph.layout(ParagraphConstraints(width = layoutWidth))
+
+ assertThat(paragraph.getLineLeft(0), equalTo(0.0f))
+ }
+ }
+
+ @Test
+ fun textAlign_whenAlignEnd_withLTR_returnsLayoutWidthForGetLineRight() {
+ withDensity(defaultDensity) {
+ val text = "aa"
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val layoutWidth = (text.length + 2) * fontSizeInPx
+
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ textAlign = TextAlign.End,
+ fontSize = fontSize
+ )
+ paragraph.layout(ParagraphConstraints(width = layoutWidth))
+
+ assertThat(paragraph.getLineRight(0), equalTo(layoutWidth))
+ }
+ }
+
+ @Test
+ fun textAlign_whenAlignStart_withRTL_returnsLayoutWidthForGetLineRight() {
+ withDensity(defaultDensity) {
+ val text = "\u05D0\u05D0"
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val layoutWidth = (text.length + 2) * fontSizeInPx
+
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ textAlign = TextAlign.Start,
+ fontSize = fontSize
+ )
+ paragraph.layout(ParagraphConstraints(width = layoutWidth))
+
+ assertThat(paragraph.getLineRight(0), equalTo(layoutWidth))
+ }
+ }
+
+ @Test
+ fun textAlign_whenAlignEnd_withRTL_returnsZeroForGetLineLeft() {
+ withDensity(defaultDensity) {
+ val text = "\u05D0\u05D0"
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val layoutWidth = (text.length + 2) * fontSizeInPx
+
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ textAlign = TextAlign.End,
+ fontSize = fontSize
+ )
+ paragraph.layout(ParagraphConstraints(width = layoutWidth))
+
+ assertThat(paragraph.getLineLeft(0), equalTo(0.0f))
+ }
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 28)
+ // We have to test justification above API 28 because of this bug b/68009059, where devices
+ // before API 28 may have an extra space at the end of line.
+ fun textAlign_whenAlignJustify_justifies() {
+ withDensity(defaultDensity) {
+ val text = "a a a"
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val layoutWidth = ("a a".length + 1) * fontSizeInPx
+
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ textAlign = TextAlign.Justify,
+ fontSize = fontSize
+ )
+ paragraph.layout(ParagraphConstraints(width = layoutWidth))
+
+ assertThat(paragraph.getLineLeft(0), equalTo(0.0f))
+ assertThat(paragraph.getLineRight(0), equalTo(layoutWidth))
+ // Last line should align start
+ assertThat(paragraph.getLineLeft(1), equalTo(0.0f))
+ }
+ }
+
+ @Test
+ fun textDirection_whenLTR_dotIsOnRight() {
+ withDensity(defaultDensity) {
+ val text = "a.."
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val layoutWidth = text.length * fontSizeInPx
+
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ textDirection = TextDirection.Ltr,
+ fontSize = fontSize
+ )
+ paragraph.layout(ParagraphConstraints(width = layoutWidth))
+ // The position of the last character in display order.
+ val position = PxPosition(("a.".length * fontSizeInPx + 1).px, (fontSizeInPx / 2).px)
+ val charIndex = paragraph.getOffsetForPosition(position)
+ assertThat(charIndex, equalTo(2))
+ }
+ }
+
+ @Test
+ fun textDirection_whenRTL_dotIsOnLeft() {
+ withDensity(defaultDensity) {
+ val text = "a.."
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val layoutWidth = text.length * fontSizeInPx
+
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ textDirection = TextDirection.Rtl,
+ fontSize = fontSize
+ )
+ paragraph.layout(ParagraphConstraints(width = layoutWidth))
+ // The position of the first character in display order.
+ val position = PxPosition((fontSizeInPx / 2 + 1).px, (fontSizeInPx / 2).px)
+ val charIndex = paragraph.getOffsetForPosition(position)
+ assertThat(charIndex, equalTo(2))
+ }
+ }
+
+ @Test
+ fun textDirection_whenDefault_withoutStrongChar_directionIsLTR() {
+ withDensity(defaultDensity) {
+ val text = "..."
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val layoutWidth = text.length * fontSizeInPx
+
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontSize = fontSize
+ )
+ paragraph.layout(ParagraphConstraints(width = layoutWidth))
+ for (i in 0..text.length) {
+ // The position of the i-th character in display order.
+ val position = PxPosition((i * fontSizeInPx + 1).px, (fontSizeInPx / 2).px)
+ val charIndex = paragraph.getOffsetForPosition(position)
+ assertThat(charIndex, equalTo(i))
+ }
+ }
+ }
+
+ @Test
+ fun textDirection_whenDefault_withFirstStrongCharLTR_directionIsLTR() {
+ withDensity(defaultDensity) {
+ val text = "a\u05D0."
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val layoutWidth = text.length * fontSizeInPx
+
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontSize = fontSize
+ )
+ paragraph.layout(ParagraphConstraints(width = layoutWidth))
+ for (i in 0 until text.length) {
+ // The position of the i-th character in display order.
+ val position = PxPosition((i * fontSizeInPx + 1).px, (fontSizeInPx / 2).px)
+ val charIndex = paragraph.getOffsetForPosition(position)
+ assertThat(charIndex, equalTo(i))
+ }
+ }
+ }
+
+ @Test
+ fun textDirection_whenDefault_withFirstStrongCharRTL_directionIsRTL() {
+ withDensity(defaultDensity) {
+ val text = "\u05D0a."
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val layoutWidth = text.length * fontSizeInPx
+
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontSize = fontSize
+ )
+ paragraph.layout(ParagraphConstraints(width = layoutWidth))
+ // The first character in display order should be '.'
+ val position = PxPosition((fontSizeInPx / 2 + 1).px, (fontSizeInPx / 2).px)
+ val index = paragraph.getOffsetForPosition(position)
+ assertThat(index, equalTo(2))
+ }
+ }
+
+ @Test
+ fun lineHeight_returnsSameAsGiven() {
+ withDensity(defaultDensity) {
+ val text = "abcdefgh"
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+ // Make the layout 4 lines
+ val layoutWidth = text.length * fontSizeInPx / 4
+ val lineHeight = 1.5f
+
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontSize = fontSize,
+ lineHeight = lineHeight
+ )
+ paragraph.layout(ParagraphConstraints(width = layoutWidth))
+
+ assertThat(paragraph.lineCount, equalTo(4))
+ // TODO(Migration/haoyuchang): Due to bug b/120530738, the height of the first line is
+ // wrong in the framework. Will fix it when the lineHeight in TextSpan is implemented.
+ for (i in 1 until paragraph.lineCount - 1) {
+ val actualHeight = paragraph.getLineHeight(i)
+ // In the sample_font.ttf, the height of the line should be
+ // fontSize + 0.2f * fontSize(line gap)
+ assertThat(
+ "line number $i",
+ actualHeight,
+ equalTo(1.2f * fontSizeInPx * lineHeight)
+ )
+ }
+ }
+ }
+
+ @Test
+ fun lineHeight_hasNoEffectOnLastLine() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val layoutWidth = (text.length - 1) * fontSizeInPx
+ val lineHeight = 1.5f
+
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontSize = fontSize,
+ lineHeight = lineHeight
+ )
+ paragraph.layout(ParagraphConstraints(width = layoutWidth))
+
+ val lastLine = paragraph.lineCount - 1
+ // In the sample_font.ttf, the height of the line should be
+ // fontSize + 0.2 * fontSize(line gap)
+ assertThat(paragraph.getLineHeight(lastLine), equalTo(1.2f * fontSizeInPx))
+ }
+ }
+
+ @Test
+ fun textIndent_onSingleLine() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val indent = 20.0f
+
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ textIndent = TextIndent(firstLine = indent.px),
+ fontSize = fontSize,
+ fontFamily = fontFamilyMeasureFont
+ )
+ paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
+
+ // This position should point to the first character 'a' if indent is applied.
+ // Otherwise this position will point to the second character 'b'.
+ val position = PxPosition((indent + 1).px, (fontSizeInPx / 2).px)
+ // The offset corresponding to the position should be the first char 'a'.
+ assertThat(paragraph.getOffsetForPosition(position), equalTo(0))
+ }
+ }
+
+ @Test
+ fun textIndent_onFirstLine() {
+ withDensity(defaultDensity) {
+ val text = "abcdef"
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val indent = 20.0f
+ val paragraphWidth = "abcd".length * fontSizeInPx
+
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ textIndent = TextIndent(firstLine = indent.px),
+ fontSize = fontSize,
+ fontFamily = fontFamilyMeasureFont
+ )
+ paragraph.layout(ParagraphConstraints(width = paragraphWidth))
+
+ assertThat(paragraph.lineCount, equalTo(2))
+ // This position should point to the first character of the first line if indent is
+ // applied. Otherwise this position will point to the second character of the second line.
+ val position = PxPosition((indent + 1).px, (fontSizeInPx / 2).px)
+ // The offset corresponding to the position should be the first char 'a'.
+ assertThat(paragraph.getOffsetForPosition(position), equalTo(0))
+ }
+ }
+
+ @Test
+ fun textIndent_onRestLine() {
+ withDensity(defaultDensity) {
+ val text = "abcde"
+ val fontSize = 20.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val indent = 20.0f
+ val paragraphWidth = "abc".length * fontSizeInPx
+
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ textIndent = TextIndent(
+ firstLine = 0.px,
+ restLine = indent.px
+ ),
+ fontSize = fontSize,
+ fontFamily = fontFamilyMeasureFont
+ )
+ paragraph.layout(ParagraphConstraints(width = paragraphWidth))
+
+ // This position should point to the first character of the second line if indent is
+ // applied. Otherwise this position will point to the second character of the second line.
+ val position = PxPosition((indent + 1).px, (fontSizeInPx / 2 + fontSizeInPx).px)
+ // The offset corresponding to the position should be the 'd' in the second line.
+ assertThat(
+ paragraph.getOffsetForPosition(position),
+ equalTo("abcd".length - 1)
+ )
+ }
+ }
+
+ private fun simpleMultiParagraph(
+ text: String = "",
+ textIndent: TextIndent? = null,
+ textAlign: TextAlign? = null,
+ textDirection: TextDirection? = null,
+ fontSize: Sp? = null,
+ maxLines: Int? = null,
+ lineHeight: Float? = null,
+ textStyles: List<AnnotatedString.Item<TextStyle>> = listOf(),
+ paragraphStyles: List<AnnotatedString.Item<ParagraphStyle>> = listOf(),
+ fontFamily: FontFamily = fontFamilyMeasureFont,
+ locale: Locale? = null,
+ textStyle: TextStyle? = null,
+ density: Density? = null
+ ): MultiParagraph {
+ return MultiParagraph(
+ annotatedString = AnnotatedString(
+ text = text,
+ textStyles = textStyles,
+ paragraphStyles = paragraphStyles
+ ),
+ textStyle = TextStyle(
+ fontFamily = fontFamily,
+ fontSize = fontSize,
+ locale = locale
+ ).merge(textStyle),
+ paragraphStyle = ParagraphStyle(
+ textIndent = textIndent,
+ textAlign = textAlign,
+ textDirection = textDirection,
+ lineHeight = lineHeight
+ ),
+ maxLines = maxLines,
+ density = density ?: defaultDensity,
+ resourceLoader = TestFontResourceLoader(context)
+ )
+ }
+}
diff --git a/ui/ui-text/src/androidTest/java/androidx/ui/text/ParagraphIntegrationTest.kt b/ui/ui-text/src/androidTest/java/androidx/ui/text/ParagraphIntegrationTest.kt
index b937a37..9718437 100644
--- a/ui/ui-text/src/androidTest/java/androidx/ui/text/ParagraphIntegrationTest.kt
+++ b/ui/ui-text/src/androidTest/java/androidx/ui/text/ParagraphIntegrationTest.kt
@@ -20,6 +20,7 @@
import androidx.test.filters.Suppress
import androidx.test.platform.app.InstrumentationRegistry
import androidx.ui.core.Density
+import androidx.ui.core.PxPosition
import androidx.ui.core.Sp
import androidx.ui.core.px
import androidx.ui.core.sp
@@ -61,6 +62,8 @@
private val resourceLoader = TestFontResourceLoader(context)
+ private val cursorWidth = 4f
+
@Test
fun empty_string() {
withDensity(defaultDensity) {
@@ -208,7 +211,7 @@
}
@Test
- fun getPositionForOffset_ltr() {
+ fun getOffsetForPosition_ltr() {
withDensity(defaultDensity) {
val text = "abc"
val fontSize = 50.sp
@@ -218,11 +221,11 @@
paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
// test positions that are 1, fontSize+1, 2fontSize+1 which maps to chars 0, 1, 2 ...
for (i in 0..text.length) {
- val offset = Offset(i * fontSizeInPx + 1, fontSizeInPx / 2)
- val position = paragraph.getPositionForOffset(offset)
+ val position = PxPosition((i * fontSizeInPx + 1).px, (fontSizeInPx / 2).px)
+ val offset = paragraph.getOffsetForPosition(position)
assertThat(
- "position at index $i, offset $offset does not match",
- position,
+ "offset at index $i, position $position does not match",
+ offset,
equalTo(i)
)
}
@@ -230,7 +233,7 @@
}
@Test
- fun getPositionForOffset_rtl() {
+ fun getOffsetForPosition_rtl() {
withDensity(defaultDensity) {
val text = "\u05D0\u05D1\u05D2"
val fontSize = 50.sp
@@ -241,11 +244,11 @@
// test positions that are 1, fontSize+1, 2fontSize+1 which maps to chars .., 2, 1, 0
for (i in 0..text.length) {
- val offset = Offset(i * fontSizeInPx + 1, fontSizeInPx / 2)
- val position = paragraph.getPositionForOffset(offset)
+ val position = PxPosition((i * fontSizeInPx + 1).px, (fontSizeInPx / 2).px)
+ val offset = paragraph.getOffsetForPosition(position)
assertThat(
- "position at index $i, offset $offset does not match",
- position,
+ "offset at index $i, position $position does not match",
+ offset,
equalTo(text.length - i)
)
}
@@ -253,7 +256,7 @@
}
@Test
- fun getPositionForOffset_ltr_multiline() {
+ fun getOffsetForPosition_ltr_multiline() {
withDensity(defaultDensity) {
val firstLine = "abc"
val secondLine = "def"
@@ -267,11 +270,11 @@
// test positions are 1, fontSize+1, 2fontSize+1 and always on the second line
// which maps to chars 3, 4, 5
for (i in 0..secondLine.length) {
- val offset = Offset(i * fontSizeInPx + 1, fontSizeInPx * 1.5f)
- val position = paragraph.getPositionForOffset(offset)
+ val position = PxPosition((i * fontSizeInPx + 1).px, (fontSizeInPx * 1.5f).px)
+ val offset = paragraph.getOffsetForPosition(position)
assertThat(
- "position at index $i, offset $offset, second line does not match",
- position,
+ "offset at index $i, position $position, second line does not match",
+ offset,
equalTo(i + firstLine.length)
)
}
@@ -279,7 +282,7 @@
}
@Test
- fun getPositionForOffset_rtl_multiline() {
+ fun getOffsetForPosition_rtl_multiline() {
withDensity(defaultDensity) {
val firstLine = "\u05D0\u05D1\u05D2"
val secondLine = "\u05D3\u05D4\u05D5"
@@ -293,11 +296,11 @@
// test positions are 1, fontSize+1, 2fontSize+1 and always on the second line
// which maps to chars 5, 4, 3
for (i in 0..secondLine.length) {
- val offset = Offset(i * fontSizeInPx + 1, fontSizeInPx * 1.5f)
- val position = paragraph.getPositionForOffset(offset)
+ val position = PxPosition((i * fontSizeInPx + 1).px, (fontSizeInPx * 1.5f).px)
+ val offset = paragraph.getOffsetForPosition(position)
assertThat(
- "position at index $i, offset $offset, second line does not match",
- position,
+ "offset at index $i, position $position, second line does not match",
+ offset,
equalTo(text.length - i)
)
}
@@ -305,7 +308,7 @@
}
@Test
- fun getPositionForOffset_ltr_width_outOfBounds() {
+ fun getOffsetForPosition_ltr_width_outOfBounds() {
withDensity(defaultDensity) {
val text = "abc"
val fontSize = 50.sp
@@ -315,19 +318,19 @@
paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
// greater than width
- var offset = Offset(fontSizeInPx * text.length * 2, fontSizeInPx / 2)
- var position = paragraph.getPositionForOffset(offset)
- assertThat(position, equalTo(text.length))
+ var position = PxPosition((fontSizeInPx * text.length * 2).px, (fontSizeInPx / 2).px)
+ var offset = paragraph.getOffsetForPosition(position)
+ assertThat(offset, equalTo(text.length))
// negative
- offset = Offset(-1 * fontSizeInPx, fontSizeInPx / 2)
- position = paragraph.getPositionForOffset(offset)
- assertThat(position, equalTo(0))
+ position = PxPosition((-1 * fontSizeInPx).px, (fontSizeInPx / 2).px)
+ offset = paragraph.getOffsetForPosition(position)
+ assertThat(offset, equalTo(0))
}
}
@Test
- fun getPositionForOffset_ltr_height_outOfBounds() {
+ fun getOffsetForPosition_ltr_height_outOfBounds() {
withDensity(defaultDensity) {
val text = "abc"
val fontSize = 50.sp
@@ -337,14 +340,14 @@
paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
// greater than height
- var offset = Offset(fontSizeInPx / 2, fontSizeInPx * text.length * 2)
- var position = paragraph.getPositionForOffset(offset)
- assertThat(position, equalTo(0))
+ var position = PxPosition((fontSizeInPx / 2).px, (fontSizeInPx * text.length * 2).px)
+ var offset = paragraph.getOffsetForPosition(position)
+ assertThat(offset, equalTo(0))
// negative
- offset = Offset(fontSizeInPx / 2, -1 * fontSizeInPx)
- position = paragraph.getPositionForOffset(offset)
- assertThat(position, equalTo(0))
+ position = PxPosition((fontSizeInPx / 2).px, (-1 * fontSizeInPx).px)
+ offset = paragraph.getOffsetForPosition(position)
+ assertThat(offset, equalTo(0))
}
}
@@ -429,6 +432,566 @@
}
}
+ @Test(expected = java.lang.AssertionError::class)
+ fun getCursorRect_larger_than_length_throw_exception() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(text = text, fontSize = fontSize)
+
+ paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+
+ paragraph.getCursorRect(text.length + 1)
+ }
+ }
+
+ @Test(expected = java.lang.AssertionError::class)
+ fun getCursorRect_negative_throw_exception() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(text = text, fontSize = fontSize)
+
+ paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+
+ paragraph.getCursorRect(-1)
+ }
+ }
+
+ @Test
+ fun getCursorRect_ltr_singleLine() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(text = text, fontSize = fontSize)
+
+ paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+
+ for (i in 0 until text.length) {
+ val cursorRect = paragraph.getCursorRect(i)
+ val cursorXOffset = i * fontSizeInPx
+ assertThat(
+ cursorRect,
+ equalTo(Rect(
+ left = cursorXOffset - cursorWidth / 2,
+ top = 0f,
+ right = cursorXOffset + cursorWidth / 2,
+ bottom = fontSizeInPx
+ ))
+ )
+ }
+ }
+ }
+
+ @Test
+ fun getCursorRect_ltr_multiLines() {
+ withDensity(defaultDensity) {
+ val text = "abcdef"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(text = text, fontSize = fontSize)
+ val charsPerLine = 3
+
+ paragraph.layout(ParagraphConstraints(width = charsPerLine * fontSizeInPx))
+
+ for (i in 0 until charsPerLine) {
+ val cursorXOffset = i * fontSizeInPx
+ assertThat(
+ paragraph.getCursorRect(i),
+ equalTo(Rect(
+ left = cursorXOffset - cursorWidth / 2,
+ top = 0f,
+ right = cursorXOffset + cursorWidth / 2,
+ bottom = fontSizeInPx
+ ))
+ )
+ }
+
+ for (i in charsPerLine until text.length) {
+ val cursorXOffset = (i % charsPerLine) * fontSizeInPx
+ assertThat(
+ paragraph.getCursorRect(i),
+ equalTo(Rect(
+ left = cursorXOffset - cursorWidth / 2,
+ top = fontSizeInPx,
+ right = cursorXOffset + cursorWidth / 2,
+ bottom = fontSizeInPx * 2.2f
+ ))
+ )
+ }
+ }
+ }
+
+ @Test
+ fun getCursorRect_ltr_newLine() {
+ withDensity(defaultDensity) {
+ val text = "abc\ndef"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(text = text, fontSize = fontSize)
+
+ paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
+
+ // Cursor before '\n'
+ assertThat(
+ paragraph.getCursorRect(3),
+ equalTo(Rect(
+ left = 3 * fontSizeInPx - cursorWidth / 2,
+ top = 0f,
+ right = 3 * fontSizeInPx + cursorWidth / 2,
+ bottom = fontSizeInPx
+ ))
+ )
+
+ // Cursor after '\n'
+ assertThat(
+ paragraph.getCursorRect(4),
+ equalTo(Rect(
+ left = -cursorWidth / 2,
+ top = fontSizeInPx,
+ right = cursorWidth / 2,
+ bottom = fontSizeInPx * 2.2f
+ ))
+ )
+ }
+ }
+
+ @Test
+ fun getCursorRect_ltr_newLine_last_char() {
+ withDensity(defaultDensity) {
+ val text = "abc\n"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(text = text, fontSize = fontSize)
+
+ paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
+
+ // Cursor before '\n'
+ assertThat(
+ paragraph.getCursorRect(3),
+ equalTo(Rect(
+ left = 3 * fontSizeInPx - cursorWidth / 2,
+ top = 0f,
+ right = 3 * fontSizeInPx + cursorWidth / 2,
+ bottom = fontSizeInPx
+ ))
+ )
+
+ // Cursor after '\n'
+ assertThat(
+ paragraph.getCursorRect(4),
+ equalTo(Rect(
+ left = -cursorWidth / 2,
+ top = fontSizeInPx,
+ right = cursorWidth / 2,
+ bottom = fontSizeInPx * 2.2f
+ ))
+ )
+ }
+ }
+
+ @Test
+ fun getCursorRect_rtl_singleLine() {
+ withDensity(defaultDensity) {
+ val text = "\u05D0\u05D1\u05D2"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(text = text, fontSize = fontSize)
+
+ paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+
+ for (i in 0 until text.length) {
+ val cursorXOffset = (text.length - i) * fontSizeInPx
+ assertThat(
+ paragraph.getCursorRect(i),
+ equalTo(Rect(
+ left = cursorXOffset - cursorWidth / 2,
+ top = 0f,
+ right = cursorXOffset + cursorWidth / 2,
+ bottom = fontSizeInPx
+ ))
+ )
+ }
+ }
+ }
+
+ @Test
+ fun getCursorRect_rtl_multiLines() {
+ withDensity(defaultDensity) {
+ val text = "\u05D0\u05D1\u05D2\u05D0\u05D1\u05D2"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(text = text, fontSize = fontSize)
+ val charsPerLine = 3
+
+ paragraph.layout(ParagraphConstraints(width = charsPerLine * fontSizeInPx))
+
+ for (i in 0 until charsPerLine) {
+ val cursorXOffset = (charsPerLine - i) * fontSizeInPx
+ assertThat(
+ paragraph.getCursorRect(i),
+ equalTo(Rect(
+ left = cursorXOffset - cursorWidth / 2,
+ top = 0f,
+ right = cursorXOffset + cursorWidth / 2,
+ bottom = fontSizeInPx
+ ))
+ )
+ }
+
+ for (i in charsPerLine until text.length) {
+ val cursorXOffset = (charsPerLine - i % charsPerLine) * fontSizeInPx
+ assertThat(
+ paragraph.getCursorRect(i),
+ equalTo(Rect(
+ left = cursorXOffset - cursorWidth / 2,
+ top = fontSizeInPx,
+ right = cursorXOffset + cursorWidth / 2,
+ bottom = fontSizeInPx * 2.2f
+ ))
+ )
+ }
+ }
+ }
+
+ @Test
+ fun getCursorRect_rtl_newLine() {
+ withDensity(defaultDensity) {
+ val text = "\u05D0\u05D1\u05D2\n\u05D0\u05D1\u05D2"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(text = text, fontSize = fontSize)
+
+ paragraph.layout(ParagraphConstraints(width = 3 * fontSizeInPx))
+
+ // Cursor before '\n'
+ assertThat(
+ paragraph.getCursorRect(3),
+ equalTo(Rect(
+ left = 0 - cursorWidth / 2,
+ top = 0f,
+ right = 0 + cursorWidth / 2,
+ bottom = fontSizeInPx
+ ))
+ )
+
+ // Cursor after '\n'
+ assertThat(
+ paragraph.getCursorRect(4),
+ equalTo(Rect(
+ left = 3 * fontSizeInPx - cursorWidth / 2,
+ top = fontSizeInPx,
+ right = 3 * fontSizeInPx + cursorWidth / 2,
+ bottom = fontSizeInPx * 2.2f
+ ))
+ )
+ }
+ }
+
+ @Test
+ fun getCursorRect_rtl_newLine_last_char() {
+ withDensity(defaultDensity) {
+ val text = "\u05D0\u05D1\u05D2\n"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(text = text, fontSize = fontSize)
+
+ paragraph.layout(ParagraphConstraints(width = 3 * fontSizeInPx))
+
+ // Cursor before '\n'
+ assertThat(
+ paragraph.getCursorRect(3),
+ equalTo(Rect(
+ left = 0 - cursorWidth / 2,
+ top = 0f,
+ right = 0 + cursorWidth / 2,
+ bottom = fontSizeInPx
+ ))
+ )
+
+ // Cursor after '\n'
+ assertThat(
+ paragraph.getCursorRect(4),
+ equalTo(Rect(
+ left = -cursorWidth / 2,
+ top = fontSizeInPx,
+ right = +cursorWidth / 2,
+ bottom = fontSizeInPx * 2.2f
+ ))
+ )
+ }
+ }
+
+ @Test
+ fun getPrimaryHorizontal_ltr_singleLine_textDirectionDefault() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(text = text, fontSize = fontSize)
+
+ paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+
+ for (i in 0..text.length) {
+ assertThat(
+ paragraph.getPrimaryHorizontal(i),
+ equalTo(fontSizeInPx * i)
+ )
+ }
+ }
+ }
+
+ @Test
+ fun getPrimaryHorizontal_rtl_singleLine_textDirectionDefault() {
+ withDensity(defaultDensity) {
+ val text = "\u05D0\u05D1\u05D2"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(text = text, fontSize = fontSize)
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ for (i in 0..text.length) {
+ assertThat(
+ paragraph.getPrimaryHorizontal(i),
+ equalTo(width - fontSizeInPx * i)
+ )
+ }
+ }
+ }
+
+ @Test
+ fun getPrimaryHorizontal_bidi_singleLine_textDirectionDefault() {
+ withDensity(defaultDensity) {
+ val ltrText = "abc"
+ val rtlText = "\u05D0\u05D1\u05D2"
+ val text = ltrText + rtlText
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(text = text, fontSize = fontSize)
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ for (i in 0..ltrText.length) {
+ assertThat(
+ paragraph.getPrimaryHorizontal(i),
+ equalTo(fontSizeInPx * i)
+ )
+ }
+
+ for (i in 1 until rtlText.length) {
+ assertThat(
+ paragraph.getPrimaryHorizontal(i + ltrText.length),
+ equalTo(width - fontSizeInPx * i)
+ )
+ }
+
+ assertThat(
+ paragraph.getPrimaryHorizontal(text.length),
+ equalTo(width)
+ )
+ }
+ }
+
+ @Test
+ fun getPrimaryHorizontal_ltr_singleLine_textDirectionRtl() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(
+ text = text,
+ fontSize = fontSize,
+ textDirection = TextDirection.Rtl
+ )
+ val width = text.length * fontSizeInPx
+ paragraph.layout(ParagraphConstraints(width))
+
+ assertThat(paragraph.getPrimaryHorizontal(0), equalTo(width))
+
+ for (i in 1 until text.length) {
+ assertThat(
+ paragraph.getPrimaryHorizontal(i),
+ equalTo(fontSizeInPx * i)
+ )
+ }
+
+ assertThat(paragraph.getPrimaryHorizontal(text.length), equalTo(0f))
+ }
+ }
+
+ @Test
+ fun getPrimaryHorizontal_rtl_singleLine_textDirectionLtr() {
+ withDensity(defaultDensity) {
+ val text = "\u05D0\u05D1\u05D2"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(
+ text = text,
+ fontSize = fontSize,
+ textDirection = TextDirection.Ltr
+ )
+ val width = text.length * fontSizeInPx
+ paragraph.layout(ParagraphConstraints(width))
+
+ assertThat(paragraph.getPrimaryHorizontal(0), equalTo(0f))
+
+ for (i in 1 until text.length) {
+ assertThat(
+ paragraph.getPrimaryHorizontal(i),
+ equalTo(width - fontSizeInPx * i)
+ )
+ }
+
+ assertThat(paragraph.getPrimaryHorizontal(text.length), equalTo(width))
+ }
+ }
+
+ @Test
+ fun getPrimaryHorizontal_bidi_singleLine_textDirectionLtr() {
+ withDensity(defaultDensity) {
+ val ltrText = "abc"
+ val rtlText = "\u05D0\u05D1\u05D2"
+ val text = ltrText + rtlText
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(
+ text = text,
+ fontSize = fontSize,
+ textDirection = TextDirection.Ltr
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ for (i in 0..ltrText.length) {
+ assertThat(
+ paragraph.getPrimaryHorizontal(i),
+ equalTo(fontSizeInPx * i)
+ )
+ }
+
+ for (i in 1 until rtlText.length) {
+ assertThat(
+ paragraph.getPrimaryHorizontal(i + ltrText.length),
+ equalTo(width - fontSizeInPx * i)
+ )
+ }
+
+ assertThat(
+ paragraph.getPrimaryHorizontal(text.length),
+ equalTo(width)
+ )
+ }
+ }
+
+ @Test
+ fun getPrimaryHorizontal_bidi_singleLine_textDirectionRtl() {
+ withDensity(defaultDensity) {
+ val ltrText = "abc"
+ val rtlText = "\u05D0\u05D1\u05D2"
+ val text = ltrText + rtlText
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(
+ text = text,
+ fontSize = fontSize,
+ textDirection = TextDirection.Rtl
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ assertThat(paragraph.getPrimaryHorizontal(0), equalTo(width))
+ for (i in 1 until ltrText.length) {
+ assertThat(
+ paragraph.getPrimaryHorizontal(i),
+ equalTo(rtlText.length * fontSizeInPx + i * fontSizeInPx)
+ )
+ }
+
+ for (i in 0..rtlText.length) {
+ assertThat(
+ paragraph.getPrimaryHorizontal(i + ltrText.length),
+ equalTo(rtlText.length * fontSizeInPx - i * fontSizeInPx)
+ )
+ }
+ }
+ }
+
+ @Test
+ fun getPrimaryHorizontal_ltr_newLine_textDirectionDefault() {
+ withDensity(defaultDensity) {
+ val text = "abc\n"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(text = text, fontSize = fontSize)
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ assertThat(paragraph.getPrimaryHorizontal(text.length), equalTo(0f))
+ }
+ }
+
+ @Test
+ fun getPrimaryHorizontal_rtl_newLine_textDirectionDefault() {
+ withDensity(defaultDensity) {
+ val text = "\u05D0\u05D1\u05D2\n"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(text = text, fontSize = fontSize)
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ assertThat(paragraph.getPrimaryHorizontal(text.length), equalTo(0f))
+ }
+ }
+
+ @Test
+ fun getPrimaryHorizontal_ltr_newLine_textDirectionRtl() {
+ withDensity(defaultDensity) {
+ val text = "abc\n"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(
+ text = text,
+ fontSize = fontSize,
+ textDirection = TextDirection.Rtl
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ assertThat(paragraph.getPrimaryHorizontal(text.length), equalTo(width))
+ }
+ }
+
+ @Test
+ fun getPrimaryHorizontal_rtl_newLine_textDirectionLtr() {
+ withDensity(defaultDensity) {
+ val text = "\u05D0\u05D1\u05D2\n"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(
+ text = text,
+ fontSize = fontSize,
+ textDirection = TextDirection.Ltr
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ assertThat(paragraph.getPrimaryHorizontal(text.length), equalTo(0f))
+ }
+ }
+
@Test
fun locale_withCJK_shouldNotDrawSame() {
withDensity(defaultDensity) {
@@ -835,9 +1398,9 @@
fontSize = fontSize
)
paragraph.layout(ParagraphConstraints(width = layoutWidth))
- // The offset of the last character in display order.
- val offset = Offset("a.".length * fontSizeInPx + 1, fontSizeInPx / 2)
- val charIndex = paragraph.getPositionForOffset(offset = offset)
+ // The position of the last character in display order.
+ val position = PxPosition(("a.".length * fontSizeInPx + 1).px, (fontSizeInPx / 2).px)
+ val charIndex = paragraph.getOffsetForPosition(position)
assertThat(charIndex, equalTo(2))
}
}
@@ -856,9 +1419,9 @@
fontSize = fontSize
)
paragraph.layout(ParagraphConstraints(width = layoutWidth))
- // The offset of the first character in display order.
- val offset = Offset(fontSizeInPx / 2 + 1, fontSizeInPx / 2)
- val charIndex = paragraph.getPositionForOffset(offset = offset)
+ // The position of the first character in display order.
+ val position = PxPosition((fontSizeInPx / 2 + 1).px, (fontSizeInPx / 2).px)
+ val charIndex = paragraph.getOffsetForPosition(position)
assertThat(charIndex, equalTo(2))
}
}
@@ -877,9 +1440,9 @@
)
paragraph.layout(ParagraphConstraints(width = layoutWidth))
for (i in 0..text.length) {
- // The offset of the i-th character in display order.
- val offset = Offset(i * fontSizeInPx + 1, fontSizeInPx / 2)
- val charIndex = paragraph.getPositionForOffset(offset = offset)
+ // The position of the i-th character in display order.
+ val position = PxPosition((i * fontSizeInPx + 1).px, (fontSizeInPx / 2).px)
+ val charIndex = paragraph.getOffsetForPosition(position)
assertThat(charIndex, equalTo(i))
}
}
@@ -899,9 +1462,9 @@
)
paragraph.layout(ParagraphConstraints(width = layoutWidth))
for (i in 0 until text.length) {
- // The offset of the i-th character in display order.
- val offset = Offset(i * fontSizeInPx + 1, fontSizeInPx / 2)
- val charIndex = paragraph.getPositionForOffset(offset = offset)
+ // The position of the i-th character in display order.
+ val position = PxPosition((i * fontSizeInPx + 1).px, (fontSizeInPx / 2).px)
+ val charIndex = paragraph.getOffsetForPosition(position)
assertThat(charIndex, equalTo(i))
}
}
@@ -921,8 +1484,8 @@
)
paragraph.layout(ParagraphConstraints(width = layoutWidth))
// The first character in display order should be '.'
- val offset = Offset(fontSizeInPx / 2 + 1, fontSizeInPx / 2)
- val index = paragraph.getPositionForOffset(offset = offset)
+ val position = PxPosition((fontSizeInPx / 2 + 1).px, (fontSizeInPx / 2).px)
+ val index = paragraph.getOffsetForPosition(position)
assertThat(index, equalTo(2))
}
}
@@ -1309,11 +1872,11 @@
)
paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
- // This offset should point to the first character 'a' if indent is applied.
- // Otherwise this offset will point to the second character 'b'.
- val offset = Offset(indent + 1, fontSizeInPx / 2)
- // The position corresponding to the offset should be the first char 'a'.
- assertThat(paragraph.getPositionForOffset(offset), equalTo(0))
+ // This position should point to the first character 'a' if indent is applied.
+ // Otherwise this position will point to the second character 'b'.
+ val position = PxPosition((indent + 1).px, (fontSizeInPx / 2).px)
+ // The offset corresponding to the position should be the first char 'a'.
+ assertThat(paragraph.getOffsetForPosition(position), equalTo(0))
}
}
@@ -1335,11 +1898,11 @@
paragraph.layout(ParagraphConstraints(width = paragraphWidth))
assertThat(paragraph.lineCount, equalTo(2))
- // This offset should point to the first character of the first line if indent is
- // applied. Otherwise this offset will point to the second character of the second line.
- val offset = Offset(indent + 1, fontSizeInPx / 2)
- // The position corresponding to the offset should be the first char 'a'.
- assertThat(paragraph.getPositionForOffset(offset), equalTo(0))
+ // This position should point to the first character of the first line if indent is
+ // applied. Otherwise this position will point to the second character of the second line.
+ val position = PxPosition((indent + 1).px, (fontSizeInPx / 2).px)
+ // The offset corresponding to the position should be the first char 'a'.
+ assertThat(paragraph.getOffsetForPosition(position), equalTo(0))
}
}
@@ -1363,12 +1926,12 @@
)
paragraph.layout(ParagraphConstraints(width = paragraphWidth))
- // This offset should point to the first character of the second line if indent is
- // applied. Otherwise this offset will point to the second character of the second line.
- val offset = Offset(indent + 1, fontSizeInPx / 2 + fontSizeInPx)
- // The position corresponding to the offset should be the 'd' in the second line.
+ // This position should point to the first character of the second line if indent is
+ // applied. Otherwise this position will point to the second character of the second line.
+ val position = PxPosition((indent + 1).px, (fontSizeInPx / 2 + fontSizeInPx).px)
+ // The offset corresponding to the position should be the 'd' in the second line.
assertThat(
- paragraph.getPositionForOffset(offset),
+ paragraph.getOffsetForPosition(position),
equalTo("abcd".length - 1)
)
}
@@ -1933,14 +2496,14 @@
fun paint_throws_exception_if_layout_is_not_called() {
val paragraph = simpleParagraph()
- paragraph.paint(mock(), 0.0f, 0.0f)
+ paragraph.paint(mock())
}
@Test(expected = IllegalStateException::class)
- fun getPositionForOffset_throws_exception_if_layout_is_not_called() {
+ fun getOffsetForPosition_throws_exception_if_layout_is_not_called() {
val paragraph = simpleParagraph()
- paragraph.getPositionForOffset(Offset(0.0f, 0.0f))
+ paragraph.getOffsetForPosition(PxPosition.Origin)
}
@Test(expected = AssertionError::class)
diff --git a/ui/ui-text/src/androidTest/java/androidx/ui/text/TextPainterIntegrationTest.kt b/ui/ui-text/src/androidTest/java/androidx/ui/text/TextPainterIntegrationTest.kt
index 3e43115..3b17aae 100644
--- a/ui/ui-text/src/androidTest/java/androidx/ui/text/TextPainterIntegrationTest.kt
+++ b/ui/ui-text/src/androidTest/java/androidx/ui/text/TextPainterIntegrationTest.kt
@@ -21,10 +21,11 @@
import androidx.test.platform.app.InstrumentationRegistry
import androidx.ui.core.Constraints
import androidx.ui.core.Density
+import androidx.ui.core.PxPosition
import androidx.ui.core.ipx
+import androidx.ui.core.px
import androidx.ui.core.sp
import androidx.ui.core.withDensity
-import androidx.ui.engine.geometry.Offset
import androidx.ui.engine.geometry.Rect
import androidx.ui.engine.geometry.Size
import androidx.ui.text.FontTestData.Companion.BASIC_MEASURE_FONT
@@ -289,7 +290,7 @@
textPainter.layout(Constraints(0.ipx, 20.ipx))
- assertThat(textPainter.paragraph).isNotNull()
+ assertThat(textPainter.multiParagraph).isNotNull()
}
@Test
@@ -313,7 +314,7 @@
)
textPainter.layout(Constraints())
- val selection = textPainter.getPositionForOffset(Offset(dx = 0f, dy = 0f))
+ val selection = textPainter.getOffsetForPosition(PxPosition.Origin)
assertThat(selection).isEqualTo(0)
}
@@ -342,8 +343,8 @@
)
textPainter.layout(Constraints())
- val selection = textPainter.getPositionForOffset(
- offset = Offset(dx = fontSize.toPx().value * characterIndex + 1f, dy = 0f)
+ val selection = textPainter.getOffsetForPosition(
+ position = PxPosition((fontSize.toPx().value * characterIndex + 1).px, 0.px)
)
assertThat(selection).isEqualTo(characterIndex)
@@ -458,10 +459,10 @@
val defaultSelectionColor = Color(0x6633B5E5)
expectedPaint.color = defaultSelectionColor
- val firstLineLeft = textPainter.paragraph?.getLineLeft(0)
- val secondLineLeft = textPainter.paragraph?.getLineLeft(1)
- val firstLineRight = textPainter.paragraph?.getLineRight(0)
- val secondLineRight = textPainter.paragraph?.getLineRight(1)
+ val firstLineLeft = textPainter.multiParagraph?.getLineLeft(0)
+ val secondLineLeft = textPainter.multiParagraph?.getLineLeft(1)
+ val firstLineRight = textPainter.multiParagraph?.getLineRight(0)
+ val secondLineRight = textPainter.multiParagraph?.getLineRight(1)
expectedCanvas.drawRect(
Rect(firstLineLeft!!, 0f, firstLineRight!!, fontSizeInPx),
expectedPaint
@@ -471,7 +472,7 @@
secondLineLeft!!,
fontSizeInPx,
secondLineRight!!,
- textPainter.paragraph!!.height
+ textPainter.multiParagraph!!.height
),
expectedPaint
)
@@ -489,8 +490,7 @@
start = 0,
end = text.length,
color = defaultSelectionColor,
- canvas = actualCanvas,
- offset = Offset.zero
+ canvas = actualCanvas
)
// Assert.
@@ -558,8 +558,7 @@
start = selectionStart,
end = selectionEnd,
color = defaultSelectionColor,
- canvas = actualCanvas,
- offset = Offset.zero
+ canvas = actualCanvas
)
// Assert
@@ -642,8 +641,7 @@
start = selectionLTRStart,
end = textLTR.length + selectionRTLEnd,
color = defaultSelectionColor,
- canvas = actualCanvas,
- offset = Offset.zero
+ canvas = actualCanvas
)
// Assert
@@ -711,8 +709,7 @@
start = selectionStart,
end = selectionEnd,
color = selectionColor,
- canvas = actualCanvas,
- offset = Offset.zero
+ canvas = actualCanvas
)
// Assert
diff --git a/ui/ui-text/src/androidTest/java/androidx/ui/text/TextTestExtensions.kt b/ui/ui-text/src/androidTest/java/androidx/ui/text/TextTestExtensions.kt
index 8d2746b..444b98d 100644
--- a/ui/ui-text/src/androidTest/java/androidx/ui/text/TextTestExtensions.kt
+++ b/ui/ui-text/src/androidTest/java/androidx/ui/text/TextTestExtensions.kt
@@ -30,7 +30,7 @@
ceil(this.height).toInt(),
Bitmap.Config.ARGB_8888
)
- this.paint(androidx.ui.painting.Canvas(Canvas(bitmap)), 0.0f, 0.0f)
+ this.paint(androidx.ui.painting.Canvas(Canvas(bitmap)))
return bitmap
}
diff --git a/ui/ui-text/src/androidTest/java/androidx/ui/text/platform/TextTestExtensions.kt b/ui/ui-text/src/androidTest/java/androidx/ui/text/platform/TextTestExtensions.kt
index 02aa82c..719f605 100644
--- a/ui/ui-text/src/androidTest/java/androidx/ui/text/platform/TextTestExtensions.kt
+++ b/ui/ui-text/src/androidTest/java/androidx/ui/text/platform/TextTestExtensions.kt
@@ -30,7 +30,7 @@
ceil(this.height).toInt(),
Bitmap.Config.ARGB_8888
)
- this.paint(androidx.ui.painting.Canvas(Canvas(bitmap)), 0.0f, 0.0f)
+ this.paint(androidx.ui.painting.Canvas(Canvas(bitmap)))
return bitmap
}
diff --git a/ui/ui-text/src/main/java/androidx/ui/input/EditProcessor.kt b/ui/ui-text/src/main/java/androidx/ui/input/EditProcessor.kt
index 0d8d5c0..d9367c9 100644
--- a/ui/ui-text/src/main/java/androidx/ui/input/EditProcessor.kt
+++ b/ui/ui-text/src/main/java/androidx/ui/input/EditProcessor.kt
@@ -30,26 +30,26 @@
class EditProcessor {
// The previous editor state we passed back to the user of this class.
- private var mPreviousState: EditorState? = null
+ private var mPreviousModel: EditorModel? = null
// The editing buffer used for applying editor commands from IME.
private var mBuffer: EditingBuffer =
EditingBuffer(initialText = "", initialSelection = TextRange(0, 0))
/**
- * Must be called whenever new editor state arrives.
+ * Must be called whenever new editor model arrives.
*
- * This method updates the internal editing buffer with the given editor state.
+ * This method updates the internal editing buffer with the given editor model.
* This method may tell the IME about the selection offset changes or extracted text changes.
*/
- fun onNewState(state: EditorState, textInputService: TextInputService?) {
- if (mPreviousState !== state) {
+ fun onNewState(model: EditorModel, textInputService: TextInputService?) {
+ if (mPreviousModel !== model) {
mBuffer = EditingBuffer(
- initialText = state.text,
- initialSelection = state.selection)
+ initialText = model.text,
+ initialSelection = model.selection)
}
- textInputService?.onStateUpdated(state)
+ textInputService?.onStateUpdated(model)
}
/**
@@ -58,10 +58,10 @@
* This method updates internal editing buffer with the given edit operations and returns the
* latest editor state representation of the editing buffer.
*/
- fun onEditCommands(ops: List<EditOperation>): EditorState {
+ fun onEditCommands(ops: List<EditOperation>): EditorModel {
ops.forEach { it.process(mBuffer) }
- val newState = EditorState(
+ val newState = EditorModel(
text = mBuffer.toString(),
selection = TextRange(mBuffer.selectionStart, mBuffer.selectionEnd),
composition = if (mBuffer.hasComposition()) {
@@ -70,7 +70,7 @@
null
})
- mPreviousState = newState
+ mPreviousModel = newState
return newState
}
}
\ No newline at end of file
diff --git a/ui/ui-text/src/main/java/androidx/ui/input/EditorState.kt b/ui/ui-text/src/main/java/androidx/ui/input/EditorModel.kt
similarity index 98%
rename from ui/ui-text/src/main/java/androidx/ui/input/EditorState.kt
rename to ui/ui-text/src/main/java/androidx/ui/input/EditorModel.kt
index b8e86aa..363e49a 100644
--- a/ui/ui-text/src/main/java/androidx/ui/input/EditorState.kt
+++ b/ui/ui-text/src/main/java/androidx/ui/input/EditorModel.kt
@@ -26,7 +26,7 @@
* state. TextInputService sends the latest editing state to TextInputClient when the platform input
* service sends some input events.
*/
-class EditorState {
+class EditorModel {
/**
* The text
diff --git a/ui/ui-text/src/main/java/androidx/ui/input/TextInputService.kt b/ui/ui-text/src/main/java/androidx/ui/input/TextInputService.kt
index 382e110..c241f70 100644
--- a/ui/ui-text/src/main/java/androidx/ui/input/TextInputService.kt
+++ b/ui/ui-text/src/main/java/androidx/ui/input/TextInputService.kt
@@ -29,7 +29,7 @@
* Start text input session for given client.
*/
fun startInput(
- initState: EditorState,
+ initModel: EditorModel,
keyboardType: KeyboardType,
imeAction: ImeAction,
onEditCommand: (List<EditOperation>) -> Unit,
@@ -49,9 +49,9 @@
fun showSoftwareKeyboard()
/*
- * Notify the new editor state to IME.
+ * Notify the new editor model to IME.
*/
- fun onStateUpdated(state: EditorState)
+ fun onStateUpdated(model: EditorModel)
/**
* Notify the focused rectangle to the system.
diff --git a/ui/ui-text/src/main/java/androidx/ui/text/AnnotatedString.kt b/ui/ui-text/src/main/java/androidx/ui/text/AnnotatedString.kt
index 97fda37..7c4dff1 100644
--- a/ui/ui-text/src/main/java/androidx/ui/text/AnnotatedString.kt
+++ b/ui/ui-text/src/main/java/androidx/ui/text/AnnotatedString.kt
@@ -21,8 +21,24 @@
*/
data class AnnotatedString(
val text: String,
- val textStyles: List<Item<TextStyle>> = listOf()
+ val textStyles: List<Item<TextStyle>> = listOf(),
+ val paragraphStyles: List<Item<ParagraphStyle>> = listOf()
) {
+
+ init {
+ var lastStyleEnd = -1
+ for (paragraphStyle in paragraphStyles) {
+ if (paragraphStyle.start < lastStyleEnd) {
+ throw IllegalArgumentException("ParagraphStyle should not overlap")
+ }
+ if (paragraphStyle.end > text.length) {
+ throw IllegalArgumentException("ParagraphStyle range " +
+ "[${paragraphStyle.start}, ${paragraphStyle.end}) is out of boundary")
+ }
+ lastStyleEnd = paragraphStyle.end
+ }
+ }
+
/**
* The information attached on the text such as a TextStyle.
*
@@ -31,5 +47,11 @@
* @param end The end of the range where [style] takes effect. It's exclusive.
*/
// TODO(haoyuchang): Check some other naming options.
- data class Item<T>(val style: T, val start: Int, val end: Int)
+ data class Item<T>(val style: T, val start: Int, val end: Int) {
+ init {
+ if (start > end) {
+ throw IllegalArgumentException("Reversed range is not supported")
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/ui/ui-text/src/main/java/androidx/ui/text/Locale.kt b/ui/ui-text/src/main/java/androidx/ui/text/Locale.kt
index 826956c..2c75c98 100644
--- a/ui/ui-text/src/main/java/androidx/ui/text/Locale.kt
+++ b/ui/ui-text/src/main/java/androidx/ui/text/Locale.kt
@@ -28,27 +28,21 @@
* both have the [languageCode] `he`, because `iw` is a deprecated language
* subtag that was replaced by the subtag `he`.
*
- * See also:
- *
- * * [Window.locale], which specifies the system's currently selected
- * [Locale].
- *
* The default constructor creates a new Locale object. The first argument is the
* primary language subtag, the second is the region subtag.
*
* For example:
*
- * ```dart
- * const Locale swissFrench = const Locale('fr', 'CH');
- * const Locale canadianFrench = const Locale('fr', 'CA');
- * ```
+ * val swissFrench = Locale('fr', 'CH');
+ * val canadianFrench = Locale('fr', 'CA');
+ *
*
* The primary language subtag must not be null. The region subtag is
* optional.
*
* The values are _case sensitive_, and should match the case of the relevant
- * subtags in the [IANA Language Subtag
- * Registry](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry).
+ * subtags in the [IANA Language Subtag Registry]
+ * (https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry).
* Typically this means the primary language subtag should be lowercase and
* the region subtag should be uppercase.
*/
@@ -210,32 +204,4 @@
}
}
}
-
- // Made this a data class, so the following are probably no longer required.
- // Leaving them commented out though.
-// override fun equals(other: Any?): Boolean {
-// if (this === other) {
-// return true
-// }
-// if (other !is Locale) {
-// return false
-// }
-// return languageCode == other.languageCode && countryCode == other.countryCode
-// }
-//
-// override fun hashCode(): Int {
-// var result = 373
-// result = 37 * result + languageCode.hashCode()
-// if (_countryCode != null) {
-// result = 37 * result + countryCode!!.hashCode()
-// }
-// return result
-// }
-//
-// override fun toString(): String {
-// if (_countryCode == null) {
-// return languageCode
-// }
-// return "${languageCode}_$countryCode"
-// }
}
\ No newline at end of file
diff --git a/ui/ui-text/src/main/java/androidx/ui/text/MultiParagraph.kt b/ui/ui-text/src/main/java/androidx/ui/text/MultiParagraph.kt
new file mode 100644
index 0000000..e7923f0
--- /dev/null
+++ b/ui/ui-text/src/main/java/androidx/ui/text/MultiParagraph.kt
@@ -0,0 +1,633 @@
+/*
+ * 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.ui.text
+
+import androidx.ui.core.Density
+import androidx.ui.core.Px
+import androidx.ui.core.PxPosition
+import androidx.ui.core.px
+import androidx.ui.engine.geometry.Offset
+import androidx.ui.engine.geometry.Rect
+import androidx.ui.painting.Canvas
+import androidx.ui.painting.Path
+import androidx.ui.text.font.Font
+import java.lang.IllegalStateException
+import kotlin.math.max
+
+/**
+ * The class that renders multiple paragraphs at once.
+ *
+ * It's designed to support multiple [ParagraphStyle]s in single text widget.
+ */
+internal class MultiParagraph(
+ val annotatedString: AnnotatedString,
+ private val textStyle: TextStyle = TextStyle(),
+ private val paragraphStyle: ParagraphStyle = ParagraphStyle(),
+ val maxLines: Int? = null,
+ val ellipsis: Boolean? = null,
+ val density: Density,
+ val resourceLoader: Font.ResourceLoader
+) {
+ /**
+ * The minimum width that this paragraph could be without failing to paint
+ * its contents within itself.
+ *
+ * Valid only after [layout] has been called.
+ */
+ val minIntrinsicWidth: Float
+ get() = paragraphInfoList.foldRight(0f) { paragraphInfo, minWidth ->
+ max(paragraphInfo.paragraph.minIntrinsicWidth, minWidth)
+ }
+
+ /**
+ * Returns the smallest width beyond which increasing the width never
+ * decreases the height.
+ *
+ * Valid only after [layout] has been called.
+ */
+ val maxIntrinsicWidth: Float
+ get() = paragraphInfoList.foldRight(0f) { paragraphInfo, maxWidth ->
+ max(paragraphInfo.paragraph.maxIntrinsicWidth, maxWidth)
+ }
+
+ /**
+ * True if there is more vertical content, but the text was truncated, either
+ * because we reached `maxLines` lines of text or because the `maxLines` was
+ * null, `ellipsis` was not null, and one of the lines exceeded the width
+ * constraint.
+ *
+ * See the discussion of the `maxLines` and `ellipsis` arguments at [ParagraphStyle].
+ */
+ var didExceedMaxLines: Boolean = false
+ private set
+
+ /**
+ * The amount of horizontal space this paragraph occupies.
+ *
+ * Valid only after [layout] has been called.
+ */
+ var width: Float = 0f
+ private set
+
+ /**
+ * The amount of vertical space this paragraph occupies.
+ *
+ * Valid only after [layout] has been called.
+ */
+ var height: Float = 0f
+ private set
+
+ /**
+ * The distance from the top of the paragraph to the alphabetic
+ * baseline of the first line, in logical pixels.
+ */
+ val baseline: Float
+ get() = if (paragraphInfoList.isEmpty()) 0f else paragraphInfoList[0].paragraph.baseline
+
+ /** The total number of lines in the text. */
+ var lineCount: Int = 0
+ private set
+
+ private var needLayout = true
+
+ private val paragraphInfoList: List<ParagraphInfo>
+
+ init {
+ val paragraphStyles = fillInParagraphRanges(annotatedString, this.paragraphStyle)
+ this.paragraphInfoList = paragraphStyles.map { (style, start, end) ->
+ // TODO(haoyuchang): Change substring to Paragraph receiving text and range.
+ val textInParagraph = if (start != end) {
+ annotatedString.text.substring(start, end)
+ } else {
+ ""
+ }
+ val textStylesInParagraph = annotatedString.getLocalStyles(start, end)
+
+ // TODO(haoyuchang): remove the top and bottom padding between two paragraphs
+ val paragraph = Paragraph(
+ textInParagraph,
+ this.textStyle,
+ style,
+ textStylesInParagraph,
+ maxLines,
+ ellipsis,
+ density,
+ resourceLoader
+ )
+
+ ParagraphInfo(
+ paragraph = paragraph,
+ startIndex = start,
+ endIndex = end
+ )
+ }
+ }
+
+ /**
+ * Computes the size and position of each glyph in the paragraph.
+ *
+ * The [ParagraphConstraints] control how wide the text is allowed to be.
+ */
+ fun layout(constraints: ParagraphConstraints) {
+ this.needLayout = false
+ this.width = constraints.width
+ this.didExceedMaxLines = false
+
+ var currentLineCount = 0
+ var currentHeight = 0f
+
+ for ((index, paragraphInfo) in paragraphInfoList.withIndex()) {
+ val paragraph = paragraphInfo.paragraph
+ paragraph.layout(constraints)
+
+ paragraphInfo.startLineIndex = currentLineCount
+ paragraphInfo.endLineIndex = currentLineCount + paragraph.lineCount
+ currentLineCount = paragraphInfo.endLineIndex
+
+ paragraphInfo.top = currentHeight.px
+ paragraphInfo.bottom = (currentHeight + paragraph.height).px
+ currentHeight += paragraph.height
+
+ // TODO(haoyuchang): solve the corner case where the ellipsis won't be applied when
+ // currentLineNum == maxLines but there are still more paragraphs
+ if (paragraph.didExceedMaxLines ||
+ (currentLineCount == maxLines && index != this.paragraphInfoList.lastIndex)
+ ) {
+ this.didExceedMaxLines = true
+ break
+ }
+ }
+ this.lineCount = currentLineCount
+ this.height = currentHeight
+ }
+
+ /** Paint the paragraphs to canvas. */
+ fun paint(canvas: Canvas) {
+ assertNeedLayout()
+
+ canvas.save()
+ paragraphInfoList.forEach {
+ it.paragraph.paint(canvas)
+ canvas.translate(0f, it.paragraph.height)
+ }
+ canvas.restore()
+ }
+
+ /** Returns path that enclose the given text range. */
+ fun getPathForRange(start: Int, end: Int): Path {
+ if (start !in 0..end || end > annotatedString.text.length) {
+ throw AssertionError(
+ "Start($start) or End($end) is out of range [0..${annotatedString.text.length})," +
+ " or start > end!"
+ )
+ }
+ assertNeedLayout()
+
+ if (start == end) return Path()
+
+ val paragraphIndex = findParagraphByIndex(paragraphInfoList, start)
+ val path = Path()
+
+ paragraphInfoList.drop(paragraphIndex)
+ .takeWhile { it.startIndex < end }
+ .filterNot { it.startIndex == it.endIndex }
+ .forEach {
+ with(it) {
+ path.addPath(
+ path = paragraph.getPathForRange(
+ start = start.toLocalIndex(),
+ end = end.toLocalIndex()
+ ).toGlobal()
+ )
+ }
+ }
+ return path
+ }
+
+ /** Returns the character offset closest to the given graphical position. */
+ fun getOffsetForPosition(position: PxPosition): Int {
+ assertNeedLayout()
+ val paragraphIndex = when {
+ position.y.value <= 0f -> 0
+ position.y.value >= height -> paragraphInfoList.lastIndex
+ else -> findParagraphByY(paragraphInfoList, position.y)
+ }
+ return with(paragraphInfoList[paragraphIndex]) {
+ if (length == 0) {
+ max(0, startIndex - 1)
+ } else {
+ paragraph.getOffsetForPosition(position.toLocal()).toGlobalIndex()
+ }
+ }
+ }
+
+ /**
+ * Returns the bounding box as Rect of the character for given character offset. Rect
+ * includes the top, bottom, left and right of a character.
+ */
+ fun getBoundingBox(offset: Int): Rect {
+ assertNeedLayout()
+ assertIndexInRange(offset)
+
+ val paragraphIndex = findParagraphByIndex(paragraphInfoList, offset)
+ return with(paragraphInfoList[paragraphIndex]) {
+ paragraph.getBoundingBox(offset.toLocalIndex()).toGlobal()
+ }
+ }
+
+ /** Get the primary horizontal position for the specified text offset. */
+ fun getPrimaryHorizontal(offset: Int): Float {
+ assertNeedLayout()
+ if (offset !in 0..annotatedString.text.length) {
+ throw AssertionError("offset($offset) is out of bounds " +
+ "(0,${annotatedString.text.length}")
+ }
+
+ val paragraphIndex = if (offset == annotatedString.text.length) {
+ paragraphInfoList.lastIndex
+ } else {
+ findParagraphByIndex(paragraphInfoList, offset)
+ }
+
+ return with(paragraphInfoList[paragraphIndex]) {
+ paragraph.getPrimaryHorizontal(offset.toLocalIndex())
+ }
+ }
+
+ /**
+ * Returns the TextRange of the word at the given character offset. Characters not
+ * part of a word, such as spaces, symbols, and punctuation, have word breaks
+ * on both sides. In such cases, this method will return TextRange(offset, offset+1).
+ * Word boundaries are defined more precisely in Unicode Standard Annex #29
+ * http://www.unicode.org/reports/tr29/#Word_Boundaries
+ */
+ fun getWordBoundary(offset: Int): TextRange {
+ assertNeedLayout()
+ assertIndexInRange(offset)
+
+ val paragraphIndex = findParagraphByIndex(paragraphInfoList, offset)
+
+ return with(paragraphInfoList[paragraphIndex]) {
+ paragraph.getWordBoundary(offset.toLocalIndex()).toGlobal()
+ }
+ }
+
+ /** Returns rectangle of the cursor area. */
+ fun getCursorRect(offset: Int): Rect {
+ assertNeedLayout()
+ if (offset !in 0..annotatedString.text.length) {
+ throw AssertionError("offset($offset) is out of bounds " +
+ "(0,${annotatedString.text.length}")
+ }
+
+ val paragraphIndex = if (offset == annotatedString.text.length) {
+ paragraphInfoList.lastIndex
+ } else {
+ findParagraphByIndex(paragraphInfoList, offset)
+ }
+
+ return with(paragraphInfoList[paragraphIndex]) {
+ paragraph.getCursorRect(offset.toLocalIndex()).toGlobal()
+ }
+ }
+
+ /**
+ * Returns the line number on which the specified text offset appears.
+ * If you ask for a position before 0, you get 0; if you ask for a position
+ * beyond the end of the text, you get the last line.
+ */
+ fun getLineForOffset(offset: Int): Int {
+ assertNeedLayout()
+ assertIndexInRange(offset)
+
+ val paragraphIndex = findParagraphByIndex(paragraphInfoList, offset)
+ return with(paragraphInfoList[paragraphIndex]) {
+ paragraph.getLineForOffset(offset.toLocalIndex()).toGlobalLineIndex()
+ }
+ }
+
+ /** Returns the left x Coordinate of the given line. */
+ fun getLineLeft(lineIndex: Int): Float {
+ assertNeedLayout()
+ assertLineIndexInRange(lineIndex)
+
+ val paragraphIndex = findParagraphByLineIndex(paragraphInfoList, lineIndex)
+
+ return with(paragraphInfoList[paragraphIndex]) {
+ paragraph.getLineLeft(lineIndex.toLocalLineIndex())
+ }
+ }
+
+ /** Returns the right x Coordinate of the given line. */
+ fun getLineRight(lineIndex: Int): Float {
+ assertNeedLayout()
+ assertLineIndexInRange(lineIndex)
+
+ val paragraphIndex = findParagraphByLineIndex(paragraphInfoList, lineIndex)
+
+ return with(paragraphInfoList[paragraphIndex]) {
+ paragraph.getLineRight(lineIndex.toLocalLineIndex())
+ }
+ }
+
+ /** Returns the bottom y coordinate of the given line. */
+ fun getLineBottom(lineIndex: Int): Float {
+ assertNeedLayout()
+ assertLineIndexInRange(lineIndex)
+
+ val paragraphIndex = findParagraphByLineIndex(paragraphInfoList, lineIndex)
+
+ return with(paragraphInfoList[paragraphIndex]) {
+ paragraph.getLineBottom(lineIndex.toLocalLineIndex())
+ }
+ }
+
+ /** Returns the height of the given line. */
+ fun getLineHeight(lineIndex: Int): Float {
+ assertNeedLayout()
+ assertLineIndexInRange(lineIndex)
+
+ val paragraphIndex = findParagraphByLineIndex(paragraphInfoList, lineIndex)
+
+ return with(paragraphInfoList[paragraphIndex]) {
+ paragraph.getLineHeight(lineIndex.toLocalLineIndex())
+ }
+ }
+
+ /** Returns the width of the given line. */
+ fun getLineWidth(lineIndex: Int): Float {
+ assertNeedLayout()
+ assertLineIndexInRange(lineIndex)
+
+ val paragraphIndex = findParagraphByLineIndex(paragraphInfoList, lineIndex)
+
+ return with(paragraphInfoList[paragraphIndex]) {
+ paragraph.getLineWidth(lineIndex.toLocalLineIndex())
+ }
+ }
+
+ private fun assertNeedLayout() {
+ if (needLayout) {
+ throw IllegalStateException("")
+ }
+ }
+
+ private fun assertIndexInRange(offset: Int) {
+ if (offset !in (0 until annotatedString.text.length)) {
+ throw IndexOutOfBoundsException("offset($offset) is out of bounds" +
+ " [0, ${annotatedString.text.length})")
+ }
+ }
+
+ private fun assertLineIndexInRange(lineIndex: Int) {
+ if (lineIndex !in (0 until lineCount)) {
+ throw IndexOutOfBoundsException("lineIndex($lineIndex) is out of bounds" +
+ " [0, $lineIndex)")
+ }
+ }
+}
+
+/**
+ * Given an character index of [MultiParagraph.annotatedString], find the corresponding
+ * [ParagraphInfo] which covers the provided index.
+ *
+ * @param paragraphInfoList The list of [ParagraphInfo] containing the information of each
+ * paragraph in the [MultiParagraph].
+ * @param index The target index in the [MultiParagraph]. It should be in the range of
+ * [0, text.length)
+ * @return The index of the target [ParagraphInfo] in [paragraphInfoList].
+ */
+private fun findParagraphByIndex(paragraphInfoList: List<ParagraphInfo>, index: Int): Int {
+ return paragraphInfoList.binarySearch { paragraphInfo ->
+ when {
+ paragraphInfo.startIndex > index -> 1
+ paragraphInfo.endIndex <= index -> -1
+ else -> 0
+ }
+ }
+}
+
+/**
+ * Given the y graphical position relative to this [MultiParagraph], find the index of the
+ * corresponding [ParagraphInfo] which occupies the provided position.
+ *
+ * @param paragraphInfoList The list of [ParagraphInfo] containing the information of each
+ * paragraph in the [MultiParagraph].
+ * @param y The y coordinate position relative to the [MultiParagraph]. It should be in the range
+ * of [0, [MultiParagraph.height]].
+ * @return The index of the target [ParagraphInfo] in [paragraphInfoList].
+ */
+private fun findParagraphByY(paragraphInfoList: List<ParagraphInfo>, y: Px): Int {
+ return paragraphInfoList.binarySearch { paragraphInfo ->
+ when {
+ paragraphInfo.top > y -> 1
+ paragraphInfo.bottom <= y -> -1
+ else -> 0
+ }
+ }
+}
+
+/**
+ * Given an line index in [MultiParagraph], find the corresponding [ParagraphInfo] which
+ * covers the provided line index.
+ *
+ * @param paragraphInfoList The list of [ParagraphInfo] containing the information of each
+ * paragraph in the [MultiParagraph].
+ * @param lineIndex The target line index in the [MultiParagraph], it should be in the range of
+ * [0, [MultiParagraph.lineCount])
+ * @return The index of the target [ParagraphInfo] in [paragraphInfoList].
+ */
+private fun findParagraphByLineIndex(paragraphInfoList: List<ParagraphInfo>, lineIndex: Int): Int {
+ return paragraphInfoList.binarySearch { paragraphInfo ->
+ when {
+ paragraphInfo.startLineIndex > lineIndex -> 1
+ paragraphInfo.endLineIndex <= lineIndex -> -1
+ else -> 0
+ }
+ }
+}
+
+/**
+ * A helper function used to determine the paragraph boundaries in [MultiParagraph].
+ *
+ * It reads paragraph information from [AnnotatedString.paragraphStyles] where only some parts of
+ * text has [ParagraphStyle] specified, and unspecified parts(gaps between specified paragraphs)
+ * are considered as default paragraph with default [ParagraphStyle].
+ * For example, the following string with a specified paragraph denoted by "[]"
+ * "Hello WorldHi!"
+ * [ ]
+ * The result paragraphs are "Hello World" and "Hi!".
+ *
+ * @param annotatedString: The [AnnotatedString] on which the paragraph boundaries need to be
+ * determined.
+ * @param defaultParagraphStyle The default [ParagraphStyle]. It's used for both unspecified
+ * default paragraphs and specified paragraph. When a specified paragraph's [ParagraphStyle] has
+ * a null attribute, the default one will be used instead.
+ */
+internal fun fillInParagraphRanges(
+ annotatedString: AnnotatedString,
+ defaultParagraphStyle: ParagraphStyle
+): List<AnnotatedString.Item<ParagraphStyle>> {
+ val length = annotatedString.text.length
+ val paragraphStyles = annotatedString.paragraphStyles
+
+ var lastOffset = 0
+ val result = mutableListOf<AnnotatedString.Item<ParagraphStyle>>()
+ for ((style, start, end) in paragraphStyles) {
+ if (start != lastOffset) {
+ result.add(AnnotatedString.Item(defaultParagraphStyle, lastOffset, start))
+ }
+ result.add(AnnotatedString.Item(defaultParagraphStyle.merge(style), start, end))
+ lastOffset = end
+ }
+ if (lastOffset != length) {
+ result.add(AnnotatedString.Item(defaultParagraphStyle, lastOffset, length))
+ }
+ // This is a corner case where annotatedString is an empty string without any ParagraphStyle.
+ // In this case, a dummy ParagraphStyle is created.
+ if (result.isEmpty()) {
+ result.add(AnnotatedString.Item(defaultParagraphStyle, 0, 0))
+ }
+ return result
+}
+
+/**
+ * Helper function used to find the [TextStyle]s in the given paragraph range and also convert the
+ * range of those [TextStyle]s to paragraph local range.
+ *
+ * @param start The start index of the paragraph range, inclusive.
+ * @param end The end index of the paragraph range, exclusive.
+ * @return The list of converted [TextStyle]s in the given paragraph range.
+ */
+private fun AnnotatedString.getLocalStyles(
+ start: Int,
+ end: Int
+): List<AnnotatedString.Item<TextStyle>> {
+ if (start == end) {
+ return listOf()
+ }
+ // If the given range covers the whole AnnotatedString, return textStyles without conversion.
+ if (start == 0 && end >= this.text.length) {
+ return textStyles
+ }
+ return textStyles.filter { it.start < end && it.end > start }
+ .map {
+ AnnotatedString.Item(
+ it.style,
+ it.start.coerceIn(start, end) - start,
+ it.end.coerceIn(start, end) - start
+ )
+ }
+}
+
+/**
+ * This is a helper data structure to store the information of a single [Paragraph] in an
+ * [MultiParagraph]. It's mainly used to convert a global index, lineNumber and [Offset] to the
+ * local ones inside the [paragraph], and vice versa.
+ *
+ * @param paragraph The [Paragraph] object corresponding to this [ParagraphInfo].
+ * @param startIndex The start index of this paragraph in the parent [MultiParagraph], inclusive.
+ * @param endIndex The end index of this paragraph in the parent [MultiParagraph], exclusive.
+ * @param startLineIndex The start line index of this paragraph in the parent [MultiParagraph],
+ * inclusive.
+ * @param endLineIndex The end line index of this paragraph in the parent [MultiParagraph],
+ * exclusive.
+ * @param top The top position of the [paragraph] relative to the parent [MultiParagraph].
+ * @param bottom The bottom position of the [paragraph] relative to the parent [MultiParagraph].
+ */
+internal data class ParagraphInfo(
+ val paragraph: Paragraph,
+ val startIndex: Int,
+ val endIndex: Int,
+ var startLineIndex: Int = -1,
+ var endLineIndex: Int = -1,
+ var top: Px = (-1).px,
+ var bottom: Px = (-1).px
+) {
+
+ /**
+ * The length of the text in the covered by this paragraph.
+ */
+ val length
+ get() = endIndex - startIndex
+
+ /**
+ * Convert an index in the parent [MultiParagraph] to the local index in the [paragraph].
+ */
+ fun Int.toLocalIndex(): Int {
+ return this.coerceIn(startIndex, endIndex) - startIndex
+ }
+
+ /**
+ * Convert a local index in the [paragraph] to the global index in the parent [MultiParagraph].
+ */
+ fun Int.toGlobalIndex(): Int {
+ return this + startIndex
+ }
+
+ /**
+ * Convert a line index in the parent [MultiParagraph] to the local line index in the
+ * [paragraph].
+ *
+ */
+ fun Int.toLocalLineIndex(): Int {
+ return this - startLineIndex
+ }
+
+ /**
+ * Convert a local line index in the [paragraph] to the global line index in the parent
+ * [MultiParagraph].
+ */
+ fun Int.toGlobalLineIndex(): Int {
+ return this + startLineIndex
+ }
+
+ /**
+ * Convert a [PxPosition] relative to the parent [MultiParagraph] to the local [PxPosition]
+ * relative to the [paragraph].
+ */
+ fun PxPosition.toLocal(): PxPosition {
+ return copy(y = y - top)
+ }
+
+ /**
+ * Convert a [Rect] relative to the [paragraph] to the [Rect] relative to the parent
+ * [MultiParagraph].
+ */
+ fun Rect.toGlobal(): Rect {
+ return shift(Offset(dx = 0f, dy = this@ParagraphInfo.top.value))
+ }
+
+ /**
+ * Convert a [Path] relative to the [paragraph] to the [Path] relative to the parent
+ * [MultiParagraph].
+ *
+ * Notice that this function changes the input value.
+ */
+ fun Path.toGlobal(): Path {
+ shift(Offset(dx = 0f, dy = top.value))
+ return this
+ }
+
+ /**
+ * Convert a [TextRange] in to the [paragraph] to the [TextRange] in the parent
+ * [MultiParagraph].
+ */
+ fun TextRange.toGlobal(): TextRange {
+ return TextRange(start = start.toGlobalIndex(), end = end.toGlobalIndex())
+ }
+}
\ No newline at end of file
diff --git a/ui/ui-text/src/main/java/androidx/ui/text/Paragraph.kt b/ui/ui-text/src/main/java/androidx/ui/text/Paragraph.kt
index 12814b0..a6a8156 100644
--- a/ui/ui-text/src/main/java/androidx/ui/text/Paragraph.kt
+++ b/ui/ui-text/src/main/java/androidx/ui/text/Paragraph.kt
@@ -15,8 +15,9 @@
*/
package androidx.ui.text
+import androidx.annotation.RestrictTo
import androidx.ui.core.Density
-import androidx.ui.engine.geometry.Offset
+import androidx.ui.core.PxPosition
import androidx.ui.engine.geometry.Rect
import androidx.ui.painting.Canvas
import androidx.ui.painting.Path
@@ -103,23 +104,51 @@
/** Returns the right x Coordinate of the given line. */
fun getLineRight(lineIndex: Int): Float
+ /**
+ * Returns the bottom y coordinate of the given line.
+ *
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ fun getLineBottom(lineIndex: Int): Float
+
/** Returns the height of the given line. */
fun getLineHeight(lineIndex: Int): Float
/** Returns the width of the given line. */
fun getLineWidth(lineIndex: Int): Float
- /** Returns the text position closest to the given offset. */
- fun getPositionForOffset(offset: Offset): Int
+ /**
+ * Returns the line number on which the specified text offset appears.
+ * If you ask for a position before 0, you get 0; if you ask for a position
+ * beyond the end of the text, you get the last line.
+ *
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ fun getLineForOffset(offset: Int): Int
/**
- * Returns the bounding box as Rect of the character for given offset. Rect includes the
- * top, bottom, left and right of a character.
+ * Get the primary horizontal position for the specified text offset.
+ * @hide
*/
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ fun getPrimaryHorizontal(offset: Int): Float
+
+ /** Returns the character offset closest to the given graphical position. */
+ fun getOffsetForPosition(position: PxPosition): Int
+
+ /**
+ * Returns the bounding box as Rect of the character for given character offset. Rect
+ * includes the top, bottom, left and right of a character.
+ *
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
fun getBoundingBox(offset: Int): Rect
/**
- * Returns the TextRange of the word at the given offset. Characters not
+ * Returns the TextRange of the word at the given character offset. Characters not
* part of a word, such as spaces, symbols, and punctuation, have word breaks
* on both sides. In such cases, this method will return TextRange(offset, offset+1).
* Word boundaries are defined more precisely in Unicode Standard Annex #29
@@ -130,7 +159,7 @@
/**
* Paint the paragraph to canvas
*/
- fun paint(canvas: Canvas, x: Float, y: Float)
+ fun paint(canvas: Canvas)
}
/*expect fun Paragraph(
diff --git a/ui/ui-text/src/main/java/androidx/ui/text/ParagraphConstraints.kt b/ui/ui-text/src/main/java/androidx/ui/text/ParagraphConstraints.kt
index fde3f64..0de2f0d 100644
--- a/ui/ui-text/src/main/java/androidx/ui/text/ParagraphConstraints.kt
+++ b/ui/ui-text/src/main/java/androidx/ui/text/ParagraphConstraints.kt
@@ -38,12 +38,11 @@
* forced line break is placed after it (even if an explicit line break
* follows).
*
- * The width influences how ellipses are applied. See the discussion at [new
- * ParagraphStyle] for more details.
+ * The width influences how ellipses are applied. See the discussion at
+ * [TextPainter] for more details.
*
- * This width is also used to position glyphs according to the [TextAlign]
- * alignment described in the [ParagraphStyle] used when building the
- * [Paragraph] with a [ParagraphBuilder].
+ * This width is also used to position glyphs according to the text alignment
+ * described in the [ParagraphStyle.textAlign] to create [Paragraph].
*/
data class ParagraphConstraints(val width: Float) {
override fun toString(): String {
diff --git a/ui/ui-text/src/main/java/androidx/ui/text/ParagraphStyle.kt b/ui/ui-text/src/main/java/androidx/ui/text/ParagraphStyle.kt
index dd7c248..4fd974e 100644
--- a/ui/ui-text/src/main/java/androidx/ui/text/ParagraphStyle.kt
+++ b/ui/ui-text/src/main/java/androidx/ui/text/ParagraphStyle.kt
@@ -47,19 +47,20 @@
}
}
// TODO(siyamed) uncomment
-// /**
-// * Returns a new paragraph style that is a combination of this style and the given [other] style.
-// *
-// * If the given paragraph style is null, returns this paragraph style.
-// */
-// fun merge(other: ParagraphStyle? = null): ParagraphStyle {
-// if (other == null) return this
-//
-// return ParagraphStyle(
-// lineHeight = other.lineHeight ?: this.lineHeight,
-// textIndent = other.textIndent ?: this.textIndent,
-// textAlign = other.textAlign ?: this.textAlign,
-// textDirection = other.textDirection ?: this.textDirection
-// )
-// }
+ /**
+ * Returns a new paragraph style that is a combination of this style and the given [other]
+ * style.
+ *
+ * If the given paragraph style is null, returns this paragraph style.
+ */
+ fun merge(other: ParagraphStyle? = null): ParagraphStyle {
+ if (other == null) return this
+
+ return ParagraphStyle(
+ lineHeight = other.lineHeight ?: this.lineHeight,
+ textIndent = other.textIndent ?: this.textIndent,
+ textAlign = other.textAlign ?: this.textAlign,
+ textDirection = other.textDirection ?: this.textDirection
+ )
+ }
}
\ No newline at end of file
diff --git a/ui/ui-text/src/main/java/androidx/ui/text/RenderComparison.kt b/ui/ui-text/src/main/java/androidx/ui/text/RenderComparison.kt
index 939a7b7..69a24fa 100644
--- a/ui/ui-text/src/main/java/androidx/ui/text/RenderComparison.kt
+++ b/ui/ui-text/src/main/java/androidx/ui/text/RenderComparison.kt
@@ -21,10 +21,6 @@
* it will affect the rendering.
*
* Used by [TextSpan.compareTo] and [TextStyle.compareTo].
- *
- * The values in this enum are ordered such that they are in increasing order
- * of cost. A value with index N implies all the values with index less than N.
- * For example, [layout] (index 3) implies [paint] (2).
*/
// TODO(siyamed) remove this class if not required
internal enum class RenderComparison {
@@ -45,9 +41,6 @@
* The two objects are different but only in ways that affect paint, not layout.
*
* For example, only the color is changed.
- *
- * [RenderObject.markNeedsPaint] would be necessary to handle this kind of
- * change in a render object.
*/
PAINT,
@@ -57,9 +50,6 @@
* For example, the size is changed.
*
* This is the most drastic level of change possible.
- *
- * [RenderObject.markNeedsLayout] would be necessary to handle this kind of
- * change in a render object.
*/
LAYOUT
}
diff --git a/ui/ui-text/src/main/java/androidx/ui/text/TextBox.kt b/ui/ui-text/src/main/java/androidx/ui/text/TextBox.kt
index 334458d..83a427b 100644
--- a/ui/ui-text/src/main/java/androidx/ui/text/TextBox.kt
+++ b/ui/ui-text/src/main/java/androidx/ui/text/TextBox.kt
@@ -49,7 +49,8 @@
}
/**
- * The [left] edge of the box for left-to-right text; the [right] edge of the box for right-to-left text.
+ * The [left] edge of the box for left-to-right text; the [right] edge of the box for
+ * right-to-left text.
* See also:
* * [direction], which specifies the text direction.
*/
@@ -58,7 +59,8 @@
}
/**
- * The [right] edge of the box for left-to-right text; the [left] edge of the box for right-to-left text.
+ * The [right] edge of the box for left-to-right text; the [left] edge of the box for
+ * right-to-left text.
* See also:
* * [direction], which specifies the text direction.
*/
diff --git a/ui/ui-text/src/main/java/androidx/ui/text/TextPainter.kt b/ui/ui-text/src/main/java/androidx/ui/text/TextPainter.kt
index e6e5b4d..c9f865d 100644
--- a/ui/ui-text/src/main/java/androidx/ui/text/TextPainter.kt
+++ b/ui/ui-text/src/main/java/androidx/ui/text/TextPainter.kt
@@ -22,6 +22,7 @@
import androidx.ui.core.Constraints
import androidx.ui.core.Density
import androidx.ui.core.IntPxSize
+import androidx.ui.core.PxPosition
import androidx.ui.core.Sp
import androidx.ui.core.constrain
import androidx.ui.core.px
@@ -117,7 +118,7 @@
}
@VisibleForTesting
- internal var paragraph: Paragraph? = null
+ internal var multiParagraph: MultiParagraph? = null
private set
@VisibleForTesting
@@ -142,7 +143,7 @@
set(value) {
if (field == value) return
field = value
- paragraph = null
+ multiParagraph = null
needsLayout = true
}
@@ -218,7 +219,7 @@
val minIntrinsicWidth: Float
get() {
assertNeedsLayout("minIntrinsicWidth")
- return applyFloatingPointHack(paragraph!!.minIntrinsicWidth)
+ return applyFloatingPointHack(multiParagraph!!.minIntrinsicWidth)
}
/**
@@ -229,7 +230,7 @@
val maxIntrinsicWidth: Float
get() {
assertNeedsLayout("maxIntrinsicWidth")
- return applyFloatingPointHack(paragraph!!.maxIntrinsicWidth)
+ return applyFloatingPointHack(multiParagraph!!.maxIntrinsicWidth)
}
/**
@@ -272,15 +273,15 @@
* If [maxLines] is not null, this is true if there were more lines to be drawn than the given
* [maxLines], and thus at least one line was omitted in the output; otherwise it is false.
*
- * If [maxLines] is null, this is true if [ellipsis] is true and there was a line that
- * overflowed the `maxWidth` argument passed to [layout]; otherwise it is false.
+ * If [maxLines] is null, this is true if [overflow] is [TextOverflow.Ellipsis] and there was a
+ * line that overflowed the `maxWidth` argument passed to [layout]; otherwise it is false.
*
* Valid only after [layout] has been called.
*/
val didExceedMaxLines: Boolean
get() {
assertNeedsLayout("didExceedMaxLines")
- return paragraph!!.didExceedMaxLines
+ return multiParagraph!!.didExceedMaxLines
}
/**
@@ -307,25 +308,25 @@
if (!needsLayout && minWidth == lastMinWidth && finalMaxWidth == lastMaxWidth) return
needsLayout = false
- if (paragraph == null) {
- paragraph = Paragraph(
- text = text!!.text,
- style = createTextStyle(),
- paragraphStyle = createParagraphStyle(),
- textStyles = text!!.textStyles,
- maxLines = maxLines,
- ellipsis = isEllipsis,
- density = density,
- resourceLoader = resourceLoader
+
+ if (multiParagraph == null) {
+ multiParagraph = MultiParagraph(
+ text!!,
+ createTextStyle(),
+ paragraphStyle ?: ParagraphStyle(),
+ maxLines,
+ isEllipsis,
+ density,
+ resourceLoader
)
}
lastMinWidth = minWidth
lastMaxWidth = finalMaxWidth
- paragraph!!.layout(ParagraphConstraints(width = finalMaxWidth))
+ multiParagraph!!.layout(ParagraphConstraints(width = finalMaxWidth))
if (minWidth != finalMaxWidth) {
val newWidth = maxIntrinsicWidth.coerceIn(minWidth, finalMaxWidth)
- if (newWidth != paragraph!!.width) {
- paragraph!!.layout(ParagraphConstraints(width = newWidth))
+ if (newWidth != multiParagraph!!.width) {
+ multiParagraph!!.layout(ParagraphConstraints(width = newWidth))
}
}
}
@@ -335,11 +336,11 @@
val didOverflowHeight = didExceedMaxLines
size = constraints.constrain(
- IntPxSize(paragraph!!.width.px.round(), paragraph!!.height.px.round())
+ IntPxSize(multiParagraph!!.width.px.round(), multiParagraph!!.height.px.round())
).let {
Size(it.width.value.toFloat(), it.height.value.toFloat())
}
- val didOverflowWidth = size.width < paragraph!!.width
+ val didOverflowWidth = size.width < multiParagraph!!.width
// TODO(abarth): We're only measuring the sizes of the line boxes here. If
// the glyphs draw outside the line boxes, we might think that there isn't
// visual overflow when there actually is visual overflow. This can become
@@ -355,8 +356,8 @@
resourceLoader = resourceLoader
)
fadeSizePainter.layoutText()
- val fadeWidth = fadeSizePainter.paragraph!!.width
- val fadeHeight = fadeSizePainter.paragraph!!.height
+ val fadeWidth = fadeSizePainter.multiParagraph!!.width
+ val fadeHeight = fadeSizePainter.multiParagraph!!.height
if (didOverflowWidth) {
val (fadeStart, fadeEnd) = if (textDirection == TextDirection.Rtl) {
Pair(fadeWidth, 0.0f)
@@ -383,7 +384,7 @@
}
/**
- * Paints the text onto the given canvas at the given offset.
+ * Paints the text onto the given canvas.
*
* Valid only after [layout] has been called.
*
@@ -395,7 +396,7 @@
* To set the text style, specify a [TextStyle] when creating the [TextSpan] that you pass to
* the [TextPainter] constructor or to the [text] property.
*/
- fun paint(canvas: Canvas, offset: Offset) {
+ fun paint(canvas: Canvas) {
assert(!needsLayout) {
"TextPainter.paint called when text geometry was not yet calculated.\n" +
"Please call layout() before paint() to position the text before painting it."
@@ -415,7 +416,7 @@
// layoutTextWithConstraints(constraints!!)
if (hasVisualOverflow) {
- val bounds = offset.and(size)
+ val bounds = Rect.fromLTWH(0f, 0f, size.width, size.height)
if (overflowShader != null) {
// This layer limits what the shader below blends with to be just the text
// (as opposed to the text and its background).
@@ -425,14 +426,15 @@
}
canvas.clipRect(bounds)
}
- paragraph!!.paint(canvas, offset.dx, offset.dy)
+
+ multiParagraph!!.paint(canvas)
if (hasVisualOverflow) {
if (overflowShader != null) {
- canvas.translate(offset.dx, offset.dy)
+ val bounds = Rect.fromLTWH(0f, 0f, size.width, size.height)
val paint = Paint()
paint.blendMode = BlendMode.multiply
paint.shader = overflowShader
- canvas.drawRect(Offset.zero.and(size), paint)
+ canvas.drawRect(bounds, paint)
}
canvas.restore()
}
@@ -443,23 +445,21 @@
*
* If the given range is empty, do nothing.
*
- * @param start inclusive start offset of the drawing range.
- * @param end exclusive end offset of the drawing range.
+ * @param start inclusive start character offset of the drawing range.
+ * @param end exclusive end character offset of the drawing range.
* @param color a color to be used for drawing background.
* @param canvas the target canvas.
- * @param offset the drawing offset.
*/
- fun paintBackground(start: Int, end: Int, color: Color, canvas: Canvas, offset: Offset) {
+ fun paintBackground(start: Int, end: Int, color: Color, canvas: Canvas) {
assert(!needsLayout)
if (start == end) return
- val selectionPath = paragraph!!.getPathForRange(start, end)
- selectionPath.shift(offset)
+ val selectionPath = multiParagraph!!.getPathForRange(start, end)
// TODO(haoyuchang): check if move this paint to parameter is better
canvas.drawPath(selectionPath, Paint().apply { this.color = color })
}
/**
- * Draws the cursor at the given offset.
+ * Draws the cursor at the given character offset.
*
* TODO(nona): Make cursor customizable.
*
@@ -468,19 +468,54 @@
*/
fun paintCursor(offset: Int, canvas: Canvas) {
assert(!needsLayout)
- val cursorRect = paragraph!!.getCursorRect(offset)
+ val cursorRect = multiParagraph!!.getCursorRect(offset)
canvas.drawRect(cursorRect, Paint().apply { this.color = Color.Black })
}
- /** Returns the position within the text for the given pixel offset. */
- fun getPositionForOffset(offset: Offset): Int {
+ /**
+ * Returns the bottom y coordinate of the given line.
+ *
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ fun getLineBottom(lineIndex: Int): Float {
assert(!needsLayout)
- return paragraph!!.getPositionForOffset(offset)
+ return multiParagraph!!.getLineBottom(lineIndex)
}
/**
- * Returns the bounding box as Rect of the character for given text position. Rect includes the
- * top, bottom, left and right of a character.
+ * Returns the line number on which the specified text offset appears.
+ * If you ask for a position before 0, you get 0; if you ask for a position
+ * beyond the end of the text, you get the last line.
+ *
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ fun getLineForOffset(offset: Int): Int {
+ assert(!needsLayout)
+ return multiParagraph!!.getLineForOffset(offset)
+ }
+
+ /**
+ * Get the primary horizontal position for the specified text offset.
+ *
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ fun getPrimaryHorizontal(offset: Int): Float {
+ assert(!needsLayout)
+ return multiParagraph!!.getPrimaryHorizontal(offset)
+ }
+
+ /** Returns the character offset closest to the given graphical position. */
+ fun getOffsetForPosition(position: PxPosition): Int {
+ assert(!needsLayout)
+ return multiParagraph!!.getOffsetForPosition(position)
+ }
+
+ /**
+ * Returns the bounding box as Rect of the character for given character offset. Rect includes
+ * the top, bottom, left and right of a character.
*
* Valid only after [layout] has been called.
*
@@ -489,19 +524,19 @@
@RestrictTo(RestrictTo.Scope.LIBRARY)
fun getBoundingBox(offset: Int): Rect {
assert(!needsLayout)
- return paragraph!!.getBoundingBox(offset)
+ return multiParagraph!!.getBoundingBox(offset)
}
/**
- * Returns the text range of the word at the given offset. Characters not part of a word, such
- * as spaces, symbols, and punctuation, have word breaks on both sides. In such cases, this
- * method will return a text range that contains the given text position.
+ * Returns the text range of the word at the given character offset. Characters not part of a
+ * word, such as spaces, symbols, and punctuation, have word breaks on both sides. In such
+ * cases, this method will return a text range that contains the given character offset.
*
* Word boundaries are defined more precisely in Unicode Standard Annex #29
* <http://www.unicode.org/reports/tr29/#Word_Boundaries>.
*/
- fun getWordBoundary(position: Int): TextRange {
+ fun getWordBoundary(offset: Int): TextRange {
assert(!needsLayout)
- return paragraph!!.getWordBoundary(position)
+ return multiParagraph!!.getWordBoundary(offset)
}
-}
\ No newline at end of file
+}
diff --git a/ui/ui-text/src/main/java/androidx/ui/text/TextSpan.kt b/ui/ui-text/src/main/java/androidx/ui/text/TextSpan.kt
index b399a56..baa9d8a 100644
--- a/ui/ui-text/src/main/java/androidx/ui/text/TextSpan.kt
+++ b/ui/ui-text/src/main/java/androidx/ui/text/TextSpan.kt
@@ -66,8 +66,7 @@
*/
fun toPlainText(): String {
val buffer = StringBuilder()
- visitTextSpan {
- span: TextSpan ->
+ visitTextSpan { span: TextSpan ->
buffer.append(span.text)
true
}
@@ -84,7 +83,8 @@
}
if (other.text != text ||
children.size != other.children.size ||
- (style == null) != (other.style == null)) {
+ (style == null) != (other.style == null)
+ ) {
return RenderComparison.LAYOUT
}
var result: RenderComparison = RenderComparison.IDENTICAL
diff --git a/ui/ui-text/src/main/java/androidx/ui/text/TextStyle.kt b/ui/ui-text/src/main/java/androidx/ui/text/TextStyle.kt
index 91bcf86..b500187 100644
--- a/ui/ui-text/src/main/java/androidx/ui/text/TextStyle.kt
+++ b/ui/ui-text/src/main/java/androidx/ui/text/TextStyle.kt
@@ -45,10 +45,9 @@
* style cannot be found in the provided custom font family.
* @param fontFamily font family to be used when rendering the text.
* @param fontFeatureSettings The advanced typography settings provided by font. The format is the
- * same as the CSS font-feature-settings attribute: https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop
+ * same as the CSS font-feature-settings attribute:
+ * https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop
* @param letterSpacing The amount of space (in logical pixels) to add between each letter.
- * @param wordSpacing The amount of space (in logical pixels) to add at each sequence of white-space
- * (i.e. between each word). Only works on Android Q and above.
* @param baselineShift This parameter specifies how much the baseline is shifted from the current
* position.
* @param textGeometricTransform The geometric transformation applied the text.
@@ -136,16 +135,12 @@
*
* This will not work well if the styles don't set the same fields.
*
- * The `t` argument represents position on the timeline, with 0.0 meaning that the interpolation
- * has not started, returning `a` (or something equivalent to `a`), 1.0 meaning that the
- * interpolation has finished, returning `b` (or something equivalent to `b`), and values in
- * between meaning that the interpolation is at the relevant point on the timeline between `a`
- * and `b`. The interpolation can be extrapolated beyond 0.0 and 1.0, so negative values and
- * values greater than 1.0 are valid (and can easily be generated by curves such as
- * [Curves.elasticInOut]).
- *
- * Values for `t` are usually obtained from an [Animation<Float>], such as an
- * [AnimationController].
+ * The `t` argument represents position on the timeline, with 0.0 meaning that the
+ * interpolation has not started, returning `a` (or something equivalent to `a`), 1.0
+ * meaning that the interpolation has finished, returning `b` (or something equivalent to
+ * `b`), and values in between meaning that the interpolation is at the relevant point on
+ * the timeline between `a` and `b`. The interpolation can be extrapolated beyond 0.0 and
+ * 1.0, so negative values and values greater than 1.0 are valid.
*/
fun lerp(a: TextStyle? = null, b: TextStyle? = null, t: Float): TextStyle? {
val aIsNull = a == null
diff --git a/ui/ui-text/src/main/java/androidx/ui/text/platform/AndroidParagraph.kt b/ui/ui-text/src/main/java/androidx/ui/text/platform/AndroidParagraph.kt
index c54d32b..ce4624b0 100644
--- a/ui/ui-text/src/main/java/androidx/ui/text/platform/AndroidParagraph.kt
+++ b/ui/ui-text/src/main/java/androidx/ui/text/platform/AndroidParagraph.kt
@@ -52,10 +52,10 @@
import androidx.text.style.SkewXSpan
import androidx.text.style.TypefaceSpan
import androidx.ui.core.Density
+import androidx.ui.core.PxPosition
import androidx.ui.text.TextRange
import androidx.ui.core.px
import androidx.ui.core.withDensity
-import androidx.ui.engine.geometry.Offset
import androidx.ui.engine.geometry.Rect
import androidx.ui.text.font.FontStyle
import androidx.ui.text.font.FontSynthesis
@@ -118,13 +118,13 @@
get() = 0.0f
override val maxIntrinsicWidth: Float
- get() = layout?.let { it.maxIntrinsicWidth } ?: 0.0f
+ get() = layout?.maxIntrinsicWidth ?: 0.0f
override val baseline: Float
- get() = layout?.let { it.layout.getLineBaseline(0).toFloat() } ?: 0.0f
+ get() = layout?.getLineBaseline(0) ?: 0.0f
override val didExceedMaxLines: Boolean
- get() = layout?.let { it.didExceedMaxLines } ?: false
+ get() = layout?.didExceedMaxLines ?: false
@VisibleForTesting
internal val textLocale: Locale
@@ -135,10 +135,9 @@
private val ensureLayout: TextLayout
get() {
- val tmpLayout = this.layout ?: throw java.lang.IllegalStateException(
+ return this.layout ?: throw java.lang.IllegalStateException(
"layout() should be called first"
)
- return tmpLayout
}
@VisibleForTesting
@@ -156,7 +155,7 @@
text = text,
textIndent = paragraphStyle.textIndent,
textStyles = listOf(
- AnnotatedString.Item<TextStyle>(
+ AnnotatedString.Item(
newStyle,
0,
text.length
@@ -203,14 +202,14 @@
this.width = floorWidth
}
- override fun getPositionForOffset(offset: Offset): Int {
- val line = ensureLayout.getLineForVertical(offset.dy.toInt())
- return ensureLayout.getOffsetForHorizontal(line, offset.dx)
+ override fun getOffsetForPosition(position: PxPosition): Int {
+ val line = ensureLayout.getLineForVertical(position.y.value.toInt())
+ return ensureLayout.getOffsetForHorizontal(line, position.x.value)
}
/**
- * Returns the bounding box as Rect of the character for given TextPosition. Rect includes the
- * top, bottom, left and right of a character.
+ * Returns the bounding box as Rect of the character for given character offset. Rect includes
+ * the top, bottom, left and right of a character.
*/
// TODO:(qqd) Implement RTL case.
override fun getBoundingBox(offset: Int): Rect {
@@ -225,7 +224,7 @@
}
override fun getPathForRange(start: Int, end: Int): Path {
- if (!(start <= end && start >= 0 && end <= text.length)) {
+ if (start !in 0..end || end > text.length) {
throw AssertionError(
"Start($start) or End($end) is out of Range(0..${text.length}), or start > end!"
)
@@ -236,7 +235,7 @@
}
override fun getCursorRect(offset: Int): Rect {
- if (!(offset in (0..text.length))) {
+ if (offset !in 0..text.length) {
throw AssertionError("offset($offset) is out of bounds (0,${text.length}")
}
// TODO(nona): Support cursor drawable.
@@ -267,22 +266,27 @@
override fun getLineRight(lineIndex: Int): Float = ensureLayout.getLineRight(lineIndex)
+ override fun getLineBottom(lineIndex: Int): Float = ensureLayout.getLineBottom(lineIndex)
+
override fun getLineHeight(lineIndex: Int): Float = ensureLayout.getLineHeight(lineIndex)
override fun getLineWidth(lineIndex: Int): Float = ensureLayout.getLineWidth(lineIndex)
+ override fun getLineForOffset(offset: Int): Int = ensureLayout.getLineForOffset(offset)
+
+ override fun getPrimaryHorizontal(offset: Int): Float =
+ ensureLayout.getPrimaryHorizontal(offset)
+
/**
* @return true if the given line is ellipsized, else false.
*/
internal fun isEllipsisApplied(lineIndex: Int): Boolean =
ensureLayout.isEllipsisApplied(lineIndex)
- override fun paint(canvas: Canvas, x: Float, y: Float) {
+ override fun paint(canvas: Canvas) {
val tmpLayout = layout ?: throw IllegalStateException("paint cannot be " +
"called before layout() is called")
- canvas.translate(x, y)
tmpLayout.paint(canvas.nativeCanvas)
- canvas.translate(-x, -y)
}
private fun createTypeface(style: TextStyle): Typeface {
@@ -476,7 +480,7 @@
// fontSizeScale must be applied after fontSize is applied.
fontSizeScale?.let {
- textPaint.textSize *= fontSizeScale
+ textPaint.textSize *= it
}
// TODO(siyamed): This default values are problem here. If the user just gives a single font
diff --git a/ui/ui-text/src/main/java/androidx/ui/text/platform/TypefaceAdapter.kt b/ui/ui-text/src/main/java/androidx/ui/text/platform/TypefaceAdapter.kt
index 395f4c7..b7d89d2 100644
--- a/ui/ui-text/src/main/java/androidx/ui/text/platform/TypefaceAdapter.kt
+++ b/ui/ui-text/src/main/java/androidx/ui/text/platform/TypefaceAdapter.kt
@@ -49,8 +49,8 @@
)
companion object {
- // Accept FontWeights at and above 600 to be bold. 600 comes from FontFamily.cpp#computeFakery
- // function in minikin
+ // Accept FontWeights at and above 600 to be bold. 600 comes from
+ // FontFamily.cpp#computeFakery function in minikin
private val ANDROID_BOLD = FontWeight.w600
// 16 is a random number and is not based on any strong logic
@@ -205,7 +205,8 @@
return if (Build.VERSION.SDK_INT < 28) {
val targetStyle = getTypefaceStyle(
isBold = synthesizeWeight,
- isItalic = synthesizeStyle && fontStyle == FontStyle.Italic)
+ isItalic = synthesizeStyle && fontStyle == FontStyle.Italic
+ )
Typeface.create(typeface, targetStyle)
} else {
val finalFontWeight = if (synthesizeWeight) {
diff --git a/ui/ui-text/src/test/java/androidx/ui/input/EditProcessorTest.kt b/ui/ui-text/src/test/java/androidx/ui/input/EditProcessorTest.kt
index 3bdaf143..591e2c4 100644
--- a/ui/ui-text/src/test/java/androidx/ui/input/EditProcessorTest.kt
+++ b/ui/ui-text/src/test/java/androidx/ui/input/EditProcessorTest.kt
@@ -39,8 +39,8 @@
val proc = EditProcessor()
val tis: TextInputService = mock()
- proc.onNewState(EditorState("ABCDE", TextRange(0, 0)), tis)
- val captor = argumentCaptor<EditorState>()
+ proc.onNewState(EditorModel("ABCDE", TextRange(0, 0)), tis)
+ val captor = argumentCaptor<EditorModel>()
verify(tis, times(1)).onStateUpdated(captor.capture())
assertEquals(1, captor.allValues.size)
assertEquals("ABCDE", captor.firstValue.text)
diff --git a/ui/ui-text/src/test/java/androidx/ui/text/MultiParagraphTest.kt b/ui/ui-text/src/test/java/androidx/ui/text/MultiParagraphTest.kt
new file mode 100644
index 0000000..ce472ad
--- /dev/null
+++ b/ui/ui-text/src/test/java/androidx/ui/text/MultiParagraphTest.kt
@@ -0,0 +1,107 @@
+/*
+ * 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.ui.text
+
+import androidx.ui.text.style.TextAlign
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class MultiParagraphTest {
+ @Test
+ fun `test fillInParagraphRanges`() {
+ val text = "Hello World"
+ val paragraphStyle = ParagraphStyle(textAlign = TextAlign.Center)
+ val paragraphStyles = listOf(AnnotatedString.Item(paragraphStyle, 0, 5))
+ val annotatedString = AnnotatedString(text = text, paragraphStyles = paragraphStyles)
+ val defaultParagraphStyle = ParagraphStyle(lineHeight = 2.0f)
+
+ val paragraphs = fillInParagraphRanges(annotatedString, defaultParagraphStyle)
+
+ assertThat(paragraphs.size).isEqualTo(2)
+
+ assertThat(paragraphs[0].style).isEqualTo(defaultParagraphStyle.merge(paragraphStyle))
+ assertThat(paragraphs[0].start).isEqualTo(0)
+ assertThat(paragraphs[0].end).isEqualTo(5)
+
+ assertThat(paragraphs[1].style).isEqualTo(defaultParagraphStyle)
+ assertThat(paragraphs[1].start).isEqualTo(5)
+ assertThat(paragraphs[1].end).isEqualTo(text.length)
+ }
+
+ @Test
+ fun `test fillInParagraphRanges only string`() {
+ val text = "Hello World"
+ val annotatedString = AnnotatedString(text = text)
+ val defaultParagraphStyle = ParagraphStyle(lineHeight = 2.0f)
+
+ val paragraphs = fillInParagraphRanges(annotatedString, defaultParagraphStyle)
+
+ assertThat(paragraphs.size).isEqualTo(1)
+
+ assertThat(paragraphs[0].style).isEqualTo(defaultParagraphStyle)
+ assertThat(paragraphs[0].start).isEqualTo(0)
+ assertThat(paragraphs[0].end).isEqualTo(text.length)
+ }
+
+ @Test
+ fun `test fillInParagraphRanges empty string`() {
+ val text = ""
+ val annotatedString = AnnotatedString(text = text)
+ val defaultParagraphStyle = ParagraphStyle(lineHeight = 2.0f)
+
+ val paragraphs = fillInParagraphRanges(annotatedString, defaultParagraphStyle)
+
+ assertThat(paragraphs.size).isEqualTo(1)
+
+ assertThat(paragraphs[0].style).isEqualTo(defaultParagraphStyle)
+ assertThat(paragraphs[0].start).isEqualTo(0)
+ assertThat(paragraphs[0].end).isEqualTo(text.length)
+ }
+
+ @Test
+ fun `test fillInParagraphRanges with newLine`() {
+ val text = "Hello\nWorld"
+ val annotatedString = AnnotatedString(text = text)
+ val defaultParagraphStyle = ParagraphStyle(lineHeight = 2.0f)
+
+ val paragraphs = fillInParagraphRanges(annotatedString, defaultParagraphStyle)
+
+ assertThat(paragraphs.size).isEqualTo(1)
+
+ assertThat(paragraphs[0].style).isEqualTo(defaultParagraphStyle)
+ assertThat(paragraphs[0].start).isEqualTo(0)
+ assertThat(paragraphs[0].end).isEqualTo(text.length)
+ }
+
+ @Test
+ fun `test fillInParagraphRanges with only lineFeed`() {
+ val text = "\n"
+ val annotatedString = AnnotatedString(text = text)
+ val defaultParagraphStyle = ParagraphStyle(lineHeight = 2.0f)
+
+ val paragraphs = fillInParagraphRanges(annotatedString, defaultParagraphStyle)
+
+ assertThat(paragraphs.size).isEqualTo(1)
+
+ assertThat(paragraphs[0].style).isEqualTo(defaultParagraphStyle)
+ assertThat(paragraphs[0].start).isEqualTo(0)
+ assertThat(paragraphs[0].end).isEqualTo(1)
+ }
+}
\ No newline at end of file
diff --git a/ui/ui-text/src/test/java/androidx/ui/text/TextPainterTest.kt b/ui/ui-text/src/test/java/androidx/ui/text/TextPainterTest.kt
index 0738a48..5dcae69 100644
--- a/ui/ui-text/src/test/java/androidx/ui/text/TextPainterTest.kt
+++ b/ui/ui-text/src/test/java/androidx/ui/text/TextPainterTest.kt
@@ -18,7 +18,6 @@
import androidx.ui.core.Constraints
import androidx.ui.core.Density
-import androidx.ui.engine.geometry.Offset
import androidx.ui.painting.Canvas
import androidx.ui.text.font.Font
import androidx.ui.text.style.TextAlign
@@ -128,7 +127,7 @@
textPainter.text = text
assertThat(textPainter.text).isEqualTo(text)
- assertThat(textPainter.paragraph).isNull()
+ assertThat(textPainter.multiParagraph).isNull()
assertThat(textPainter.needsLayout).isTrue()
}
@@ -223,6 +222,6 @@
val textPainter = TextPainter(density = density, resourceLoader = resourceLoader)
val canvas = mock<Canvas>()
- textPainter.paint(canvas, Offset(0.0f, 0.0f))
+ textPainter.paint(canvas)
}
}
diff --git a/ui/ui-vector/OWNERS b/ui/ui-vector/OWNERS
new file mode 100644
index 0000000..c3a259b
--- /dev/null
+++ b/ui/ui-vector/OWNERS
@@ -0,0 +1,13 @@
+pavlis@google.com
+adamp@google.com
+mount@google.com
+popam@google.com
+andreykulikov@google.com
+ryanmentley@google.com
+shepshapard@google.com
+njawad@google.com
+haoyuchang@google.com
+nona@google.com
+siyamed@google.com
+qqd@google.com
+sumir@google.com
\ No newline at end of file
diff --git a/ui/ui-vector/build.gradle b/ui/ui-vector/build.gradle
new file mode 100644
index 0000000..014b6cb
--- /dev/null
+++ b/ui/ui-vector/build.gradle
@@ -0,0 +1,59 @@
+/*
+ * 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.
+ */
+
+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.*
+
+plugins {
+ id("AndroidXPlugin")
+ id("com.android.library")
+ id("AndroidXUiPlugin")
+ id("org.jetbrains.kotlin.android")
+}
+
+dependencies {
+ kotlinPlugin project(path: ":compose:compose-compiler", configuration: "embeddablePlugin")
+
+ implementation project(':ui:ui-core')
+ implementation "androidx.collection:collection:1.0.0"
+ implementation project(":compose:compose-runtime")
+ implementation (KOTLIN_COMPOSE_STDLIB)
+
+ // TODO: Non-Kotlin dependency, move to Android-specific code
+// implementation "androidx.collection:collection:1.0.0-alpha01"
+ // TODO: Non-Kotlin dependency, move to Android-specific code
+ implementation "androidx.core:core:1.0.0"
+}
+
+androidx {
+ name = "AndroidX UI Vector"
+ publish = Publish.SNAPSHOT_AND_RELEASE
+ mavenVersion = LibraryVersions.UI
+ mavenGroup = LibraryGroups.UI
+ inceptionYear = "2019"
+ description = "AndroidX UI Vector"
+}
+
+tasks.withType(KotlinCompile).configureEach {
+ kotlinOptions {
+ // TODO(njawad): Temporary disabled, make it true when IR bug b/129076229 is fixed.
+ useIR = false
+ }
+}
diff --git a/benchmark/src/androidTest/AndroidManifest.xml b/ui/ui-vector/src/androidTest/AndroidManifest.xml
similarity index 66%
copy from benchmark/src/androidTest/AndroidManifest.xml
copy to ui/ui-vector/src/androidTest/AndroidManifest.xml
index f5ec776..d974f89 100644
--- a/benchmark/src/androidTest/AndroidManifest.xml
+++ b/ui/ui-vector/src/androidTest/AndroidManifest.xml
@@ -14,13 +14,6 @@
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.benchmark.test">
+<manifest package="androidx.ui.vector" xmlns:android="http://schemas.android.com/apk/res/android">
+</manifest>
- <application
- android:name="androidx.benchmark.ArgumentInjectingApplication">
- <activity android:name="android.app.Activity"/>
- </application>
-</manifest>
\ No newline at end of file
diff --git a/benchmark/src/androidTest/AndroidManifest.xml b/ui/ui-vector/src/main/AndroidManifest.xml
similarity index 66%
copy from benchmark/src/androidTest/AndroidManifest.xml
copy to ui/ui-vector/src/main/AndroidManifest.xml
index f5ec776..d974f89 100644
--- a/benchmark/src/androidTest/AndroidManifest.xml
+++ b/ui/ui-vector/src/main/AndroidManifest.xml
@@ -14,13 +14,6 @@
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.benchmark.test">
+<manifest package="androidx.ui.vector" xmlns:android="http://schemas.android.com/apk/res/android">
+</manifest>
- <application
- android:name="androidx.benchmark.ArgumentInjectingApplication">
- <activity android:name="android.app.Activity"/>
- </application>
-</manifest>
\ No newline at end of file
diff --git a/ui/ui-vector/src/main/java/androidx/ui/vector/VectorComposeNonIR.kt b/ui/ui-vector/src/main/java/androidx/ui/vector/VectorComposeNonIR.kt
new file mode 100644
index 0000000..c9fb0acd
--- /dev/null
+++ b/ui/ui-vector/src/main/java/androidx/ui/vector/VectorComposeNonIR.kt
@@ -0,0 +1,214 @@
+/*
+ * 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.ui.vector
+
+import androidx.compose.Applier
+import androidx.compose.ApplyAdapter
+import androidx.compose.Component
+import androidx.compose.Composable
+import androidx.compose.Composer
+import androidx.compose.ComposerUpdater
+import androidx.compose.CompositionContext
+import androidx.compose.CompositionReference
+import androidx.compose.Effect
+import androidx.compose.Recomposer
+import androidx.compose.SlotTable
+import androidx.compose.ViewValidator
+import androidx.compose.cache
+import androidx.ui.graphics.vectorgraphics.GroupComponent
+import androidx.ui.graphics.vectorgraphics.VNode
+import androidx.ui.graphics.vectorgraphics.VectorComponent
+import java.util.WeakHashMap
+
+private val VectorTreeRoots = WeakHashMap<VectorComponent, VectorTree>()
+
+class VectorScope(val composer: VectorComposition)
+
+private fun obtainVectorTree(container: VectorComponent): VectorTree {
+ var vectorTree = VectorTreeRoots[container]
+ if (vectorTree == null) {
+ vectorTree = VectorTree()
+ VectorTreeRoots[container] = vectorTree
+ }
+ return vectorTree
+}
+
+fun composeVector(
+ container: VectorComponent,
+ parent: CompositionReference? = null,
+ composable: @Composable() VectorScope.() -> Unit
+) {
+ var root = VectorTreeRoots[container]
+ if (root == null) {
+ lateinit var composer: VectorComposer
+ root = obtainVectorTree(container)
+ root.context = CompositionContext.prepare(root, parent) {
+ VectorComposer(container.root, this).also { composer = it }
+ }
+ root.scope = VectorScope(VectorComposition(composer))
+ }
+ root.composable = composable
+ root.context.compose()
+}
+
+class VectorComposer(
+ val root: VNode,
+ recomposer: Recomposer
+) : Composer<VNode>(SlotTable(), Applier(root, VectorApplyAdapter()), recomposer)
+
+fun disposeVector(container: VectorComponent, parent: CompositionReference? = null) {
+ composeVector(container, parent) {}
+ VectorTreeRoots.remove(container)
+}
+
+private class VectorTree : Component() {
+
+ lateinit var scope: VectorScope
+ lateinit var composable: @Composable() VectorScope.() -> Unit
+ lateinit var context: CompositionContext
+
+ override fun compose() {
+ with(context.composer) {
+ startGroup(0) // TODO (njawad) what key should be used here?
+ scope.composable()
+ endGroup()
+ }
+ }
+}
+
+@PublishedApi
+internal val VectorGroupKey = Object()
+
+internal class VectorApplyAdapter : ApplyAdapter<VNode> {
+ override fun VNode.start(instance: VNode) {
+ // NO-OP
+ }
+
+ override fun VNode.insertAt(index: Int, instance: VNode) {
+ obtainGroup().insertAt(index, instance)
+ }
+
+ override fun VNode.removeAt(index: Int, count: Int) {
+ obtainGroup().remove(index, count)
+ }
+
+ override fun VNode.move(from: Int, to: Int, count: Int) {
+ obtainGroup().move(from, to, count)
+ }
+
+ override fun VNode.end(instance: VNode, parent: VNode) {
+ // NO-OP
+ }
+
+ fun VNode.obtainGroup(): GroupComponent {
+ return when (this) {
+ is GroupComponent -> this
+ else -> throw IllegalArgumentException("Cannot only insert VNode into Group")
+ }
+ }
+}
+
+typealias VectorUpdater<T> = ComposerUpdater<VNode, T>
+
+class VectorComposition(val composer: VectorComposer) {
+ @Suppress("NOTHING_TO_INLINE")
+ inline operator fun <V> Effect<V>.unaryPlus(): V = resolve(this@VectorComposition.composer)
+
+ inline fun <T: VNode> emit(
+ key: Any,
+ /*crossinline*/
+ ctor: () -> T,
+ update: VectorUpdater<VNode>.() -> Unit
+ ) = with(composer) {
+ startNode(key)
+
+ @Suppress("UNCHECKED_CAST")
+ val node = if (inserting) {
+ ctor().also {
+ emitNode(it)
+ }
+ } else {
+ useNode()
+ }
+
+ VectorUpdater(this, node).update()
+ endNode()
+ }
+
+ inline fun emit(
+ key: Any,
+ /*crossinline*/
+ ctor: () -> GroupComponent,
+ update: VectorUpdater<GroupComponent>.() -> Unit,
+ children: () -> Unit
+ ) = with(composer) {
+ startNode(key)
+
+ @Suppress("UNCHECKED_CAST")
+ val node = if (inserting) {
+ ctor().also {
+ emitNode(it)
+ }
+ } else {
+ useNode() as GroupComponent
+ }
+
+ VectorUpdater(this, node).update()
+ children()
+ endNode()
+ }
+
+ @Suppress("NOTHING_TO_INLINE")
+ inline fun joinKey(left: Any, right: Any?): Any = composer.joinKey(left, right)
+
+ inline fun call(
+ key: Any,
+ /*crossinline*/
+ invalid: ViewValidator.() -> Boolean,
+ block: () -> Unit
+ ) = with(composer) {
+ startGroup(key)
+ if (ViewValidator(composer).invalid() || inserting) {
+ startGroup(0)
+ block()
+ endGroup()
+ } else {
+ skipGroup(0)
+ }
+ endGroup()
+ }
+
+ inline fun <T> call(
+ key: Any,
+ /*crossinline*/
+ ctor: () -> T,
+ /*crossinline*/
+ invalid: ViewValidator.(f: T) -> Boolean,
+ block: (f: T) -> Unit
+ ) = with(composer) {
+ startGroup(key)
+ val f = cache(true, ctor)
+ if (ViewValidator(this).invalid(f) || inserting) {
+ startGroup(0)
+ block(f)
+ endGroup()
+ } else {
+ skipGroup(0)
+ }
+ endGroup()
+ }
+}
\ No newline at end of file
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/AccessibilityTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/AccessibilityTest.kt
index 050aab8..4736f91 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/AccessibilityTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/AccessibilityTest.kt
@@ -97,7 +97,7 @@
assertBasicState(initialPage)
var node = AccessibilityNodeInfoCompat.obtain()
- activityTestRule.runOnUiThread {
+ runOnUiThreadSync {
ViewCompat.onInitializeAccessibilityNodeInfo(viewPager, node)
}
var collectionInfo = node.collectionInfo
@@ -127,7 +127,7 @@
val initialPage = viewPager.currentItem
assertBasicState(initialPage)
- activityTestRule.runOnUiThread {
+ runOnUiThreadSync {
viewPager.setOrientation(getOppositeOrientation(config.orientation))
}
assertBasicState(initialPage)
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/AdapterDataSetChangeTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/AdapterDataSetChangeTest.kt
index face96e..8d4bc8f 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/AdapterDataSetChangeTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/AdapterDataSetChangeTest.kt
@@ -67,7 +67,7 @@
// Dispatch and wait for data set changes
val layoutChangedLatch = test.viewPager.addWaitForLayoutChangeLatch()
- activityTestRule.runOnUiThread {
+ test.runOnUiThreadSync {
config.actions.forEach { it.perform(adapter) }
}
layoutChangedLatch.await(1, SECONDS)
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/AdapterDataSetChangeWhileSmoothScrollTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/AdapterDataSetChangeWhileSmoothScrollTest.kt
new file mode 100644
index 0000000..a1bc911
--- /dev/null
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/AdapterDataSetChangeWhileSmoothScrollTest.kt
@@ -0,0 +1,405 @@
+/*
+ * 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.viewpager2.widget
+
+import androidx.test.filters.LargeTest
+import androidx.testutils.LocaleTestUtils
+import androidx.viewpager2.widget.AdapterDataSetChangeWhileSmoothScrollTest.Event.MarkerEvent
+import androidx.viewpager2.widget.AdapterDataSetChangeWhileSmoothScrollTest.Event.OnPageScrollStateChangedEvent
+import androidx.viewpager2.widget.AdapterDataSetChangeWhileSmoothScrollTest.Event.OnPageScrolledEvent
+import androidx.viewpager2.widget.AdapterDataSetChangeWhileSmoothScrollTest.Event.OnPageSelectedEvent
+import androidx.viewpager2.widget.AdapterDataSetChangeWhileSmoothScrollTest.Modification.REMOVE_FIRST_VISIBLE
+import androidx.viewpager2.widget.AdapterDataSetChangeWhileSmoothScrollTest.Modification.SHIFT_FIRST_VISIBLE
+import androidx.viewpager2.widget.AdapterDataSetChangeWhileSmoothScrollTest.Modification.SHIFT_FIRST_VISIBLE_THEN_REMOVE_FIRST
+import androidx.viewpager2.widget.AdapterDataSetChangeWhileSmoothScrollTest.Modification.SHIFT_FIRST_VISIBLE_THEN_REMOVE_FIRST_AND_LAST
+import androidx.viewpager2.widget.AdapterDataSetChangeWhileSmoothScrollTest.Modification.SHIFT_FIRST_VISIBLE_THEN_REMOVE_LAST
+import androidx.viewpager2.widget.AdapterDataSetChangeWhileSmoothScrollTest.TestConfig
+import androidx.viewpager2.widget.ViewPager2.ORIENTATION_HORIZONTAL
+import androidx.viewpager2.widget.ViewPager2.ORIENTATION_VERTICAL
+import androidx.viewpager2.widget.ViewPager2.SCROLL_STATE_IDLE
+import androidx.viewpager2.widget.ViewPager2.SCROLL_STATE_SETTLING
+import org.hamcrest.CoreMatchers.equalTo
+import org.hamcrest.Matchers.greaterThan
+import org.junit.Assert.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import java.util.concurrent.TimeUnit.SECONDS
+import kotlin.math.roundToInt
+
+/** Number of pages */
+private const val pageCount = 25
+/** Page where VP2 starts */
+private const val initialPage = 0
+/** Page where we smooth scroll to */
+private const val targetPage = 20
+
+/** Id of the mark we make when modifying the dataset */
+private const val modificationMark = 1
+
+/** How many pages from x before x gets bound? */
+private const val bindThreshold = 2
+/** Value between 0 and 1/pageSizePx */
+private const val epsilon = 0.00001f
+
+@RunWith(Parameterized::class)
+@LargeTest
+class AdapterDataSetChangeWhileSmoothScrollTest(private val config: TestConfig) : BaseTest() {
+ data class TestConfig(
+ @ViewPager2.Orientation val orientation: Int,
+ val rtl: Boolean,
+ val targetBound: Boolean,
+ val modification: Modification,
+ val adapterProvider: AdapterProviderForItems,
+ val expectedFinalPage: Int,
+ val expectedFinalPageText: String
+ )
+
+ companion object {
+ @JvmStatic
+ @Parameterized.Parameters(name = "{0}")
+ fun spec(): List<TestConfig> = createTestSet()
+ }
+
+ enum class Modification {
+ SHIFT_FIRST_VISIBLE,
+ SHIFT_FIRST_VISIBLE_THEN_REMOVE_FIRST,
+ SHIFT_FIRST_VISIBLE_THEN_REMOVE_LAST,
+ SHIFT_FIRST_VISIBLE_THEN_REMOVE_FIRST_AND_LAST,
+ REMOVE_FIRST_VISIBLE
+ }
+
+ // start and end of the window of opportunity to modify the dataset
+ private val windowStart = targetPage - bindThreshold - if (config.targetBound) 0 else 1
+ private val windowEnd = targetPage - if (config.targetBound) 1 else bindThreshold
+
+ private lateinit var test: Context
+ private lateinit var dataSet: MutableList<String>
+
+ override fun setUp() {
+ super.setUp()
+ if (config.rtl) {
+ localeUtil.resetLocale()
+ localeUtil.setLocale(LocaleTestUtils.RTL_LANGUAGE)
+ }
+
+ test = setUpTest(config.orientation)
+ activityTestRule.runOnUiThread { test.viewPager.offscreenPageLimit = 1 }
+ dataSet = stringSequence(pageCount).toMutableList()
+ test.setAdapterSync(config.adapterProvider(dataSet))
+ }
+
+ @Test
+ fun test() {
+ tryNTimes(3, resetBlock = { test.resetViewPagerTo(initialPage) }) {
+ // when we are scrolling to the target
+ val recorder = test.viewPager.addNewRecordingCallback()
+ val idleLatch = test.viewPager.addWaitForIdleLatch()
+
+ scrollToTargetUntilWindowStart()
+
+ // and we remove the first visible item
+ test.modifyDataSetSync {
+ verifyWindowOfOpportunity(recorder.scrollEvents.last())
+ recorder.markModification()
+ makeModification()
+ }
+ idleLatch.await(10, SECONDS)
+
+ // then
+ test.assertBasicState(config.expectedFinalPage, config.expectedFinalPageText)
+ recorder.apply {
+ val removeItemMarkIx = markerIx(modificationMark)
+ val expectedSelectEvents = if (targetPage == config.expectedFinalPage) {
+ listOf(targetPage)
+ } else {
+ listOf(targetPage, config.expectedFinalPage)
+ }
+ // verify all events
+ assertThat(settlingIx, equalTo(0))
+ assertThat(pageSelectedIx(targetPage), equalTo(1))
+ assertThat(removeItemMarkIx, greaterThan(1))
+ assertThat(idleIx, equalTo(lastIx))
+ assertThat(selectEvents.map { it.position }, equalTo(expectedSelectEvents))
+ assertThat(scrollEventCount, equalTo(eventCount - 3 - expectedSelectEvents.size))
+
+ // verify scroll events _before_ and _after_ the marker
+ val scrollsBeforeMarker = scrollEventsBefore(removeItemMarkIx)
+ val scrollsAfterMarker = scrollEventsAfter(removeItemMarkIx)
+ listOf(scrollsBeforeMarker, scrollsAfterMarker).forEach {
+ it.assertPositionSorted(SortOrder.ASC)
+ it.assertOffsetSorted(SortOrder.ASC)
+ it.assertValueSanity(0, targetPage, test.viewPager.pageSize)
+ }
+ // Only check assertMaxShownPages on scroll events _before_ the marker:
+ // after the data set change, it can scroll an arbitrary number of pages
+ scrollsBeforeMarker.assertMaxShownPages()
+ // Only check assertLastCorrect on scroll events _after_ the marker:
+ // the target is not reached before the data set change
+ scrollsAfterMarker.assertLastCorrect(config.expectedFinalPage)
+ }
+ }
+ }
+
+ private fun scrollToTargetUntilWindowStart() {
+ val latch = test.viewPager
+ .addWaitForDistanceToTarget(targetPage, targetPage - windowStart - epsilon)
+ test.runOnUiThreadSync {
+ test.viewPager.setCurrentItem(targetPage, true)
+ }
+ latch.await(2, SECONDS)
+ }
+
+ private fun verifyWindowOfOpportunity(lastScrollEvent: OnPageScrolledEvent) {
+ val lastScrollPosition = lastScrollEvent.let {
+ it.position + it.positionOffset.toDouble()
+ }
+ if (lastScrollPosition >= windowEnd) {
+ throw RetryException("Data set should be modified while scrolling through " +
+ "($windowStart, $windowEnd), but was modified at $lastScrollPosition")
+ }
+ }
+
+ private fun makeModification() {
+ when (config.modification) {
+ REMOVE_FIRST_VISIBLE -> {
+ removeCurrentPage()
+ }
+ SHIFT_FIRST_VISIBLE -> {
+ shiftCurrentPageToStart()
+ }
+ SHIFT_FIRST_VISIBLE_THEN_REMOVE_FIRST -> {
+ shiftCurrentPageToStart()
+ removeFirstPages()
+ }
+ SHIFT_FIRST_VISIBLE_THEN_REMOVE_LAST -> {
+ shiftCurrentPageToStart()
+ removeLastPages()
+ }
+ SHIFT_FIRST_VISIBLE_THEN_REMOVE_FIRST_AND_LAST -> {
+ shiftCurrentPageToStart()
+ removeLastPages()
+ removeFirstPages()
+ }
+ }
+ }
+
+ private fun shiftCurrentPageToStart() {
+ // Move currently visible position and target apart from each other
+ repeat(test.viewPager.linearLayoutManager.findFirstVisibleItemPosition()) {
+ val item = dataSet.removeAt(0)
+ dataSet.add(targetPage - 1, item)
+ test.viewPager.adapter!!.notifyItemMoved(0, targetPage - 1)
+ }
+ }
+
+ private fun removeCurrentPage() {
+ val position = test.viewPager.linearLayoutManager.findFirstVisibleItemPosition()
+ dataSet.removeAt(position)
+ test.viewPager.adapter!!.notifyItemRemoved(position)
+ }
+
+ private fun removeLastPages() {
+ // Remove last items (including the target)
+ val removeCount = pageCount - targetPage
+ repeat(removeCount) {
+ dataSet.removeAt(targetPage)
+ }
+ test.viewPager.adapter!!.notifyItemRangeRemoved(targetPage, removeCount)
+ }
+
+ private fun removeFirstPages() {
+ // Remove first items (including the first visible item)
+ repeat(2) { dataSet.removeAt(0) }
+ test.viewPager.adapter!!.notifyItemRangeRemoved(0, 2)
+ }
+
+ private fun ViewPager2.addNewRecordingCallback(): RecordingCallback {
+ return RecordingCallback().also { registerOnPageChangeCallback(it) }
+ }
+
+ private sealed class Event {
+ data class OnPageScrolledEvent(
+ val position: Int,
+ val positionOffset: Float,
+ val positionOffsetPixels: Int
+ ) : Event()
+ data class OnPageSelectedEvent(val position: Int) : Event()
+ data class OnPageScrollStateChangedEvent(val state: Int) : Event()
+ data class MarkerEvent(val id: Int) : Event()
+ }
+
+ private class RecordingCallback : ViewPager2.OnPageChangeCallback() {
+ private val events = mutableListOf<Event>()
+
+ val scrollEvents get() = events.mapNotNull { it as? OnPageScrolledEvent }
+ val selectEvents get() = events.mapNotNull { it as? OnPageSelectedEvent }
+ val eventCount get() = events.size
+ val scrollEventCount get() = scrollEvents.size
+ val lastIx get() = events.size - 1
+ val settlingIx get() = events.indexOf(OnPageScrollStateChangedEvent(SCROLL_STATE_SETTLING))
+ val idleIx get() = events.indexOf(OnPageScrollStateChangedEvent(SCROLL_STATE_IDLE))
+ val pageSelectedIx: (page: Int) -> Int = { events.indexOf(OnPageSelectedEvent(it)) }
+ val markerIx: (id: Int) -> Int = { events.indexOf(MarkerEvent(it)) }
+
+ val scrollEventsBefore: (ix: Int) -> List<OnPageScrolledEvent> =
+ { scrollEventsBetween(0, it) }
+ val scrollEventsAfter: (ix: Int) -> List<OnPageScrolledEvent> =
+ { scrollEventsBetween(it + 1, events.size) }
+ val scrollEventsBetween: (fromIx: Int, toIx: Int) -> List<OnPageScrolledEvent> = { a, b ->
+ events.subList(a, b).mapNotNull { it as? OnPageScrolledEvent }
+ }
+
+ override fun onPageScrolled(
+ position: Int,
+ positionOffset: Float,
+ positionOffsetPixels: Int
+ ) {
+ events.add(OnPageScrolledEvent(position, positionOffset, positionOffsetPixels))
+ }
+
+ override fun onPageSelected(position: Int) {
+ events.add(OnPageSelectedEvent(position))
+ }
+
+ override fun onPageScrollStateChanged(state: Int) {
+ events.add(OnPageScrollStateChangedEvent(state))
+ }
+
+ fun markEvent(id: Int) {
+ events.add(MarkerEvent(id))
+ }
+ }
+
+ private fun RecordingCallback.markModification() {
+ markEvent(modificationMark)
+ }
+
+ private fun List<OnPageScrolledEvent>.assertPositionSorted(sortOrder: SortOrder) {
+ map { it.position }.assertSorted { it * sortOrder.sign }
+ }
+
+ private fun List<OnPageScrolledEvent>.assertLastCorrect(targetPage: Int) {
+ last().apply {
+ assertThat(position, equalTo(targetPage))
+ assertThat(positionOffsetPixels, equalTo(0))
+ }
+ }
+
+ private fun List<OnPageScrolledEvent>.assertValueSanity(
+ initialPage: Int,
+ otherPage: Int,
+ pageSize: Int
+ ) = forEach {
+ assertThat(it.position, isBetweenInInMinMax(initialPage, otherPage))
+ assertThat(it.positionOffset, isBetweenInEx(0f, 1f))
+ assertThat((it.positionOffset * pageSize).roundToInt(), equalTo(it.positionOffsetPixels))
+ }
+
+ private fun List<OnPageScrolledEvent>.assertOffsetSorted(sortOrder: SortOrder) {
+ map { it.position + it.positionOffset.toDouble() }.assertSorted { it * sortOrder.sign }
+ }
+
+ private fun List<OnPageScrolledEvent>.assertMaxShownPages() {
+ assertThat(map { it.position }.distinct().size, isBetweenInIn(0, 4))
+ }
+}
+
+// region Test Suite creation
+
+private fun createTestSet(): List<TestConfig> {
+ return listOf(viewAdapterProvider, viewAdapterProviderValueId).flatMap { adapterProvider ->
+ listOf(ORIENTATION_HORIZONTAL, ORIENTATION_VERTICAL).flatMap { orientation ->
+ listOf(false, true).flatMap { rtl ->
+ listOf(
+ TestConfig(
+ orientation, rtl, true,
+ SHIFT_FIRST_VISIBLE,
+ adapterProvider = adapterProvider,
+ expectedFinalPage = 2,
+ expectedFinalPageText = "0"
+ ),
+ TestConfig(
+ orientation, rtl, true,
+ SHIFT_FIRST_VISIBLE_THEN_REMOVE_FIRST,
+ adapterProvider = adapterProvider,
+ expectedFinalPage = targetPage - 1,
+ expectedFinalPageText = "${targetPage + 1}"
+ ),
+ TestConfig(
+ orientation, rtl, true,
+ SHIFT_FIRST_VISIBLE_THEN_REMOVE_LAST,
+ adapterProvider = adapterProvider,
+ expectedFinalPage = 2,
+ expectedFinalPageText = "0"
+ ),
+ TestConfig(
+ orientation, rtl, true,
+ SHIFT_FIRST_VISIBLE_THEN_REMOVE_FIRST_AND_LAST,
+ adapterProvider = adapterProvider,
+ expectedFinalPage = targetPage - 3,
+ expectedFinalPageText = "${targetPage - 3}"
+ ),
+ TestConfig(
+ orientation, rtl, true,
+ REMOVE_FIRST_VISIBLE,
+ adapterProvider = adapterProvider,
+ expectedFinalPage = targetPage - 1,
+ expectedFinalPageText = "$targetPage"
+ ),
+ TestConfig(
+ orientation, rtl, false,
+ SHIFT_FIRST_VISIBLE,
+ adapterProvider = adapterProvider,
+ expectedFinalPage = targetPage,
+ expectedFinalPageText = "$targetPage"
+ ),
+ TestConfig(
+ orientation, rtl, false,
+ SHIFT_FIRST_VISIBLE_THEN_REMOVE_FIRST,
+ adapterProvider = adapterProvider,
+ expectedFinalPage = targetPage,
+ expectedFinalPageText = "${targetPage + 2}"
+ ),
+ TestConfig(
+ orientation, rtl, false,
+ SHIFT_FIRST_VISIBLE_THEN_REMOVE_LAST,
+ adapterProvider = adapterProvider,
+ expectedFinalPage = targetPage - 1,
+ expectedFinalPageText = "${targetPage - 4}"
+ ),
+ TestConfig(
+ orientation, rtl, false,
+ SHIFT_FIRST_VISIBLE_THEN_REMOVE_FIRST_AND_LAST,
+ adapterProvider = adapterProvider,
+ expectedFinalPage = targetPage - 3,
+ expectedFinalPageText = "${targetPage - 4}"
+ ),
+ TestConfig(
+ orientation, rtl, false,
+ REMOVE_FIRST_VISIBLE,
+ adapterProvider = adapterProvider,
+ expectedFinalPage = targetPage,
+ expectedFinalPageText = "${targetPage + 1}"
+ )
+ )
+ }
+ }
+ }
+}
+
+// endregion
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/AdapterTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/AdapterTest.kt
index fc705e5..e696227 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/AdapterTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/AdapterTest.kt
@@ -18,8 +18,6 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
-import androidx.testutils.PollingCheck
-import androidx.testutils.waitForExecution
import androidx.viewpager2.widget.AdapterTest.Event.OnPageScrollStateChangedEvent
import androidx.viewpager2.widget.AdapterTest.Event.OnPageScrolledEvent
import androidx.viewpager2.widget.AdapterTest.Event.OnPageSelectedEvent
@@ -33,7 +31,6 @@
import org.junit.Assert.assertThat
import org.junit.Test
import org.junit.runner.RunWith
-import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit.SECONDS
@LargeTest
@@ -56,7 +53,7 @@
test.assertBasicState(0)
test.viewPager.setCurrentItemSync(1, false, 2, SECONDS)
test.assertBasicState(1)
- activityTestRule.runOnUiThread {
+ test.runOnUiThreadSync {
test.viewPager.adapter = test.viewPager.adapter
}
test.assertBasicState(0)
@@ -211,7 +208,7 @@
private fun clearDataSet() {
assertThat(dataSet.size, greaterThan(0))
- modifyDataSet {
+ test.modifyDataSetSync {
val itemCount = dataSet.size
dataSet.clear()
test.viewPager.adapter!!.notifyItemRangeRemoved(0, itemCount)
@@ -221,35 +218,13 @@
private fun fillDataSet() {
assertThat(dataSet.size, equalTo(0))
- modifyDataSet {
+ test.modifyDataSetSync {
dataSet.addAll(stringSequence(pageCount))
test.viewPager.adapter!!.notifyItemRangeInserted(0, pageCount)
}
test.assertBasicState(0)
}
- private fun modifyDataSet(block: () -> Unit) {
- val layoutChangedLatch = test.viewPager.addWaitForLayoutChangeLatch()
- activityTestRule.runOnUiThread {
- block()
- }
- layoutChangedLatch.await(1, SECONDS)
-
- // Let animations run
- val animationLatch = CountDownLatch(1)
- test.viewPager.recyclerView.itemAnimator!!.isRunning {
- animationLatch.countDown()
- }
- animationLatch.await(1, SECONDS)
-
- // Wait until VP2 has stabilized
- activityTestRule.waitForExecution()
- val adapter = test.viewPager.adapter
- if (adapter != null && adapter.itemCount > 0) {
- PollingCheck.waitFor(1000) { test.viewPager.currentCompletelyVisibleItem != -1 }
- }
- }
-
private fun ViewPager2.addNewRecordingCallback(): RecordingCallback {
return RecordingCallback().also { registerOnPageChangeCallback(it) }
}
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/BaseTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/BaseTest.kt
index 679943f..8185e8b 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/BaseTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/BaseTest.kt
@@ -45,6 +45,7 @@
import androidx.test.rule.ActivityTestRule
import androidx.testutils.LocaleTestUtils
import androidx.testutils.recreate
+import androidx.testutils.waitForExecution
import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.test.R
import androidx.viewpager2.widget.ViewPager2.ORIENTATION_HORIZONTAL
@@ -146,7 +147,20 @@
field = value
}
- fun runOnUiThread(f: () -> Unit) = activity.runOnUiThread(f)
+ fun runOnUiThreadSync(f: () -> Unit) {
+ var thrownError: Throwable? = null
+ activityTestRule.runOnUiThread {
+ try {
+ f()
+ } catch (t: Throwable) {
+ thrownError = t
+ }
+ }
+ val caughtError = thrownError
+ if (caughtError != null) {
+ throw caughtError
+ }
+ }
val viewPager: ViewPager2 get() = activity.findViewById(R.id.view_pager)
@@ -302,7 +316,7 @@
)
var node = AccessibilityNodeInfo.obtain()
- activityTestRule.runOnUiThread { viewPager.onInitializeAccessibilityNodeInfo(node) }
+ runOnUiThreadSync { viewPager.onInitializeAccessibilityNodeInfo(node) }
@Suppress("DEPRECATION") var standardActions = node.actions
assertThat("scroll backward action expected: $expectScrollBackwardAction",
@@ -374,7 +388,7 @@
fun Context.setAdapterSync(adapterProvider: AdapterProvider) {
lateinit var waitForRenderLatch: CountDownLatch
- activityTestRule.runOnUiThread {
+ runOnUiThreadSync {
waitForRenderLatch = viewPager.addWaitForLayoutChangeLatch()
viewPager.adapter = adapterProvider(activity)
}
@@ -443,6 +457,9 @@
return latch
}
+ val ViewPager2.linearLayoutManager: LinearLayoutManager
+ get() = recyclerView.layoutManager as LinearLayoutManager
+
val ViewPager2.recyclerView: RecyclerView
get() {
return getChildAt(0) as RecyclerView
@@ -452,8 +469,7 @@
get() {
var position = RecyclerView.NO_POSITION
activityTestRule.runOnUiThread {
- position = (recyclerView.layoutManager as LinearLayoutManager)
- .findFirstCompletelyVisibleItemPosition()
+ position = linearLayoutManager.findFirstCompletelyVisibleItemPosition()
}
return position
}
@@ -488,6 +504,28 @@
assertPageActions()
}
+ fun Context.resetViewPagerTo(page: Int) {
+ viewPager.setCurrentItemSync(page, false, 2, TimeUnit.SECONDS)
+ // VP2 was potentially settling while the RetryException was raised,
+ // in which case we must wait until the IDLE event has been fired
+ activityTestRule.waitForExecution(1)
+ }
+
+ fun Context.modifyDataSetSync(block: () -> Unit) {
+ val layoutChangedLatch = viewPager.addWaitForLayoutChangeLatch()
+ runOnUiThreadSync {
+ block()
+ }
+ layoutChangedLatch.await(1, TimeUnit.SECONDS)
+
+ // Let animations run
+ val animationLatch = CountDownLatch(1)
+ viewPager.recyclerView.itemAnimator!!.isRunning {
+ animationLatch.countDown()
+ }
+ animationLatch.await(1, TimeUnit.SECONDS)
+ }
+
fun ViewPager2.setCurrentItemSync(
targetPage: Int,
smoothScroll: Boolean,
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/ChangeDataSetWhileScrollingTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/ChangeDataSetWhileScrollingTest.kt
new file mode 100644
index 0000000..861f676
--- /dev/null
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/ChangeDataSetWhileScrollingTest.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.viewpager2.widget
+
+import android.os.SystemClock.sleep
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.testutils.PollingCheck
+import androidx.viewpager2.widget.ViewPager2.ORIENTATION_HORIZONTAL
+import androidx.viewpager2.widget.ViewPager2.SCROLL_STATE_IDLE
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@LargeTest
+class ChangeDataSetWhileScrollingTest : BaseTest() {
+ private val orientation = ORIENTATION_HORIZONTAL
+ private val adapterProvider = viewAdapterProviderValueId
+
+ @Test
+ fun test_regression01() {
+ setUpTest(orientation).apply {
+ val items = listOf("49", "51").toMutableList()
+ setAdapterSync(adapterProvider(items))
+ assertBasicState(0, items[0])
+
+ viewPager.post {
+ viewPager.setCurrentItem(1, true)
+ }
+
+ viewPager.post {
+ items.remove("51")
+ viewPager.adapter!!.notifyDataSetChanged()
+ }
+
+ sleep(200) // introduce some delay, follow-up with pollingCheck
+
+ PollingCheck.waitFor(2000) {
+ viewPager.scrollState == SCROLL_STATE_IDLE && viewPager.currentItem == 0
+ }
+
+ assertBasicState(0, "49")
+ }
+ }
+}
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/DragWhileSmoothScrollTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/DragWhileSmoothScrollTest.kt
index 1282878..ea4bcf4 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/DragWhileSmoothScrollTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/DragWhileSmoothScrollTest.kt
@@ -17,7 +17,6 @@
package androidx.viewpager2.widget
import android.widget.TextView
-import androidx.recyclerview.widget.LinearLayoutManager
import androidx.test.filters.LargeTest
import androidx.test.platform.app.InstrumentationRegistry
import androidx.testutils.SwipeToLocation.flingToCenter
@@ -83,7 +82,7 @@
// when we are close enough
val waitTillCloseEnough = test.viewPager.addWaitForDistanceToTarget(config.targetPage,
config.distanceToTargetWhenStartDrag)
- test.runOnUiThread { test.viewPager.setCurrentItem(config.targetPage, true) }
+ test.runOnUiThreadSync { test.viewPager.setCurrentItem(config.targetPage, true) }
waitTillCloseEnough.await(2, SECONDS)
// then perform a swipe
@@ -158,8 +157,8 @@
// Find the view on the UI thread, as RV may be in layout
val pageText = "$pageToSnapTo"
var viewFound = false
- test.activityTestRule.runOnUiThread {
- val llm = test.viewPager.recyclerView.layoutManager as LinearLayoutManager
+ test.runOnUiThreadSync {
+ val llm = test.viewPager.linearLayoutManager
var i = 0
while (!viewFound && i < llm.childCount) {
val view = llm.getChildAt(i++) as TextView
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FakeDragTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FakeDragTest.kt
index 874da2c..c3614cf 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FakeDragTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FakeDragTest.kt
@@ -28,7 +28,6 @@
import androidx.test.filters.LargeTest
import androidx.test.filters.SdkSuppress
import androidx.testutils.LocaleTestUtils
-import androidx.testutils.waitForExecution
import androidx.viewpager2.widget.BaseTest.Context.SwipeMethod
import androidx.viewpager2.widget.FakeDragTest.Event.OnPageScrollStateChangedEvent
import androidx.viewpager2.widget.FakeDragTest.Event.OnPageScrolledEvent
@@ -322,7 +321,7 @@
@SdkSuppress(minSdkVersion = 16)
fun test_performA11yActionDuringFakeDrag() {
startManualDragDuringFakeDrag(.9f, 1000, interpolator = fastDecelerateInterpolator) {
- activityTestRule.runOnUiThread {
+ test.runOnUiThreadSync {
ViewCompat.performAccessibilityAction(test.viewPager, getNextPageAction(), null)
}
}
@@ -400,22 +399,17 @@
) {
val initialPage = test.viewPager.currentItem
- tryNTimes(3, { /* RESET block */
- test.viewPager.setCurrentItemSync(initialPage, false, 2, SECONDS)
- // VP2 was potentially settling while the RetryException was raised, in which case we
- // must wait until the IDLE event has been fired
- activityTestRule.waitForExecution(1)
- }) { /* TRY block */
+ tryNTimes(3, resetBlock = { test.resetViewPagerTo(initialPage) }) {
val recorder = test.viewPager.addNewRecordingCallback()
// start smooth scroll
val scrollLatch = test.viewPager.addWaitForFirstScrollEventLatch()
- test.runOnUiThread { test.viewPager.setCurrentItem(settleTarget, true) }
+ test.runOnUiThreadSync { test.viewPager.setCurrentItem(settleTarget, true) }
assertThat(scrollLatch.await(2, SECONDS), equalTo(true))
// start fake drag, but check some preconditions first
var idleLatch: CountDownLatch? = null
- activityTestRule.runOnUiThread {
+ test.runOnUiThreadSync {
val dragDistance = dragDistanceCallback()
val currPosition = test.viewPager.relativeScrollPosition
@@ -481,18 +475,14 @@
}
private fun doIllegalAction(errorMessage: String, action: () -> Unit) {
- val executionLatch = CountDownLatch(1)
var exception: IllegalStateException? = null
- test.runOnUiThread {
+ test.runOnUiThreadSync {
try {
action()
} catch (e: IllegalStateException) {
exception = e
- } finally {
- executionLatch.countDown()
}
}
- assertThat(executionLatch.await(1, SECONDS), equalTo(true))
assertThat(exception, notNullValue())
assertThat(exception!!.message, equalTo(errorMessage))
}
@@ -512,12 +502,7 @@
val initialPage = test.viewPager.currentItem
val expectedFinalPage = initialPage + 1
- tryNTimes(3, resetBlock = {
- test.viewPager.setCurrentItemSync(initialPage, false, 2, SECONDS)
- // VP2 was potentially settling while the RetryException was raised,
- // in which case we must wait until the IDLE event has been fired
- activityTestRule.waitForExecution(1)
- }) {
+ tryNTimes(3, resetBlock = { test.resetViewPagerTo(initialPage) }) {
val recorder = test.viewPager.addNewRecordingCallback()
// start fake drag
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FragmentLifecycleTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FragmentLifecycleTest.kt
index 99e6f4e..51f84084 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FragmentLifecycleTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FragmentLifecycleTest.kt
@@ -184,7 +184,7 @@
// wait for layout to finish after data-set change
val latchLayout = viewPager.addWaitForLayoutChangeLatch()
- activityTestRule.runOnUiThread(it.dataChangeAction)
+ runOnUiThreadSync(it.dataChangeAction)
latchLayout.await(timeoutMs, MILLISECONDS)
// wait for animations to finish after data-set change
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/MutableCollectionsTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/MutableCollectionsTest.kt
index 1e98bbf..ee8c165 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/MutableCollectionsTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/MutableCollectionsTest.kt
@@ -110,7 +110,7 @@
}
private fun Context.setCurrentPageContent(newContent: String) {
- runOnUiThread {
+ runOnUiThreadSync {
PageView.setPageText(PageView.findPageInActivity(activity)!!, newContent)
}
}
@@ -144,7 +144,7 @@
val latch = CountDownLatch(1)
viewPager.viewTreeObserver.addOnGlobalLayoutListener { latch.countDown() }
- runOnUiThread { viewPager.adapter!!.notifyDataSetChanged() }
+ runOnUiThreadSync { viewPager.adapter!!.notifyDataSetChanged() }
latch.await(5, TimeUnit.SECONDS)
}
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/OffscreenPageLimitTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/OffscreenPageLimitTest.kt
index 18a3c27..a19d995 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/OffscreenPageLimitTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/OffscreenPageLimitTest.kt
@@ -78,7 +78,7 @@
@LargeTest
fun test() {
test = setUpTest(config.orientation)
- activityTestRule.runOnUiThread {
+ test.runOnUiThreadSync {
test.viewPager.offscreenPageLimit = config.offscreenPageLimit
}
val recorder = test.viewPager.addNewRecordingCallback()
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageChangeCallbackTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageChangeCallbackTest.kt
index 81b6edd..da60cc2 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageChangeCallbackTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageChangeCallbackTest.kt
@@ -26,7 +26,6 @@
import androidx.test.filters.LargeTest
import androidx.testutils.LocaleTestUtils
import androidx.testutils.PollingCheck
-import androidx.testutils.waitForExecution
import androidx.viewpager.widget.ViewPager
import androidx.viewpager2.test.ui.SparseAdapter
import androidx.viewpager2.widget.BaseTest.Context.SwipeMethod
@@ -441,7 +440,7 @@
val latch = viewPager.addWaitForScrolledLatch(targetPages.last(), true)
// when
- runOnUiThread {
+ runOnUiThreadSync {
targetPages.forEach {
viewPager.setCurrentItem(it, true)
}
@@ -493,9 +492,9 @@
val idleLatch = viewPager.addWaitForIdleLatch()
// when
- runOnUiThread { viewPager.setCurrentItem(targetPage, true) }
+ runOnUiThreadSync { viewPager.setCurrentItem(targetPage, true) }
scrollLatch.await(2, SECONDS)
- runOnUiThread {
+ runOnUiThreadSync {
viewPager.setCurrentItem(targetPage, false)
callback.markEvent(marker)
}
@@ -609,7 +608,7 @@
val callback = viewPager.addNewRecordingCallback()
// when
- runOnUiThread { viewPager.setCurrentItem(targetPage, true) }
+ runOnUiThreadSync { viewPager.setCurrentItem(targetPage, true) }
delayCallback(viewPager)
recreateActivity(adapterProvider) { newViewPager ->
@@ -706,8 +705,7 @@
// when
tryNTimes(3, resetBlock = {
- test.viewPager.setCurrentItemSync(currentPage, false, 2, SECONDS)
- activityTestRule.waitForExecution(1)
+ test.resetViewPagerTo(currentPage)
test.viewPager.unregisterOnPageChangeCallback(recorder)
recorder = test.viewPager.addNewRecordingCallback()
}) {
@@ -718,7 +716,7 @@
pageSwiper.swipeForward(halfPage + 2 * touchSlop, AccelerateInterpolator())
settleLatch.await(2, SECONDS)
var scrollLatch: CountDownLatch? = null
- activityTestRule.runOnUiThread {
+ test.runOnUiThreadSync {
scrollLatch = test.viewPager.addWaitForFirstScrollEventLatch()
}
scrollLatch!!.await(2, SECONDS)
@@ -774,7 +772,7 @@
// when
listOf(2, 2, 0, 0, 1, 2, 1, 0).forEach { targetPage ->
- runOnUiThread { viewPager.setCurrentItem(targetPage, smoothScroll) }
+ runOnUiThreadSync { viewPager.setCurrentItem(targetPage, smoothScroll) }
// poll the viewpager on the ui thread
viewPager.waitUntilSnappedOnTargetByPolling(targetPage)
@@ -816,7 +814,7 @@
// Test SCROLL_STATE_SETTLING
test_getScrollState(test, SCROLL_STATE_SETTLING, 1) {
- test.runOnUiThread { test.viewPager.setCurrentItem(1, true) }
+ test.runOnUiThreadSync { test.viewPager.setCurrentItem(1, true) }
}
// Test SCROLL_STATE_DRAGGING (real drag)
@@ -1020,7 +1018,7 @@
val recorder = test.viewPager.addNewRecordingCallback()
val distanceLatch = test.viewPager.addWaitForDistanceToTarget(targetPage, 1.5f)
- test.runOnUiThread {
+ test.runOnUiThreadSync {
test.viewPager.setCurrentItem(targetPage, true)
}
@@ -1058,11 +1056,8 @@
val allEvents get() = events
val scrollEvents get() = events.mapNotNull { it as? OnPageScrolledEvent }
- val scrollEventsBeforeSettling
- get() = events.subList(0, settlingIx).mapNotNull { it as? OnPageScrolledEvent }
- val scrollEventsAfterSettling
- get() = events.subList(settlingIx + 1, events.size)
- .mapNotNull { it as? OnPageScrolledEvent }
+ val scrollEventsBeforeSettling get() = scrollEventsBefore(settlingIx)
+ val scrollEventsAfterSettling get() = scrollEventsAfter(settlingIx)
val selectEvents get() = events.mapNotNull { it as? OnPageSelectedEvent }
val stateEvents get() = events.mapNotNull { it as? OnPageScrollStateChangedEvent }
val scrollAndSelectEvents get() = events.mapNotNull {
@@ -1081,6 +1076,14 @@
val idleIx get() = events.indexOf(OnPageScrollStateChangedEvent(SCROLL_STATE_IDLE))
val pageSelectedIx: (page: Int) -> Int = { events.indexOf(OnPageSelectedEvent(it)) }
+ val scrollEventsBefore: (ix: Int) -> List<OnPageScrolledEvent> =
+ { scrollEventsBetween(0, it) }
+ val scrollEventsAfter: (ix: Int) -> List<OnPageScrolledEvent> =
+ { scrollEventsBetween(it + 1, events.size) }
+ val scrollEventsBetween: (fromIx: Int, toIx: Int) -> List<OnPageScrolledEvent> = { a, b ->
+ events.subList(a, b).mapNotNull { it as? OnPageScrolledEvent }
+ }
+
val wasSettleInterrupted: Boolean get() {
val changeToSettlingEvent = OnPageScrollStateChangedEvent(SCROLL_STATE_SETTLING)
val lastScrollEvent = events
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageFillTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageFillTest.kt
index a14a337..75c233e 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageFillTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageFillTest.kt
@@ -66,7 +66,7 @@
}
setUpTest(ORIENTATION_HORIZONTAL).apply {
- runOnUiThread {
+ runOnUiThreadSync {
viewPager.adapter = fixedViewSizeAdapter
try {
viewPager.measure(0, 0)
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageTransformerItemAnimatorTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageTransformerItemAnimatorTest.kt
new file mode 100644
index 0000000..8dc6458
--- /dev/null
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageTransformerItemAnimatorTest.kt
@@ -0,0 +1,93 @@
+/*
+ * 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.viewpager2.widget
+
+import androidx.recyclerview.widget.DefaultItemAnimator
+import androidx.recyclerview.widget.RecyclerView
+import androidx.recyclerview.widget.RecyclerView.ItemAnimator
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.viewpager2.widget.ViewPager2.ORIENTATION_HORIZONTAL
+import androidx.viewpager2.widget.ViewPager2.PageTransformer
+import org.hamcrest.CoreMatchers.equalTo
+import org.hamcrest.CoreMatchers.not
+import org.hamcrest.CoreMatchers.notNullValue
+import org.hamcrest.CoreMatchers.nullValue
+import org.junit.Assert.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests that setting a transformer disables data-set change animations, and that those are restored
+ * when a transformer is removed.
+ */
+@RunWith(AndroidJUnit4::class)
+@LargeTest
+class PageTransformerItemAnimatorTest : BaseTest() {
+
+ @Test
+ fun test() {
+ setUpTest(ORIENTATION_HORIZONTAL).apply {
+ setAdapterSync(viewAdapterProviderValueId(stringSequence(5)))
+ assertBasicState(0)
+
+ val rv = viewPager.getChildAt(0) as RecyclerView
+ val animatorDefault = rv.itemAnimator as ItemAnimator
+ val animatorCustom = object : DefaultItemAnimator() {} as ItemAnimator
+ assertThat(animatorDefault, notNullValue())
+ assertThat(animatorDefault, not(equalTo(animatorCustom)))
+
+ val transformer1 = PageTransformer { _, _ -> }
+ val transformer2 = PageTransformer { _, _ -> }
+ val transformer3 = PageTransformer { _, _ -> }
+
+ runOnUiThreadSync {
+ assertThat(rv.itemAnimator, equalTo(animatorDefault))
+ viewPager.setPageTransformer(MarginPageTransformer(50))
+ assertThat(rv.itemAnimator, nullValue())
+
+ viewPager.setPageTransformer(transformer1)
+ assertThat(rv.itemAnimator, nullValue())
+
+ viewPager.setPageTransformer(null)
+ assertThat(rv.itemAnimator, equalTo(animatorDefault))
+
+ rv.itemAnimator = animatorCustom
+ viewPager.setPageTransformer(transformer2)
+ assertThat(rv.itemAnimator, nullValue())
+
+ viewPager.setPageTransformer(MarginPageTransformer(100))
+ assertThat(rv.itemAnimator, nullValue())
+
+ viewPager.setPageTransformer(CompositePageTransformer())
+ assertThat(rv.itemAnimator, nullValue())
+
+ viewPager.setPageTransformer(null)
+ assertThat(rv.itemAnimator, equalTo(animatorCustom))
+
+ viewPager.setPageTransformer(transformer3)
+ assertThat(rv.itemAnimator, nullValue())
+
+ viewPager.setPageTransformer(null)
+ assertThat(rv.itemAnimator, equalTo(animatorCustom))
+
+ viewPager.setPageTransformer(null)
+ assertThat(rv.itemAnimator, equalTo(animatorCustom))
+ }
+ }
+ }
+}
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageTransformerTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageTransformerTest.kt
index 5981d6b..4d78da2 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageTransformerTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageTransformerTest.kt
@@ -180,8 +180,7 @@
}
private fun ViewPager2.addNewRecordingCallback(): RecordingCallback {
- val layoutManager = recyclerView.layoutManager as LinearLayoutManager
- return RecordingCallback(layoutManager).also {
+ return RecordingCallback(linearLayoutManager).also {
setPageTransformer(it)
registerOnPageChangeCallback(it)
}
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/SetItemWhileScrollInProgressTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/SetItemWhileScrollInProgressTest.kt
index 3f4e84f..8d6159b 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/SetItemWhileScrollInProgressTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/SetItemWhileScrollInProgressTest.kt
@@ -144,7 +144,7 @@
// when
pageSequence.forEachIndexed { i, targetPage ->
- runOnUiThread {
+ runOnUiThreadSync {
viewPager.setCurrentItem(targetPage, i !in instantScrolls)
viewPager.assertCurrentItemSet(targetPage)
if (currentPage != targetPage) {
@@ -345,6 +345,13 @@
totalPages = 12,
pageSequence = listOf(8, 7, 9, 7, 3, 0, 7, 11, 10, 0),
instantScrolls = setOf(1, 4, 5, 8, 9)
+ ),
+ TestConfig(
+ title = "smooth-instant-long_smooth",
+ orientation = orientation,
+ totalPages = 5,
+ pageSequence = listOf(3, 4, 0),
+ instantScrolls = setOf(1)
)
)
.plus(
@@ -396,4 +403,4 @@
return (0 until seqLen).filter { r.nextFloat() < probability }.toSet()
}
-// endregion
\ No newline at end of file
+// endregion
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/SwipeTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/SwipeTest.kt
index bc81524..9cacdcd 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/SwipeTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/SwipeTest.kt
@@ -51,7 +51,7 @@
val modifiedPageValue: String? = stepToNewValue[currentStep]
if (modifiedPageValue != null) {
expectedValues[currentPage] = modifiedPageValue
- runOnUiThread {
+ runOnUiThreadSync {
PageView.setPageText(PageView.findPageInActivity(activity)!!,
modifiedPageValue)
}
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/TransientStateFragmentTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/TransientStateFragmentTest.kt
new file mode 100644
index 0000000..ebe5a6a
--- /dev/null
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/TransientStateFragmentTest.kt
@@ -0,0 +1,80 @@
+/*
+ * 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.viewpager2.widget
+
+import android.os.Bundle
+import android.view.View
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
+import androidx.viewpager2.widget.ViewPager2.ORIENTATION_HORIZONTAL
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.util.concurrent.TimeUnit.MILLISECONDS
+
+/**
+ * Verifies that [androidx.viewpager2.adapter.FragmentStateAdapter] can handle [Fragment]s
+ * having transient state.
+ */
+@RunWith(AndroidJUnit4::class)
+@LargeTest
+class TransientStateFragmentTest : BaseTest() {
+ private val orientation = ORIENTATION_HORIZONTAL
+ private val totalPages = 10
+ private val adapterProvider = fragmentAdapterProviderValueId
+ private val timeoutMs = 3000L
+
+ @Test
+ @SdkSuppress(minSdkVersion = 16) /** [View.setHasTransientState] was introduced in API 16 */
+ fun test_swipeBetweenPages() {
+ setUpTest(orientation).apply {
+ val expectedValues = stringSequence(totalPages)
+ val adapter = adapterProvider(expectedValues)
+
+ val fragmentManager = activity.supportFragmentManager
+
+ val transientStateCallback = createTransientStateCallback()
+ fragmentManager.registerFragmentLifecycleCallbacks(transientStateCallback, false)
+ setAdapterSync(adapter)
+
+ assertBasicState(0)
+ listOf(1, 0, 1, 2, 3, 4, 3).plus(4 until totalPages).forEach { target ->
+ val latch = viewPager.addWaitForIdleLatch()
+ swipe(viewPager.currentItem, target)
+ latch.await(timeoutMs, MILLISECONDS)
+ assertBasicState(target)
+ }
+
+ fragmentManager.unregisterFragmentLifecycleCallbacks(transientStateCallback)
+ }
+ }
+
+ private fun createTransientStateCallback(): FragmentManager.FragmentLifecycleCallbacks {
+ return object : FragmentManager.FragmentLifecycleCallbacks() {
+ override fun onFragmentViewCreated(
+ fm: FragmentManager,
+ f: Fragment,
+ v: View,
+ savedInstanceState: Bundle?
+ ) {
+ v.setHasTransientState(true)
+ }
+ }
+ }
+}
diff --git a/viewpager2/src/androidTest/res/layout/item_test_layout.xml b/viewpager2/src/androidTest/res/layout/item_test_layout.xml
index 74a0e03..ab051e4 100644
--- a/viewpager2/src/androidTest/res/layout/item_test_layout.xml
+++ b/viewpager2/src/androidTest/res/layout/item_test_layout.xml
@@ -20,4 +20,5 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textColor="@color/primary_text_default_material_light"
+ android:textSize="40sp"
android:gravity="center"/>
diff --git a/viewpager2/src/main/java/androidx/viewpager2/adapter/FragmentStateAdapter.java b/viewpager2/src/main/java/androidx/viewpager2/adapter/FragmentStateAdapter.java
index 435762c..53ddc92 100644
--- a/viewpager2/src/main/java/androidx/viewpager2/adapter/FragmentStateAdapter.java
+++ b/viewpager2/src/main/java/androidx/viewpager2/adapter/FragmentStateAdapter.java
@@ -60,8 +60,7 @@
* position. If we already have the fragment, or have previously saved its state, we use those.
* <li>{@link RecyclerView.Adapter#onAttachedToWindow} we attach the {@link Fragment} to a
* container.
- * <li>{@link RecyclerView.Adapter#onViewRecycled} and
- * {@link RecyclerView.Adapter#onFailedToRecycleView} we remove, save state, destroy the
+ * <li>{@link RecyclerView.Adapter#onViewRecycled} we remove, save state, destroy the
* {@link Fragment}.
* </ul>
*/
@@ -395,12 +394,20 @@
@Override
public final boolean onFailedToRecycleView(@NonNull FragmentViewHolder holder) {
- // This happens when a ViewHolder is in a transient state (e.g. during custom
- // animation). We don't have sufficient information on how to clear up what lead to
- // the transient state, so we are throwing away the ViewHolder to stay on the
- // conservative side.
- onViewRecycled(holder); // the same clean-up steps as when recycling a ViewHolder
- return false; // don't recycle the view
+ /*
+ This happens when a ViewHolder is in a transient state (e.g. during an
+ animation).
+
+ Our ViewHolders are effectively just FrameLayout instances in which we put Fragment
+ Views, so it's safe to force recycle them. This is because:
+ - FrameLayout instances are not to be directly manipulated, so no animations are
+ expected to be running directly on them.
+ - Fragment Views are not reused between position (one Fragment = one page). Animation
+ running in one of the Fragment Views won't affect another Fragment View.
+ - If a user chooses to violate these assumptions, they are also in the position to
+ correct the state in their code.
+ */
+ return true;
}
private void removeFragment(long itemId) {
diff --git a/viewpager2/src/main/java/androidx/viewpager2/widget/ScrollEventAdapter.java b/viewpager2/src/main/java/androidx/viewpager2/widget/ScrollEventAdapter.java
index f5e9736..456786f 100644
--- a/viewpager2/src/main/java/androidx/viewpager2/widget/ScrollEventAdapter.java
+++ b/viewpager2/src/main/java/androidx/viewpager2/widget/ScrollEventAdapter.java
@@ -78,6 +78,7 @@
private int mTarget;
private boolean mDispatchSelected;
private boolean mScrollHappened;
+ private boolean mDataSetChangeHappened;
private boolean mFakeDragging;
ScrollEventAdapter(@NonNull ViewPager2 viewPager) {
@@ -96,6 +97,7 @@
mDispatchSelected = false;
mScrollHappened = false;
mFakeDragging = false;
+ mDataSetChangeHappened = false;
}
/**
@@ -156,6 +158,19 @@
resetState();
}
}
+
+ if (mAdapterState == STATE_IN_PROGRESS_SMOOTH_SCROLL
+ && newState == RecyclerView.SCROLL_STATE_IDLE && mDataSetChangeHappened) {
+ updateScrollEventValues();
+ if (mScrollValues.mOffsetPx == 0) {
+ if (mTarget != mScrollValues.mPosition) {
+ dispatchSelected(
+ mScrollValues.mPosition == NO_POSITION ? 0 : mScrollValues.mPosition);
+ }
+ dispatchStateChanged(SCROLL_STATE_IDLE);
+ resetState();
+ }
+ }
}
/**
@@ -279,6 +294,10 @@
dispatchStateChanged(SCROLL_STATE_DRAGGING);
}
+ void notifyDataSetChangeHappened() {
+ mDataSetChangeHappened = true;
+ }
+
/**
* Let the adapter know a programmatic scroll was initiated.
*/
diff --git a/viewpager2/src/main/java/androidx/viewpager2/widget/ViewPager2.java b/viewpager2/src/main/java/androidx/viewpager2/widget/ViewPager2.java
index 5b9f51e..d0aee32 100644
--- a/viewpager2/src/main/java/androidx/viewpager2/widget/ViewPager2.java
+++ b/viewpager2/src/main/java/androidx/viewpager2/widget/ViewPager2.java
@@ -134,6 +134,7 @@
@Override
public void onChanged() {
mCurrentItemDirty = true;
+ mScrollEventAdapter.notifyDataSetChangeHappened();
}
};
@@ -142,10 +143,12 @@
private Parcelable mPendingAdapterState;
private RecyclerView mRecyclerView;
private PagerSnapHelper mPagerSnapHelper;
- private ScrollEventAdapter mScrollEventAdapter;
+ ScrollEventAdapter mScrollEventAdapter;
private CompositeOnPageChangeCallback mPageChangeEventDispatcher;
private FakeDrag mFakeDragger;
private PageTransformerAdapter mPageTransformerAdapter;
+ private RecyclerView.ItemAnimator mSavedItemAnimator = null;
+ private boolean mSavedItemAnimatorPresent = false;
private boolean mUserInputEnabled = true;
private @OffscreenPageLimit int mOffscreenPageLimit = OFFSCREEN_PAGE_LIMIT_DEFAULT;
AccessibilityProvider mAccessibilityProvider; // to avoid creation of a synthetic accessor
@@ -512,17 +515,7 @@
mTmpChildRect.bottom);
if (mCurrentItemDirty) {
- RecyclerView.ItemAnimator animator = mRecyclerView.getItemAnimator();
- if (animator == null) {
- updateCurrentItem();
- } else {
- animator.isRunning(new RecyclerView.ItemAnimator.ItemAnimatorFinishedListener() {
- @Override
- public void onAnimationsFinished() {
- updateCurrentItem();
- }
- });
- }
+ updateCurrentItem();
}
}
@@ -869,6 +862,10 @@
* Sets a {@link PageTransformer} that will be called for each attached page whenever the
* scroll position is changed. This allows the application to apply custom property
* transformations to each page, overriding the default sliding behavior.
+ * <p>
+ * Note: setting a {@link PageTransformer} disables data-set change animations to prevent
+ * conflicts between the two animation systems. Setting a {@code null} transformer will restore
+ * data-set change animations.
*
* @param transformer PageTransformer that will modify each page's animation properties
*
@@ -876,6 +873,20 @@
* @see CompositePageTransformer
*/
public void setPageTransformer(@Nullable PageTransformer transformer) {
+ if (transformer != null) {
+ if (!mSavedItemAnimatorPresent) {
+ mSavedItemAnimator = mRecyclerView.getItemAnimator();
+ mSavedItemAnimatorPresent = true;
+ }
+ mRecyclerView.setItemAnimator(null);
+ } else {
+ if (mSavedItemAnimatorPresent) {
+ mRecyclerView.setItemAnimator(mSavedItemAnimator);
+ mSavedItemAnimator = null;
+ mSavedItemAnimatorPresent = false;
+ }
+ }
+
// TODO: add support for reverseDrawingOrder: b/112892792
// TODO: add support for pageLayerType: b/112893074
if (transformer == mPageTransformerAdapter.getPageTransformer()) {
diff --git a/webkit/integration-tests/testapp/src/main/AndroidManifest.xml b/webkit/integration-tests/testapp/src/main/AndroidManifest.xml
index b999b64..c827c61 100644
--- a/webkit/integration-tests/testapp/src/main/AndroidManifest.xml
+++ b/webkit/integration-tests/testapp/src/main/AndroidManifest.xml
@@ -83,6 +83,9 @@
android:name=".AssetLoaderAjaxActivity"
android:exported="true" />
<activity
+ android:name=".AssetLoaderInternalStorageActivity"
+ android:exported="true" />
+ <activity
android:name=".ForceDarkActivity"
android:theme="@style/AppDayNightTheme"
android:exported="true" />
diff --git a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/AssetLoaderInternalStorageActivity.java b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/AssetLoaderInternalStorageActivity.java
new file mode 100644
index 0000000..a62d043
--- /dev/null
+++ b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/AssetLoaderInternalStorageActivity.java
@@ -0,0 +1,137 @@
+/*
+ * 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 com.example.androidx.webkit;
+
+import android.app.Activity;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.webkit.WebResourceRequest;
+import android.webkit.WebResourceResponse;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.webkit.WebViewAssetLoader;
+import androidx.webkit.WebViewAssetLoader.InternalStoragePathHandler;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * An {@link Activity} to show case a use case of using {@link InternalStoragePathHandler}.
+ */
+public class AssetLoaderInternalStorageActivity extends AppCompatActivity {
+ private static final String DEMO_HTML_CONTENT =
+ "<h3>Successfully loaded html from app files dir!</h3>";
+
+ @NonNull private File mPublicDir;
+ @NonNull private File mDemoFile;
+
+ @NonNull private WebViewAssetLoader mAssetLoader;
+ @NonNull private WebView mWebView;
+
+ private class MyWebViewClient extends WebViewClient {
+ @Override
+ public boolean shouldOverrideUrlLoading(WebView view, String url) {
+ return false;
+ }
+
+ @Override
+ @RequiresApi(21)
+ public WebResourceResponse shouldInterceptRequest(WebView view,
+ WebResourceRequest request) {
+ return mAssetLoader.shouldInterceptRequest(request.getUrl());
+ }
+
+ @Override
+ public WebResourceResponse shouldInterceptRequest(WebView view, String request) {
+ return mAssetLoader.shouldInterceptRequest(Uri.parse(request));
+ }
+ }
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.activity_asset_loader);
+ setTitle(R.string.asset_loader_internal_storage_activity_title);
+ WebkitHelpers.appendWebViewVersionToTitle(this);
+
+ mWebView = findViewById(R.id.webview_asset_loader_webview);
+ mWebView.setWebViewClient(new MyWebViewClient());
+
+ mPublicDir = new File(getFilesDir(), "public");
+ mDemoFile = new File(mPublicDir, "some_text.html");
+
+ // Host "files/public/" in app's data directory under:
+ // http://appassets.androidplatform.net/public_data/...
+ mAssetLoader = new WebViewAssetLoader.Builder()
+ .addPathHandler("/public_data/", new InternalStoragePathHandler(this, mPublicDir))
+ .build();
+
+ // Write the demo file asynchronously and then load the file after it's written.
+ new WriteFileTask(mDemoFile, DEMO_HTML_CONTENT) {
+ @Override
+ protected void onPostExecute(Void result) {
+ Uri path = new Uri.Builder()
+ .scheme("https")
+ .authority(WebViewAssetLoader.DEFAULT_DOMAIN)
+ .appendPath("public_data")
+ .appendPath("some_text.html")
+ .build();
+
+ mWebView.loadUrl(path.toString());
+ }
+ }.execute();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ // Clean the test/demo file for tests.
+ mDemoFile.delete();
+ mPublicDir.delete();
+ }
+
+ // Writes to file asynchronously in the background thread.
+ private class WriteFileTask extends AsyncTask<Void, Void, Void> {
+ @NonNull private final File mFile;
+ @NonNull private final String mContent;
+
+ WriteFileTask(@NonNull File file, @NonNull String content) {
+ mFile = file;
+ mContent = content;
+ }
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ mFile.getParentFile().mkdirs();
+ try (FileOutputStream fos = new FileOutputStream(mFile)) {
+ fos.write(mContent.getBytes("utf-8"));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ return null;
+ }
+
+ }
+}
diff --git a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/AssetLoaderListActivity.java b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/AssetLoaderListActivity.java
index 24628b15..e07539e 100644
--- a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/AssetLoaderListActivity.java
+++ b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/AssetLoaderListActivity.java
@@ -49,6 +49,9 @@
new MenuListView.MenuItem(
getResources().getString(R.string.asset_loader_ajax_activity_title),
new Intent(activityContext, AssetLoaderAjaxActivity.class)),
+ new MenuListView.MenuItem(
+ getResources().getString(R.string.asset_loader_internal_storage_activity_title),
+ new Intent(activityContext, AssetLoaderInternalStorageActivity.class)),
};
listView.setItems(menuItems);
}
diff --git a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/ForceDarkActivity.java b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/ForceDarkActivity.java
index c2c6aea..aa8531b 100644
--- a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/ForceDarkActivity.java
+++ b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/ForceDarkActivity.java
@@ -16,7 +16,6 @@
package com.example.androidx.webkit;
-import android.annotation.SuppressLint;
import android.app.Activity;
import android.os.Bundle;
import android.util.Base64;
@@ -42,7 +41,6 @@
"<html><body><h1>Force Dark Mode is %s </h1></body></html>";
@Override
- @SuppressLint("RestrictedApi")
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -62,7 +60,6 @@
}
}
- @SuppressLint("RestrictedApi")
private void setupWebView(WebView webView, int forceDarkMode) {
webView.setWebViewClient(new WebViewClient());
String formatedDescription;
diff --git a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/MultiProcessEnabledActivity.java b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/MultiProcessEnabledActivity.java
index 9a13e2f..ae365cd 100644
--- a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/MultiProcessEnabledActivity.java
+++ b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/MultiProcessEnabledActivity.java
@@ -16,7 +16,6 @@
package com.example.androidx.webkit;
-import android.annotation.SuppressLint;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
@@ -29,7 +28,6 @@
public class MultiProcessEnabledActivity extends AppCompatActivity {
@Override
- @SuppressLint("RestrictedApi")
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_is_multi_process_enabled);
diff --git a/webkit/integration-tests/testapp/src/main/res/values/strings.xml b/webkit/integration-tests/testapp/src/main/res/values/strings.xml
index 881793a..13c5706 100644
--- a/webkit/integration-tests/testapp/src/main/res/values/strings.xml
+++ b/webkit/integration-tests/testapp/src/main/res/values/strings.xml
@@ -22,6 +22,7 @@
<string name="not_updateable_webview">not updatable</string>
<string name="proxy_override_requests_served">Requests served: %d</string>
<string name="asset_loader_ajax_activity_title">Asset Loader AJAX Demo</string>
+ <string name="asset_loader_internal_storage_activity_title">Load from internal storage</string>
<string name="asset_loader_list_activity_title">WebView Asset Loader Demos</string>
<string name="asset_loader_simple_activity_title">Simple Asset Loader</string>
<string name="small_interstitial_activity_title">Small Interstitial</string>
diff --git a/webkit/src/androidTest/java/androidx/webkit/WebViewAssetLoaderIntegrationTest.java b/webkit/src/androidTest/java/androidx/webkit/WebViewAssetLoaderIntegrationTest.java
index 70a2b11..06a7a6f 100644
--- a/webkit/src/androidTest/java/androidx/webkit/WebViewAssetLoaderIntegrationTest.java
+++ b/webkit/src/androidTest/java/androidx/webkit/WebViewAssetLoaderIntegrationTest.java
@@ -17,8 +17,10 @@
package androidx.webkit;
import static androidx.webkit.WebViewAssetLoader.AssetsPathHandler;
+import static androidx.webkit.WebViewAssetLoader.InternalStoragePathHandler;
import static androidx.webkit.WebViewAssetLoader.ResourcesPathHandler;
+import android.content.Context;;
import android.net.Uri;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
@@ -27,6 +29,7 @@
import androidx.test.filters.MediumTest;
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
+import androidx.webkit.internal.AssetHelper;
import org.junit.After;
import org.junit.Assert;
@@ -35,6 +38,8 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.File;
+
@RunWith(AndroidJUnit4.class)
public class WebViewAssetLoaderIntegrationTest {
private static final String TAG = "WebViewAssetLoaderIntegrationTest";
@@ -43,6 +48,12 @@
public final ActivityTestRule<WebViewTestActivity> mActivityRule =
new ActivityTestRule<>(WebViewTestActivity.class);
+ private static final String TEST_INTERNAL_STORAGE_DIR = "app_public/";
+ private static final String TEST_INTERNAL_STORAGE_FILE =
+ TEST_INTERNAL_STORAGE_DIR + "html/test_with_title.html";
+ private static final String TEST_HTML_CONTENT =
+ "<head><title>WebViewAssetLoaderTest</title></head>";
+
private WebViewOnUiThread mOnUiThread;
private static class AssetLoadingWebViewClient extends WebViewOnUiThread.WaitForLoadedClient {
@@ -76,6 +87,10 @@
if (mOnUiThread != null) {
mOnUiThread.cleanUp();
}
+
+ Context context = mActivityRule.getActivity();
+ WebkitUtils.recursivelyDeleteFile(
+ new File(AssetHelper.getDataDir(context), TEST_INTERNAL_STORAGE_DIR));
}
@Test
@@ -125,4 +140,34 @@
Assert.assertEquals("WebViewAssetLoaderTest", mOnUiThread.getTitle());
}
+
+ @Test
+ @MediumTest
+ public void testAppInternalStorageHosting() throws Exception {
+ final WebViewTestActivity activity = mActivityRule.getActivity();
+
+ File dataDir = AssetHelper.getDataDir(activity);
+ WebkitUtils.writeToFile(new File(dataDir, TEST_INTERNAL_STORAGE_FILE), TEST_HTML_CONTENT);
+
+ InternalStoragePathHandler handler = new InternalStoragePathHandler(activity,
+ new File(dataDir, TEST_INTERNAL_STORAGE_DIR));
+ WebViewAssetLoader assetLoader = new WebViewAssetLoader.Builder()
+ .addPathHandler("/data/public/", handler)
+ .build();
+
+ mOnUiThread.setWebViewClient(new AssetLoadingWebViewClient(mOnUiThread, assetLoader));
+
+ String url = new Uri.Builder()
+ .scheme("https")
+ .authority(WebViewAssetLoader.DEFAULT_DOMAIN)
+ .appendPath("data")
+ .appendPath("public")
+ .appendPath("html")
+ .appendPath("test_with_title.html")
+ .build()
+ .toString();
+ mOnUiThread.loadUrlAndWaitForCompletion(url);
+
+ Assert.assertEquals("WebViewAssetLoaderTest", mOnUiThread.getTitle());
+ }
}
diff --git a/webkit/src/androidTest/java/androidx/webkit/WebViewAssetLoaderTest.java b/webkit/src/androidTest/java/androidx/webkit/WebViewAssetLoaderTest.java
index 029425a..f5a667b 100644
--- a/webkit/src/androidTest/java/androidx/webkit/WebViewAssetLoaderTest.java
+++ b/webkit/src/androidTest/java/androidx/webkit/WebViewAssetLoaderTest.java
@@ -16,11 +16,13 @@
package androidx.webkit;
+import android.content.Context;
import android.content.ContextWrapper;
import android.net.Uri;
import android.webkit.WebResourceResponse;
import static androidx.webkit.WebViewAssetLoader.AssetsPathHandler;
+import static androidx.webkit.WebViewAssetLoader.InternalStoragePathHandler;
import static androidx.webkit.WebViewAssetLoader.PathHandler;
import static androidx.webkit.WebViewAssetLoader.ResourcesPathHandler;
@@ -37,6 +39,7 @@
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
+import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
@@ -215,6 +218,84 @@
assertResponse(response, testHtmlContents);
}
+ @SmallTest
+ @Test(expected = IllegalArgumentException.class)
+ public void testCreateInternalStorageHandler_entireDataDir() throws Throwable {
+ Context context = ApplicationProvider.getApplicationContext();
+ File testDir = AssetHelper.getDataDir(context);
+ PathHandler handler =
+ new InternalStoragePathHandler(context, testDir);
+ }
+
+ @SmallTest
+ @Test(expected = IllegalArgumentException.class)
+ public void testCreateInternalStorageHandler_entireCacheDir() throws Throwable {
+ Context context = ApplicationProvider.getApplicationContext();
+ File testDir = context.getCacheDir();
+ PathHandler handler =
+ new InternalStoragePathHandler(context, testDir);
+ }
+
+ @SmallTest
+ @Test(expected = IllegalArgumentException.class)
+ public void testCreateInternalStorageHandler_databasesDir() throws Throwable {
+ Context context = ApplicationProvider.getApplicationContext();
+ File testDir = new File(AssetHelper.getDataDir(context), "databases/");
+ PathHandler handler =
+ new InternalStoragePathHandler(context, testDir);
+ }
+
+ @SmallTest
+ @Test(expected = IllegalArgumentException.class)
+ public void testCreateInternalStorageHandler_libDir() throws Throwable {
+ Context context = ApplicationProvider.getApplicationContext();
+ File testDir = new File(AssetHelper.getDataDir(context), "lib/");
+ PathHandler handler =
+ new InternalStoragePathHandler(context, testDir);
+ }
+
+ @SmallTest
+ @Test(expected = IllegalArgumentException.class)
+ public void testCreateInternalStorageHandler_webViewDir() throws Throwable {
+ Context context = ApplicationProvider.getApplicationContext();
+ File testDir = new File(AssetHelper.getDataDir(context), "app_webview");
+ PathHandler handler =
+ new InternalStoragePathHandler(context, testDir);
+ }
+
+ @SmallTest
+ @Test(expected = IllegalArgumentException.class)
+ public void testCreateInternalStorageHandler_sharedPrefsDir() throws Throwable {
+ Context context = ApplicationProvider.getApplicationContext();
+ File testDir = new File(AssetHelper.getDataDir(context), "/shared_prefs/");
+ PathHandler handler =
+ new InternalStoragePathHandler(context, testDir);
+ }
+
+ @Test
+ @SmallTest
+ public void testHostInternalStorageHandler_invalidAccess() throws Throwable {
+ Context context = ApplicationProvider.getApplicationContext();
+ File testDir = new File(AssetHelper.getDataDir(context), "/public/");
+ PathHandler handler =
+ new InternalStoragePathHandler(context, testDir);
+ WebViewAssetLoader assetLoader = new WebViewAssetLoader.Builder()
+ .addPathHandler("/public-data/", handler)
+ .build();
+
+ WebResourceResponse response = assetLoader.shouldInterceptRequest(
+ Uri.parse("https://appassets.androidplatform.net/public-data/../test.html"));
+ Assert.assertNull(
+ "should be null since it tries to access a file outside the mounted directory",
+ response.getData());
+
+ response = assetLoader.shouldInterceptRequest(
+ Uri.parse("https://appassets.androidplatform.net/public-data/html/test.html"));
+ Assert.assertNull(
+ "should be null as it accesses a non-existent file under the mounted directory",
+ response.getData());
+ }
+
@Test
@SmallTest
public void testMultiplePathHandlers() throws Throwable {
@@ -314,6 +395,48 @@
assertResponse(response, FakeTextPathHandler.CONTENTS);
}
+ @Test
+ @SmallTest
+ public void testMimeTypeInPathHandlers() throws Throwable {
+ final String testHtmlContents = "<body><div>test</div></body>";
+
+ AssetHelper mockAssetHelper = new MockAssetHelper() {
+ @Override
+ public InputStream openResource(Uri uri) {
+ try {
+ return new ByteArrayInputStream(testHtmlContents.getBytes(ENCODING));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ };
+
+ WebViewAssetLoader assetLoader = new WebViewAssetLoader.Builder()
+ .addPathHandler("/assets/", new AssetsPathHandler(mockAssetHelper))
+ .addPathHandler("/res/", new ResourcesPathHandler(mockAssetHelper))
+ .build();
+
+ WebResourceResponse response = assetLoader.shouldInterceptRequest(
+ Uri.parse("https://appassets.androidplatform.net/res/raw/test"));
+ Assert.assertEquals("File doesn't have an extension, MIME type should be text/plain",
+ AssetHelper.DEFAULT_MIME_TYPE, response.getMimeType());
+
+ response = assetLoader.shouldInterceptRequest(
+ Uri.parse("https://appassets.androidplatform.net/assets/other/test"));
+ Assert.assertEquals("File doesn't have an extension, MIME type should be text/plain",
+ AssetHelper.DEFAULT_MIME_TYPE, response.getMimeType());
+
+ response = assetLoader.shouldInterceptRequest(
+ Uri.parse("https://appassets.androidplatform.net/res/drawable/test.png"));
+ Assert.assertEquals(".png file should have mime type image/png regardless of its content",
+ "image/png", response.getMimeType());
+
+ response = assetLoader.shouldInterceptRequest(
+ Uri.parse("https://appassets.androidplatform.net/assets/images/test.png"));
+ Assert.assertEquals(".png file should have mime type image/png regardless of its content",
+ "image/png", response.getMimeType());
+ }
+
private static void assertResponse(@Nullable WebResourceResponse response,
@NonNull String expectedContent) throws IOException {
Assert.assertNotNull("failed to match the URL and returned null response", response);
diff --git a/webkit/src/androidTest/java/androidx/webkit/WebkitUtils.java b/webkit/src/androidTest/java/androidx/webkit/WebkitUtils.java
index 28d0633..a7994fc 100644
--- a/webkit/src/androidTest/java/androidx/webkit/WebkitUtils.java
+++ b/webkit/src/androidTest/java/androidx/webkit/WebkitUtils.java
@@ -25,6 +25,9 @@
import org.junit.Assume;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
@@ -211,6 +214,42 @@
}
}
+ /**
+ * Write a string to a file, and create the whole parent directories if they don't exist.
+ */
+ public static void writeToFile(File file, String content)
+ throws IOException {
+ file.getParentFile().mkdirs();
+ FileOutputStream fos = new FileOutputStream(file);
+ try {
+ fos.write(content.getBytes("utf-8"));
+ } finally {
+ fos.close();
+ }
+ }
+
+ /**
+ * Delete the given File and (if it's a directory) everything within it.
+ * @param currentFile The file or directory to delete. Does not need to exist.
+ * @return Whether currentFile does not exist afterwards.
+ */
+ public static boolean recursivelyDeleteFile(File currentFile) {
+ if (!currentFile.exists()) {
+ return true;
+ }
+ if (currentFile.isDirectory()) {
+ File[] files = currentFile.listFiles();
+ if (files != null) {
+ for (File file : files) {
+ recursivelyDeleteFile(file);
+ }
+ }
+ }
+
+ boolean ret = currentFile.delete();
+ return ret;
+ }
+
// Do not instantiate this class.
private WebkitUtils() {}
}
diff --git a/webkit/src/androidTest/java/androidx/webkit/internal/AssetHelperTest.java b/webkit/src/androidTest/java/androidx/webkit/internal/AssetHelperTest.java
index 9ecf3f0..5696e8a9 100644
--- a/webkit/src/androidTest/java/androidx/webkit/internal/AssetHelperTest.java
+++ b/webkit/src/androidTest/java/androidx/webkit/internal/AssetHelperTest.java
@@ -21,15 +21,19 @@
import android.util.Log;
import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import androidx.webkit.WebkitUtils;
+import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.ByteArrayOutputStream;
+import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@@ -39,11 +43,19 @@
private static final String TEST_STRING = "Just a test";
private AssetHelper mAssetHelper;
+ private File mInternalStorageTestDir;
@Before
public void setup() {
Context context = InstrumentationRegistry.getContext();
mAssetHelper = new AssetHelper(context);
+ mInternalStorageTestDir = new File(context.getFilesDir(), "test_dir");
+ mInternalStorageTestDir.mkdirs();
+ }
+
+ @After
+ public void tearDown() {
+ WebkitUtils.recursivelyDeleteFile(mInternalStorageTestDir);
}
@Test
@@ -114,6 +126,52 @@
mAssetHelper.openAsset(Uri.parse("/android_asset/test.txt")));
}
+ @Test
+ @MediumTest
+ public void testOpenFileFromInternalStorage() throws Throwable {
+ File testFile = new File(mInternalStorageTestDir, "some_file.txt");
+ WebkitUtils.writeToFile(testFile, TEST_STRING);
+
+ InputStream stream = AssetHelper.openFile(testFile);
+ Assert.assertNotNull("Should be able to open \"" + testFile + "\" from internal storage",
+ stream);
+ Assert.assertEquals(readAsString(stream), TEST_STRING);
+ }
+
+ @Test
+ @MediumTest
+ public void testOpenNonExistingFileInInternalStorage() throws Throwable {
+ File testFile = new File(mInternalStorageTestDir, "some/path/to/non_exist_file.txt");
+ InputStream stream = AssetHelper.openFile(testFile);
+ Assert.assertNull("Should not be able to open a non existing file from internal storage",
+ stream);
+ }
+
+ @Test
+ @SmallTest
+ public void testIsCanonicalChildOf() throws Throwable {
+ // Two files are used for testing :
+ // "/some/path/to/file_1.txt" and "/some/path/file_2.txt"
+
+ File parent = new File(mInternalStorageTestDir, "/some/path/");
+ File child = new File(parent, "/to/./file_1.txt");
+ boolean res = AssetHelper.isCanonicalChildOf(parent, child);
+ Assert.assertTrue(
+ "/to/./\"file_1.txt\" is in a subdirectory of \"/some/path/\"", res);
+
+ parent = new File(mInternalStorageTestDir, "/some/path/");
+ child = new File(parent, "/to/../file_2.txt");
+ res = AssetHelper.isCanonicalChildOf(parent, child);
+ Assert.assertTrue(
+ "/to/../\"file_2.txt\" is in a subdirectory of \"/some/path/\"", res);
+
+ parent = new File(mInternalStorageTestDir, "/some/path/to");
+ child = new File(parent, "/../file_2.txt");
+ res = AssetHelper.isCanonicalChildOf(parent, child);
+ Assert.assertFalse(
+ "/../\"file_2.txt\" is not in a subdirectory of \"/some/path/to/\"", res);
+ }
+
private static String readAsString(InputStream is) {
ByteArrayOutputStream os = new ByteArrayOutputStream();
byte[] buffer = new byte[512];
diff --git a/webkit/src/main/java/androidx/webkit/WebSettingsCompat.java b/webkit/src/main/java/androidx/webkit/WebSettingsCompat.java
index c4540e0..62f2a98 100644
--- a/webkit/src/main/java/androidx/webkit/WebSettingsCompat.java
+++ b/webkit/src/main/java/androidx/webkit/WebSettingsCompat.java
@@ -293,7 +293,7 @@
* TODO(amalova): unhide
* @hide
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public static final int FORCE_DARK_OFF = 0;
/**
@@ -307,7 +307,7 @@
* TODO(amalova): unhide
* @hide
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public static final int FORCE_DARK_AUTO = 1;
/**
@@ -320,7 +320,7 @@
* TODO(amalova): unhide
* @hide
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public static final int FORCE_DARK_ON = 2;
/**
@@ -350,7 +350,7 @@
* TODO(amalova): unhide
* @hide
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@SuppressLint("NewApi")
@RequiresFeature(name = WebViewFeature.FORCE_DARK,
enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
@@ -383,7 +383,7 @@
* TODO(amalova): unhide
* @hide
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@SuppressLint("NewApi")
@RequiresFeature(name = WebViewFeature.FORCE_DARK,
enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
diff --git a/webkit/src/main/java/androidx/webkit/WebViewAssetLoader.java b/webkit/src/main/java/androidx/webkit/WebViewAssetLoader.java
index fa064fc..f782bcb 100644
--- a/webkit/src/main/java/androidx/webkit/WebViewAssetLoader.java
+++ b/webkit/src/main/java/androidx/webkit/WebViewAssetLoader.java
@@ -18,24 +18,28 @@
import android.content.Context;
import android.net.Uri;
+import android.util.Log;
import android.webkit.WebResourceResponse;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;
import androidx.webkit.internal.AssetHelper;
+import java.io.File;
+import java.io.IOException;
import java.io.InputStream;
-import java.net.URLConnection;
import java.util.ArrayList;
import java.util.List;
/**
- * Helper class to load files including application's static assets and resources using http(s)://
- * URLs inside a {@link android.webkit.WebView} class.
- * Loading assets and resources using web-like URLs is desirable as it is compatible with the
- * Same-Origin policy.
+ * Helper class to load local files including application's static assets and resources using
+ * http(s):// URLs inside a {@link android.webkit.WebView} class.
+ * Loading local files using web-like URLs instead of {@code "file://"} is desirable as it is
+ * compatible with the Same-Origin policy.
*
* <p>
* For more context about application's assets and resources and how to normally access them please
@@ -144,6 +148,11 @@
* falling back to network and trying to resolve a path that doesn't exist. A
* {@link WebResourceResponse} with {@code null} {@link InputStream} will be received as an
* HTTP response with status code {@code 404} and no body.
+ * <p class="note">
+ * The MIME type for the file will be determined from the file's extension using
+ * {@link java.net.URLConnection#guessContentTypeFromName}. Developers should ensure that
+ * asset files are named using standard file extensions. If the file does not have a
+ * recognised extension, {@code "text/plain"} will be used by default.
*
* @param path the suffix path to be handled.
* @return {@link WebResourceResponse} for the requested file.
@@ -157,7 +166,7 @@
.build();
InputStream is = mAssetHelper.openAsset(uri);
- String mimeType = URLConnection.guessContentTypeFromName(path);
+ String mimeType = AssetHelper.guessMimeType(path);
return new WebResourceResponse(mimeType, null, is);
}
}
@@ -189,6 +198,11 @@
* falling back to network and trying to resolve a path that doesn't exist. A
* {@link WebResourceResponse} with {@code null} {@link InputStream} will be received as an
* HTTP response with status code {@code 404} and no body.
+ * <p class="note">
+ * The MIME type for the file will be determined from the file's extension using
+ * {@link java.net.URLConnection#guessContentTypeFromName}. Developers should ensure that
+ * resource files are named using standard file extensions. If the file does not have a
+ * recognised extension, {@code "text/plain"} will be used by default.
*
* @param path the suffix path to be handled.
* @return {@link WebResourceResponse} for the requested file.
@@ -202,13 +216,125 @@
.build();
InputStream is = mAssetHelper.openResource(uri);
- String mimeType = URLConnection.guessContentTypeFromName(path);
+ String mimeType = AssetHelper.guessMimeType(path);
return new WebResourceResponse(mimeType, null, is);
}
}
/**
+ * Handler class to open files from application internal storage.
+ * For more information about android storage please refer to
+ * <a href="https://developer.android.com/guide/topics/data/data-storage">Android Developers
+ * Docs: Data and file storage overview</a>.
+ * <p>
+ * To avoid leaking user or app data to the web, make sure to choose {@code directory}
+ * carefully, and assume any file under this directory could be accessed by any web page subject
+ * to same-origin rules.
+ * @hide
+ */
+ // TODO(b/132880733) unhide the API when it's ready.
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ public static final class InternalStoragePathHandler implements PathHandler {
+ /**
+ * Forbidden subdirectories of {@link Context#getDataDir} that cannot be exposed by this
+ * handler. They are forbidden as they often contain sensitive information.
+ */
+ public static final String[] FORBIDDEN_DATA_DIRS =
+ new String[] {"app_webview/", "databases/", "lib/", "shared_prefs/", "code_cache/"};
+
+ @NonNull private final File mDirectory;
+
+ /**
+ * Creates PathHandler for app's internal storage.
+ * The directory to be exposed must be inside either the application's internal data
+ * directory {@link context#getDataDir} or cache directory {@link context#getCacheDir}.
+ * External storage is not supported for security reasons, as other apps with
+ * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} may be able to modify the
+ * files.
+ * <p>
+ * Exposing the entire data or cache directory is not permitted, to avoid accidentally
+ * exposing sensitive application files to the web. Certain existing directories are also
+ * not permitted, such as {@link FORBIDDEN_DATA_DIRS}, as they are often sensitive.
+ * <p>
+ * The application should typically use a dedicated subdirectory for the files it intends to
+ * expose and keep them separate from other files.
+ *
+ * @param context {@link Context} that is used to access app's internal storage.
+ * @param directory the absolute path of the exposed app internal storage directory from
+ * which files can be loaded.
+ * @throws IllegalArgumentException if the directory is not allowed.
+ */
+ public InternalStoragePathHandler(@NonNull Context context, @NonNull File directory) {
+ if (!isAllowedInternalStorageDir(context, directory)) {
+ throw new IllegalArgumentException("The given directory \"" + directory
+ + "\" doesn't exist under an allowed app internal storage directory");
+ }
+ mDirectory = directory;
+ }
+
+ private static boolean isAllowedInternalStorageDir(@NonNull Context context,
+ @NonNull File dir) {
+ try {
+ String dirPath = AssetHelper.getCanonicalPath(dir);
+ String cacheDirPath = AssetHelper.getCanonicalPath(context.getCacheDir());
+ String dataDirPath = AssetHelper.getCanonicalPath(AssetHelper.getDataDir(context));
+ // dir has to be a subdirectory of data or cache dir.
+ if (!dirPath.startsWith(cacheDirPath) && !dirPath.startsWith(dataDirPath)) {
+ return false;
+ }
+ // dir cannot be the entire cache or data dir.
+ if (dirPath.equals(cacheDirPath) || dirPath.equals(dataDirPath)) {
+ return false;
+ }
+ // dir cannot be a subdirectory of any forbidden data dir.
+ for (String forbiddenPath : FORBIDDEN_DATA_DIRS) {
+ if (dirPath.startsWith(dataDirPath + forbiddenPath)) return false;
+ }
+ return true;
+ } catch (IOException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Opens the requested file from the exposed data directory.
+ * <p>
+ * The matched prefix path used shouldn't be a prefix of a real web path. Thus, if the
+ * requested file cannot be found or is outside the mounted directory a
+ * {@link WebResourceResponse} object with a {@code null} {@link InputStream} will be
+ * returned instead of {@code null}. This saves the time of falling back to network and
+ * trying to resolve a path that doesn't exist. A {@link WebResourceResponse} with
+ * {@code null} {@link InputStream} will be received as an HTTP response with status code
+ * {@code 404} and no body.
+ * <p class="note">
+ * The MIME type for the file will be determined from the file's extension using
+ * {@link java.net.URLConnection#guessContentTypeFromName}. Developers should ensure that
+ * files are named using standard file extensions. If the file does not have a
+ * recognised extension, {@code "text/plain"} will be used by default.
+ *
+ * @param path the suffix path to be handled.
+ * @return {@link WebResourceResponse} for the requested file.
+ */
+ @Override
+ @WorkerThread
+ @NonNull
+ public WebResourceResponse handle(@NonNull String path) {
+ File file = new File(mDirectory, path);
+ InputStream is = null;
+ if (AssetHelper.isCanonicalChildOf(mDirectory, file)) {
+ is = AssetHelper.openFile(file);
+ } else {
+ Log.e(TAG, "The requested file: " + path + " is outside the mounted directory: "
+ + mDirectory);
+ }
+ String mimeType = AssetHelper.guessMimeType(path);
+ return new WebResourceResponse(mimeType, null, is);
+ }
+ }
+
+
+ /**
* Matches URIs on the form: {@code "http(s)://authority/path/**"}, HTTPS is always enabled.
*
* <p>
diff --git a/webkit/src/main/java/androidx/webkit/WebViewCompat.java b/webkit/src/main/java/androidx/webkit/WebViewCompat.java
index e021a09..565d36b 100644
--- a/webkit/src/main/java/androidx/webkit/WebViewCompat.java
+++ b/webkit/src/main/java/androidx/webkit/WebViewCompat.java
@@ -622,7 +622,7 @@
* //TODO(laisminchillo): unhide
* @hide
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@RequiresFeature(name = WebViewFeature.MULTI_PROCESS_QUERY,
enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
public static boolean isMultiProcessEnabled() {
diff --git a/webkit/src/main/java/androidx/webkit/WebViewFeature.java b/webkit/src/main/java/androidx/webkit/WebViewFeature.java
index 5994789..f71c46d 100644
--- a/webkit/src/main/java/androidx/webkit/WebViewFeature.java
+++ b/webkit/src/main/java/androidx/webkit/WebViewFeature.java
@@ -391,7 +391,7 @@
* TODO(cricke): unhide
* @hide
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public static final String SUPPRESS_ERROR_PAGE = "SUPPRESS_ERROR_PAGE";
/**
@@ -401,7 +401,7 @@
* TODO(laisminchillo): unhide
* @hide
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public static final String MULTI_PROCESS_QUERY = "MULTI_PROCESS_QUERY";
/**
@@ -413,7 +413,7 @@
* TODO(amalova): unhide
* @hide
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public static final String FORCE_DARK = "FORCE_DARK";
/**
diff --git a/webkit/src/main/java/androidx/webkit/internal/AssetHelper.java b/webkit/src/main/java/androidx/webkit/internal/AssetHelper.java
index d2f2dfe..e6d5d2e 100644
--- a/webkit/src/main/java/androidx/webkit/internal/AssetHelper.java
+++ b/webkit/src/main/java/androidx/webkit/internal/AssetHelper.java
@@ -20,14 +20,18 @@
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.net.Uri;
+import android.os.Build;
import android.util.Log;
import android.util.TypedValue;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import java.io.File;
+import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.net.URLConnection;
import java.util.List;
import java.util.zip.GZIPInputStream;
@@ -38,6 +42,11 @@
public class AssetHelper {
private static final String TAG = "AssetHelper";
+ /**
+ * Default value to be used as MIME type if guessing MIME type failed.
+ */
+ public static final String DEFAULT_MIME_TYPE = "text/plain";
+
@NonNull private Context mContext;
public AssetHelper(@NonNull Context context) {
@@ -124,4 +133,85 @@
return null;
}
}
+
+ /**
+ * Open an {@code InputStream} for a file in application data directories.
+ *
+ * @param file The the file to be opened.
+ * @return An {@code InputStream} for the requested file or {@code null} if an error happens.
+ */
+ @Nullable
+ public static InputStream openFile(@NonNull File file) {
+ try {
+ FileInputStream fis = new FileInputStream(file);
+ return handleSvgzStream(Uri.parse(file.getPath()), fis);
+ } catch (IOException e) {
+ Log.e(TAG, "Error opening the requested file " + file, e);
+ return null;
+ }
+ }
+
+ /**
+ * Util method to test if the a given file is a child of the given parent directory.
+ * It uses canonical paths to make sure to resolve any symlinks, {@code "../"}, {@code "./"}
+ * ... etc in the given paths.
+ *
+ * @param parent the parent directory.
+ * @param child the child file.
+ * @return {@code true} if the canonical path of the given {@code child} starts with the
+ * canonical path of the given {@code parent}, {@code false} otherwise.
+ */
+ public static boolean isCanonicalChildOf(@NonNull File parent, @NonNull File child) {
+ try {
+ String parentCanonicalPath = parent.getCanonicalPath();
+ String childCanonicalPath = child.getCanonicalPath();
+
+ if (!parentCanonicalPath.endsWith("/")) parentCanonicalPath += "/";
+
+ return childCanonicalPath.startsWith(parentCanonicalPath);
+ } catch (IOException e) {
+ Log.e(TAG, "Error getting the canonical path of file", e);
+ return false;
+ }
+ }
+
+ /**
+ * Get the canonical path of the given directory with a slash {@code "/"} at the end.
+ */
+ @NonNull
+ public static String getCanonicalPath(@NonNull File file) throws IOException {
+ String path = file.getCanonicalPath();
+ if (!path.endsWith("/")) path += "/";
+ return path;
+ }
+
+ /**
+ * Get the data dir for an application.
+ *
+ * @param context the {@link Context} used to get the data dir.
+ * @return data dir {@link File} for that app.
+ */
+ @NonNull
+ public static File getDataDir(@NonNull Context context) {
+ // Context#getDataDir is only available in APIs >= 24.
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ return context.getDataDir();
+ } else {
+ // For APIs < 24 cache dir is created under the data dir.
+ return context.getCacheDir().getParentFile();
+ }
+ }
+
+ /**
+ * Use {@link URLConnection#guessContentTypeFromName} to guess MIME type or return the
+ * {@link DEFAULT_MIME_TYPE} if it can't guess.
+ *
+ * @param filePath path of the file to guess its MIME type.
+ * @return MIME type guessed from file extension or {@link DEFAULT_MIME_TYPE}.
+ */
+ @NonNull
+ public static String guessMimeType(@NonNull String filePath) {
+ String mimeType = URLConnection.guessContentTypeFromName(filePath);
+ return mimeType == null ? DEFAULT_MIME_TYPE : mimeType;
+ }
}
diff --git a/work/workmanager-benchmark/build.gradle b/work/workmanager-benchmark/build.gradle
new file mode 100644
index 0000000..e64d2a7
--- /dev/null
+++ b/work/workmanager-benchmark/build.gradle
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2017 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 androidx.build.AndroidXExtension
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
+import androidx.build.Publish
+
+import static androidx.build.dependencies.DependenciesKt.*
+
+plugins {
+ id("AndroidXPlugin")
+ id("com.android.library")
+ id("kotlin-android")
+ id("androidx.benchmark")
+}
+
+dependencies {
+ androidTestImplementation(project(':work:work-runtime-ktx'))
+ androidTestImplementation(project(":benchmark:benchmark-junit4"))
+ androidTestImplementation(WORK_ARCH_ROOM_RUNTIME)
+ androidTestImplementation(JUNIT)
+ androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
+ androidTestImplementation(ANDROIDX_TEST_CORE)
+ androidTestImplementation(ANDROIDX_TEST_RUNNER)
+ androidTestImplementation(ESPRESSO_CORE)
+ androidTestImplementation(KOTLIN_STDLIB)
+ androidTestImplementation(KOTLIN_COROUTINES)
+}
+
+androidx {
+ name = "Android WorkManager Benchmarks"
+ publish = Publish.NONE
+ mavenVersion = LibraryVersions.WORK
+ mavenGroup = LibraryGroups.WORK
+ inceptionYear = "2019"
+ description = "Android WorkManager Benchmark Library"
+ url = AndroidXExtension.ARCHITECTURE_URL
+}
diff --git a/work/workmanager-benchmark/src/androidTest/AndroidManifest.xml b/work/workmanager-benchmark/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000..9ad6fef
--- /dev/null
+++ b/work/workmanager-benchmark/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?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.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="androidx.work.benchmark.test">
+
+ <!-- Important: disable debuggable for accurate performance results -->
+ <application
+ android:debuggable="false"
+ tools:replace="android:debuggable">
+ </application>
+</manifest>
diff --git a/work/workmanager-benchmark/src/androidTest/java/androidx/work/benchmark/DispatchingExecutor.kt b/work/workmanager-benchmark/src/androidTest/java/androidx/work/benchmark/DispatchingExecutor.kt
new file mode 100644
index 0000000..5b6b635
--- /dev/null
+++ b/work/workmanager-benchmark/src/androidTest/java/androidx/work/benchmark/DispatchingExecutor.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.work.benchmark
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+import java.util.concurrent.Executor
+
+/**
+ * An [Executor] where we can await termination of all commands.
+ */
+class DispatchingExecutor : Executor {
+ val job = Job()
+ private val scope = CoroutineScope(Dispatchers.Default + job)
+ override fun execute(command: Runnable) {
+ scope.launch {
+ command.run()
+ }
+ }
+}
diff --git a/work/workmanager-benchmark/src/androidTest/java/androidx/work/benchmark/InitializeBenchmark.kt b/work/workmanager-benchmark/src/androidTest/java/androidx/work/benchmark/InitializeBenchmark.kt
new file mode 100644
index 0000000..3d33749
--- /dev/null
+++ b/work/workmanager-benchmark/src/androidTest/java/androidx/work/benchmark/InitializeBenchmark.kt
@@ -0,0 +1,130 @@
+/*
+ * 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.work.benchmark
+
+import android.content.Context
+import android.util.Log
+import androidx.benchmark.junit4.BenchmarkRule
+import androidx.benchmark.junit4.measureRepeated
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.work.Configuration
+import androidx.work.OneTimeWorkRequestBuilder
+import androidx.work.WorkInfo
+import androidx.work.impl.WorkDatabase
+import androidx.work.impl.WorkManagerImpl
+import androidx.work.impl.utils.SerialExecutor
+import androidx.work.impl.utils.taskexecutor.TaskExecutor
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.util.concurrent.Executor
+
+@RunWith(AndroidJUnit4::class)
+@LargeTest
+class InitializeBenchmark {
+
+ @get:Rule
+ val benchmarkRule = BenchmarkRule()
+ private lateinit var context: Context
+ private lateinit var executor: DispatchingExecutor
+ private lateinit var taskExecutor: TaskExecutor
+ private lateinit var configuration: Configuration
+
+ @Before
+ fun setUp() {
+ context = ApplicationProvider.getApplicationContext()
+
+ // Use a DispatchingExecutor to avoid having to wait for all the tasks to be done
+ // in the actual benchmark.
+ executor = DispatchingExecutor()
+ val serialExecutor = SerialExecutor(executor)
+
+ taskExecutor = object : TaskExecutor {
+ override fun postToMainThread(runnable: Runnable) {
+ serialExecutor.execute(runnable)
+ }
+
+ override fun getMainThreadExecutor(): Executor {
+ return serialExecutor
+ }
+
+ override fun executeOnBackgroundThread(runnable: Runnable) {
+ serialExecutor.execute(runnable)
+ }
+
+ override fun getBackgroundExecutor(): SerialExecutor {
+ return serialExecutor
+ }
+ }
+
+ configuration = Configuration.Builder()
+ .setTaskExecutor(executor)
+ .setExecutor(executor)
+ .setMinimumLoggingLevel(Log.DEBUG)
+ .build()
+ }
+
+ @Test
+ fun initializeSimple() {
+ benchmarkRule.measureRepeated {
+ // Runs ForceStopRunnable
+ val database = WorkDatabase.create(context, configuration.taskExecutor, false)
+ WorkManagerImpl(context, configuration, taskExecutor, database)
+ runWithTimingDisabled {
+ runBlocking {
+ executor.job.children.forEach {
+ it.join()
+ }
+ }
+ database.close()
+ }
+ }
+ }
+
+ @Test
+ fun initializeWithWorkLeft() {
+ val count = 20
+ benchmarkRule.measureRepeated {
+ val database = WorkDatabase.create(context, configuration.taskExecutor, false)
+ runWithTimingDisabled {
+ for (i in 0 until count) {
+ val request = OneTimeWorkRequestBuilder<NoOpWorker>()
+ val state =
+ if (i <= count - 2) WorkInfo.State.SUCCEEDED else WorkInfo.State.RUNNING
+ request.setInitialState(state)
+ database.workSpecDao().insertWorkSpec(request.build().workSpec)
+ }
+ }
+ // Runs ForceStopRunnable
+ WorkManagerImpl(context, configuration, taskExecutor, database)
+ // Prune records for the next run.
+ runWithTimingDisabled {
+ runBlocking {
+ executor.job.children.forEach {
+ it.join()
+ }
+ }
+ database.workSpecDao().pruneFinishedWorkWithZeroDependentsIgnoringKeepForAtLeast()
+ database.close()
+ }
+ }
+ }
+}
diff --git a/work/workmanager-benchmark/src/androidTest/java/androidx/work/benchmark/NoOpWorker.kt b/work/workmanager-benchmark/src/androidTest/java/androidx/work/benchmark/NoOpWorker.kt
new file mode 100644
index 0000000..760ffd9
--- /dev/null
+++ b/work/workmanager-benchmark/src/androidTest/java/androidx/work/benchmark/NoOpWorker.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.work.benchmark
+
+import android.content.Context
+import androidx.work.CoroutineWorker
+import androidx.work.WorkerParameters
+
+/**
+ * Does not do anything useful except returning a [androidx.work.ListenableWorker.Result.success].
+ */
+class NoOpWorker(context: Context, parameters: WorkerParameters) :
+ CoroutineWorker(context, parameters) {
+ override suspend fun doWork(): Result {
+ return Result.success()
+ }
+}
\ No newline at end of file
diff --git a/media2/widget/src/main/res/values/public.xml b/work/workmanager-benchmark/src/main/AndroidManifest.xml
similarity index 67%
copy from media2/widget/src/main/res/values/public.xml
copy to work/workmanager-benchmark/src/main/AndroidManifest.xml
index 6f7f00f..4eb2ff3 100644
--- a/media2/widget/src/main/res/values/public.xml
+++ b/work/workmanager-benchmark/src/main/AndroidManifest.xml
@@ -1,6 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright 2018 The Android Open Source Project
+ ~ 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.
@@ -14,9 +13,4 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
-<!-- Definitions of attributes to be exposed as public -->
-<resources>
- <public type="attr" name="enableControlView" />
- <public type="attr" name="viewType" />
-</resources>
+<manifest package="androidx.work.benchmark" />
diff --git a/work/workmanager-gcm/src/androidTest/java/androidx/work/impl/background/gcm/WorkManagerGcmDispatcherTest.kt b/work/workmanager-gcm/src/androidTest/java/androidx/work/impl/background/gcm/WorkManagerGcmDispatcherTest.kt
index c67dfd2..f127aba 100644
--- a/work/workmanager-gcm/src/androidTest/java/androidx/work/impl/background/gcm/WorkManagerGcmDispatcherTest.kt
+++ b/work/workmanager-gcm/src/androidTest/java/androidx/work/impl/background/gcm/WorkManagerGcmDispatcherTest.kt
@@ -25,6 +25,7 @@
import androidx.test.filters.MediumTest
import androidx.work.Configuration
import androidx.work.impl.WorkManagerImpl
+import androidx.work.impl.utils.SerialExecutor
import androidx.work.impl.utils.SynchronousExecutor
import com.google.android.gms.gcm.GcmNetworkManager
import com.google.android.gms.gcm.TaskParams
@@ -63,7 +64,8 @@
val workTaskExecutor: androidx.work.impl.utils.taskexecutor.TaskExecutor =
object : androidx.work.impl.utils.taskexecutor.TaskExecutor {
- override fun postToMainThread(runnable: Runnable?) {
+ private val mSerialExecutor = SerialExecutor(mExecutor)
+ override fun postToMainThread(runnable: Runnable) {
mExecutor.execute(runnable)
}
@@ -71,12 +73,12 @@
return mExecutor
}
- override fun executeOnBackgroundThread(runnable: Runnable?) {
- mExecutor.execute(runnable)
+ override fun executeOnBackgroundThread(runnable: Runnable) {
+ mSerialExecutor.execute(runnable)
}
- override fun getBackgroundExecutor(): Executor {
- return mExecutor
+ override fun getBackgroundExecutor(): SerialExecutor {
+ return mSerialExecutor
}
}
diff --git a/work/workmanager-ktx/src/androidTest/java/androidx/work/CoroutineWorkerTest.kt b/work/workmanager-ktx/src/androidTest/java/androidx/work/CoroutineWorkerTest.kt
index 125f4e7..301e7e1 100644
--- a/work/workmanager-ktx/src/androidTest/java/androidx/work/CoroutineWorkerTest.kt
+++ b/work/workmanager-ktx/src/androidTest/java/androidx/work/CoroutineWorkerTest.kt
@@ -25,6 +25,7 @@
import androidx.test.filters.SmallTest
import androidx.work.impl.WorkDatabase
import androidx.work.impl.WorkManagerImpl
+import androidx.work.impl.utils.SerialExecutor
import androidx.work.impl.utils.WorkProgressUpdater
import androidx.work.impl.utils.futures.SettableFuture
import androidx.work.impl.utils.taskexecutor.TaskExecutor
@@ -197,6 +198,7 @@
class InstantWorkTaskExecutor : TaskExecutor {
private val mSynchronousExecutor = SynchronousExecutor()
+ private val mSerialExecutor = SerialExecutor(mSynchronousExecutor)
override fun postToMainThread(runnable: Runnable) {
runnable.run()
@@ -207,11 +209,11 @@
}
override fun executeOnBackgroundThread(runnable: Runnable) {
- runnable.run()
+ mSerialExecutor.execute(runnable)
}
- override fun getBackgroundExecutor(): Executor {
- return mSynchronousExecutor
+ override fun getBackgroundExecutor(): SerialExecutor {
+ return mSerialExecutor
}
}
diff --git a/work/workmanager-rxjava2/src/test/java/androidx/work/RxWorkerTest.kt b/work/workmanager-rxjava2/src/test/java/androidx/work/RxWorkerTest.kt
index bf07147..2e7c0a0 100644
--- a/work/workmanager-rxjava2/src/test/java/androidx/work/RxWorkerTest.kt
+++ b/work/workmanager-rxjava2/src/test/java/androidx/work/RxWorkerTest.kt
@@ -17,6 +17,7 @@
package androidx.work
import android.content.Context
+import androidx.work.impl.utils.SerialExecutor
import androidx.work.impl.utils.SynchronousExecutor
import androidx.work.impl.utils.taskexecutor.TaskExecutor
import io.reactivex.Single
@@ -140,6 +141,7 @@
class InstantWorkTaskExecutor : TaskExecutor {
private val mSynchronousExecutor = SynchronousExecutor()
+ private val mSerialExecutor = SerialExecutor(mSynchronousExecutor)
override fun postToMainThread(runnable: Runnable) {
runnable.run()
@@ -150,11 +152,11 @@
}
override fun executeOnBackgroundThread(runnable: Runnable) {
- runnable.run()
+ mSerialExecutor.execute(runnable)
}
- override fun getBackgroundExecutor(): Executor {
- return mSynchronousExecutor
+ override fun getBackgroundExecutor(): SerialExecutor {
+ return mSerialExecutor
}
}
}
diff --git a/work/workmanager-testing/src/androidTest/java/androidx/work/testing/WorkManagerInitHelperTest.java b/work/workmanager-testing/src/androidTest/java/androidx/work/testing/WorkManagerInitHelperTest.java
index d089045..32f1a86 100644
--- a/work/workmanager-testing/src/androidTest/java/androidx/work/testing/WorkManagerInitHelperTest.java
+++ b/work/workmanager-testing/src/androidTest/java/androidx/work/testing/WorkManagerInitHelperTest.java
@@ -28,6 +28,7 @@
import androidx.work.Configuration;
import androidx.work.WorkManager;
import androidx.work.impl.WorkManagerImpl;
+import androidx.work.impl.utils.SerialExecutor;
import org.junit.After;
import org.junit.Before;
@@ -60,11 +61,13 @@
public void testWorkManagerIsInitialized() {
Configuration configuration = new Configuration.Builder()
.setExecutor(mExecutor)
+ .setTaskExecutor(mExecutor)
.build();
WorkManagerTestInitHelper.initializeTestWorkManager(mContext, configuration);
WorkManagerImpl workManager = (WorkManagerImpl) WorkManager.getInstance(mContext);
assertThat(workManager, is(notNullValue()));
- assertThat(workManager.getWorkTaskExecutor().getBackgroundExecutor(), is(mExecutor));
+ SerialExecutor serialExecutor = workManager.getWorkTaskExecutor().getBackgroundExecutor();
+ assertThat(serialExecutor.getDelegatedExecutor(), is(mExecutor));
}
}
diff --git a/work/workmanager-testing/src/main/java/androidx/work/testing/InstantWorkTaskExecutor.java b/work/workmanager-testing/src/main/java/androidx/work/testing/InstantWorkTaskExecutor.java
index f727fd7..36e7764 100644
--- a/work/workmanager-testing/src/main/java/androidx/work/testing/InstantWorkTaskExecutor.java
+++ b/work/workmanager-testing/src/main/java/androidx/work/testing/InstantWorkTaskExecutor.java
@@ -17,6 +17,7 @@
package androidx.work.testing;
import androidx.annotation.NonNull;
+import androidx.work.impl.utils.SerialExecutor;
import androidx.work.impl.utils.SynchronousExecutor;
import androidx.work.impl.utils.taskexecutor.TaskExecutor;
@@ -28,6 +29,7 @@
class InstantWorkTaskExecutor implements TaskExecutor {
private Executor mSynchronousExecutor = new SynchronousExecutor();
+ private SerialExecutor mSerialExecutor = new SerialExecutor(mSynchronousExecutor);
@NonNull
Executor getSynchronousExecutor() {
@@ -46,11 +48,11 @@
@Override
public void executeOnBackgroundThread(Runnable runnable) {
- runnable.run();
+ mSerialExecutor.execute(runnable);
}
@Override
- public Executor getBackgroundExecutor() {
- return mSynchronousExecutor;
+ public SerialExecutor getBackgroundExecutor() {
+ return mSerialExecutor;
}
}
diff --git a/work/workmanager-testing/src/main/java/androidx/work/testing/TestWorkManagerImpl.java b/work/workmanager-testing/src/main/java/androidx/work/testing/TestWorkManagerImpl.java
index f956bdb..4e7c916 100644
--- a/work/workmanager-testing/src/main/java/androidx/work/testing/TestWorkManagerImpl.java
+++ b/work/workmanager-testing/src/main/java/androidx/work/testing/TestWorkManagerImpl.java
@@ -54,7 +54,8 @@
configuration,
new TaskExecutor() {
Executor mSynchronousExecutor = new SynchronousExecutor();
- Executor mBackgroundExecutor = new SerialExecutor(configuration.getExecutor());
+ SerialExecutor mSerialExecutor =
+ new SerialExecutor(configuration.getTaskExecutor());
@Override
public void postToMainThread(Runnable runnable) {
@@ -68,12 +69,12 @@
@Override
public void executeOnBackgroundThread(Runnable runnable) {
- mBackgroundExecutor.execute(runnable);
+ mSerialExecutor.execute(runnable);
}
@Override
- public Executor getBackgroundExecutor() {
- return configuration.getExecutor();
+ public SerialExecutor getBackgroundExecutor() {
+ return mSerialExecutor;
}
},
true);
diff --git a/work/workmanager-testing/src/main/java/androidx/work/testing/WorkManagerTestInitHelper.java b/work/workmanager-testing/src/main/java/androidx/work/testing/WorkManagerTestInitHelper.java
index f0e3c34..1864168 100644
--- a/work/workmanager-testing/src/main/java/androidx/work/testing/WorkManagerTestInitHelper.java
+++ b/work/workmanager-testing/src/main/java/androidx/work/testing/WorkManagerTestInitHelper.java
@@ -37,6 +37,7 @@
SynchronousExecutor synchronousExecutor = new SynchronousExecutor();
Configuration configuration = new Configuration.Builder()
.setExecutor(synchronousExecutor)
+ .setTaskExecutor(synchronousExecutor)
.build();
initializeTestWorkManager(context, configuration);
}
diff --git a/work/workmanager/api/2.3.0-alpha01.txt b/work/workmanager/api/2.3.0-alpha01.txt
index a6cb786..0e3b058 100644
--- a/work/workmanager/api/2.3.0-alpha01.txt
+++ b/work/workmanager/api/2.3.0-alpha01.txt
@@ -64,6 +64,7 @@
public final class Data {
ctor public Data(androidx.work.Data);
+ method public static androidx.work.Data fromByteArray(byte[]);
method public boolean getBoolean(String, boolean);
method public boolean[]? getBooleanArray(String);
method public byte getByte(String, byte);
@@ -79,6 +80,7 @@
method public long[]? getLongArray(String);
method public String? getString(String);
method public String![]? getStringArray(String);
+ method public static byte[] toByteArray(androidx.work.Data);
field public static final androidx.work.Data! EMPTY;
field public static final int MAX_DATA_BYTES = 10240; // 0x2800
}
diff --git a/work/workmanager/api/current.txt b/work/workmanager/api/current.txt
index a6cb786..0e3b058 100644
--- a/work/workmanager/api/current.txt
+++ b/work/workmanager/api/current.txt
@@ -64,6 +64,7 @@
public final class Data {
ctor public Data(androidx.work.Data);
+ method public static androidx.work.Data fromByteArray(byte[]);
method public boolean getBoolean(String, boolean);
method public boolean[]? getBooleanArray(String);
method public byte getByte(String, byte);
@@ -79,6 +80,7 @@
method public long[]? getLongArray(String);
method public String? getString(String);
method public String![]? getStringArray(String);
+ method public static byte[] toByteArray(androidx.work.Data);
field public static final androidx.work.Data! EMPTY;
field public static final int MAX_DATA_BYTES = 10240; // 0x2800
}
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcherTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcherTest.java
index 620307f..95b6d4c 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcherTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcherTest.java
@@ -567,6 +567,7 @@
@Test
public void testConstraintsChanged_withFutureWork() throws InterruptedException {
+ when(mBatteryChargingTracker.getInitialState()).thenReturn(true);
// Use a mocked scheduler in this test.
Scheduler scheduler = mock(Scheduler.class);
doCallRealMethod().when(mWorkManager).rescheduleEligibleWork();
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/utils/taskexecutor/InstantWorkTaskExecutor.java b/work/workmanager/src/androidTest/java/androidx/work/impl/utils/taskexecutor/InstantWorkTaskExecutor.java
index 8bfee8e..965f9b3 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/utils/taskexecutor/InstantWorkTaskExecutor.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/utils/taskexecutor/InstantWorkTaskExecutor.java
@@ -16,6 +16,7 @@
package androidx.work.impl.utils.taskexecutor;
+import androidx.work.impl.utils.SerialExecutor;
import androidx.work.impl.utils.SynchronousExecutor;
import java.util.concurrent.Executor;
@@ -26,6 +27,7 @@
public class InstantWorkTaskExecutor implements TaskExecutor {
private Executor mSynchronousExecutor = new SynchronousExecutor();
+ private SerialExecutor mBackgroundExecutor = new SerialExecutor(mSynchronousExecutor);
@Override
public void postToMainThread(Runnable runnable) {
@@ -43,7 +45,7 @@
}
@Override
- public Executor getBackgroundExecutor() {
- return mSynchronousExecutor;
+ public SerialExecutor getBackgroundExecutor() {
+ return mBackgroundExecutor;
}
}
diff --git a/work/workmanager/src/main/java/androidx/work/Data.java b/work/workmanager/src/main/java/androidx/work/Data.java
index 6d0beba..8febaf8 100644
--- a/work/workmanager/src/main/java/androidx/work/Data.java
+++ b/work/workmanager/src/main/java/androidx/work/Data.java
@@ -346,11 +346,9 @@
* @return The byte array representation of the input
* @throws IllegalStateException if the serialized payload is bigger than
* {@link #MAX_DATA_BYTES}
- * @hide
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@TypeConverter
- public static @NonNull byte[] toByteArray(@NonNull Data data) throws IllegalStateException {
+ public static @NonNull byte[] toByteArray(@NonNull Data data) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = null;
try {
@@ -394,11 +392,9 @@
* @param bytes The byte array representation to convert
* @return An {@link Data} object built from the input
* @throws IllegalStateException if bytes is bigger than {@link #MAX_DATA_BYTES}
- * @hide
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@TypeConverter
- public static @NonNull Data fromByteArray(@NonNull byte[] bytes) throws IllegalStateException {
+ public static @NonNull Data fromByteArray(@NonNull byte[] bytes) {
if (bytes.length > MAX_DATA_BYTES) {
throw new IllegalStateException(
"Data cannot occupy more than " + MAX_DATA_BYTES + " bytes when serialized");
diff --git a/work/workmanager/src/main/java/androidx/work/ListenableWorker.java b/work/workmanager/src/main/java/androidx/work/ListenableWorker.java
index 2203618..41ac5d0 100644
--- a/work/workmanager/src/main/java/androidx/work/ListenableWorker.java
+++ b/work/workmanager/src/main/java/androidx/work/ListenableWorker.java
@@ -180,6 +180,9 @@
* A ListenableWorker is given a maximum of ten minutes to finish its execution and return a
* {@link Result}. After this time has expired, the worker will be signalled to stop and its
* {@link ListenableFuture} will be cancelled.
+ * <p>
+ * The future will also be cancelled if this worker is stopped for any reason
+ * (see {@link #onStopped()}).
*
* @return A {@link ListenableFuture} with the {@link Result} of the computation. If you
* cancel this Future, WorkManager will treat this unit of work as failed.
@@ -222,12 +225,13 @@
}
/**
- * This method is invoked when this Worker has been told to stop. This could happen due
- * to an explicit cancellation signal by the user, or because the system has decided to preempt
- * the task. In these cases, the results of the work will be ignored by WorkManager. All
- * processing in this method should be lightweight - there are no contractual guarantees about
- * which thread will invoke this call, so this should not be a long-running or blocking
- * operation.
+ * This method is invoked when this Worker has been told to stop. At this point, the
+ * {@link ListenableFuture} returned by the instance of {@link #startWork()} is
+ * also cancelled. This could happen due to an explicit cancellation signal by the user, or
+ * because the system has decided to preempt the task. In these cases, the results of the
+ * work will be ignored by WorkManager. All processing in this method should be lightweight
+ * - there are no contractual guarantees about which thread will invoke this call, so this
+ * should not be a long-running or blocking operation.
*/
public void onStopped() {
// Do nothing by default.
diff --git a/work/workmanager/src/main/java/androidx/work/impl/WorkManagerImpl.java b/work/workmanager/src/main/java/androidx/work/impl/WorkManagerImpl.java
index e5120d2..9c52835b 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/WorkManagerImpl.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/WorkManagerImpl.java
@@ -93,7 +93,7 @@
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- public static void setDelegate(WorkManagerImpl delegate) {
+ public static void setDelegate(@Nullable WorkManagerImpl delegate) {
synchronized (sLock) {
sDelegatedInstance = delegate;
}
@@ -220,10 +220,33 @@
@NonNull Configuration configuration,
@NonNull TaskExecutor workTaskExecutor,
boolean useTestDatabase) {
+ this(context,
+ configuration,
+ workTaskExecutor,
+ WorkDatabase.create(
+ context.getApplicationContext(),
+ configuration.getTaskExecutor(),
+ useTestDatabase)
+ );
+ }
+ /**
+ * Create an instance of {@link WorkManagerImpl}.
+ *
+ * @param context The application {@link Context}
+ * @param configuration The {@link Configuration} configuration
+ * @param workTaskExecutor The {@link TaskExecutor} for running "processing" jobs, such as
+ * enqueueing, scheduling, cancellation, etc.
+ * @param database The {@link WorkDatabase}
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ public WorkManagerImpl(
+ @NonNull Context context,
+ @NonNull Configuration configuration,
+ @NonNull TaskExecutor workTaskExecutor,
+ @NonNull WorkDatabase database) {
Context applicationContext = context.getApplicationContext();
- WorkDatabase database = WorkDatabase.create(
- applicationContext, configuration.getTaskExecutor(), useTestDatabase);
Logger.setLogger(new Logger.LogcatLogger(configuration.getMinimumLoggingLevel()));
List<Scheduler> schedulers = createSchedulers(applicationContext, workTaskExecutor);
Processor processor = new Processor(
diff --git a/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcher.java b/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcher.java
index 548da36..5ddb288 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcher.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcher.java
@@ -32,6 +32,7 @@
import androidx.work.impl.ExecutionListener;
import androidx.work.impl.Processor;
import androidx.work.impl.WorkManagerImpl;
+import androidx.work.impl.utils.SerialExecutor;
import androidx.work.impl.utils.WakeLocks;
import androidx.work.impl.utils.taskexecutor.TaskExecutor;
@@ -221,8 +222,11 @@
}
mCurrentIntent = null;
}
+ SerialExecutor serialExecutor = mTaskExecutor.getBackgroundExecutor();
+ if (!mCommandHandler.hasPendingCommands()
+ && mIntents.isEmpty()
+ && !serialExecutor.hasPendingTasks()) {
- if (!mCommandHandler.hasPendingCommands() && mIntents.isEmpty()) {
// If there are no more intents to process, and the command handler
// has no more pending commands, stop the service.
Logger.get().debug(TAG, "No more commands & intents.");
diff --git a/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/SystemJobScheduler.java b/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/SystemJobScheduler.java
index d4de7b9..03df2de 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/SystemJobScheduler.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/SystemJobScheduler.java
@@ -264,9 +264,7 @@
List<JobInfo> jobs = getPendingJobs(context, jobScheduler);
if (jobs != null && !jobs.isEmpty()) {
for (JobInfo jobInfo : jobs) {
- PersistableBundle extras = jobInfo.getExtras();
- //noinspection ConstantConditions
- if (extras == null || !extras.containsKey(EXTRA_WORK_SPEC_ID)) {
+ if (getWorkSpecIdFromJobInfo(jobInfo) == null) {
cancelJobById(jobScheduler, jobInfo.getId());
}
}
@@ -312,7 +310,6 @@
* For reference: b/133556574, b/133556809, b/133556535
*/
@Nullable
- @SuppressWarnings("ConstantConditions")
private static List<Integer> getPendingJobIds(
@NonNull Context context,
@NonNull JobScheduler jobScheduler,
@@ -327,14 +324,25 @@
List<Integer> jobIds = new ArrayList<>(2);
for (JobInfo jobInfo : jobs) {
- PersistableBundle extras = jobInfo.getExtras();
- if (extras != null && extras.containsKey(EXTRA_WORK_SPEC_ID)) {
- if (workSpecId.equals(extras.getString(EXTRA_WORK_SPEC_ID))) {
- jobIds.add(jobInfo.getId());
- }
+ if (workSpecId.equals(getWorkSpecIdFromJobInfo(jobInfo))) {
+ jobIds.add(jobInfo.getId());
}
}
return jobIds;
}
-}
+
+ @SuppressWarnings("ConstantConditions")
+ private static @Nullable String getWorkSpecIdFromJobInfo(@NonNull JobInfo jobInfo) {
+ PersistableBundle extras = jobInfo.getExtras();
+ try {
+ if (extras != null && extras.containsKey(EXTRA_WORK_SPEC_ID)) {
+ return extras.getString(EXTRA_WORK_SPEC_ID);
+ }
+ } catch (NullPointerException e) {
+ // b/138364061: BaseBundle.mMap seems to be null in some cases here. Ignore and return
+ // null.
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/SystemJobService.java b/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/SystemJobService.java
index aa28fb2..bbc87a0 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/SystemJobService.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/SystemJobService.java
@@ -16,6 +16,8 @@
package androidx.work.impl.background.systemjob;
+import static androidx.work.impl.background.systemjob.SystemJobInfoConverter.EXTRA_WORK_SPEC_ID;
+
import android.app.Application;
import android.app.job.JobParameters;
import android.app.job.JobService;
@@ -24,6 +26,7 @@
import android.text.TextUtils;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.work.Logger;
@@ -86,22 +89,14 @@
}
@Override
- public boolean onStartJob(JobParameters params) {
+ public boolean onStartJob(@NonNull JobParameters params) {
if (mWorkManagerImpl == null) {
Logger.get().debug(TAG, "WorkManager is not initialized; requesting retry.");
jobFinished(params, true);
return false;
}
- PersistableBundle extras = params.getExtras();
- // This can be null, possibly on a device-specific/API-specific (23) situation. b/134028277
- //noinspection ConstantConditions
- if (extras == null) {
- Logger.get().error(TAG, "No extras in JobParameters.");
- return false;
- }
-
- String workSpecId = extras.getString(SystemJobInfoConverter.EXTRA_WORK_SPEC_ID);
+ String workSpecId = getWorkSpecIdFromJobParameters(params);
if (TextUtils.isEmpty(workSpecId)) {
Logger.get().error(TAG, "WorkSpec id not found!");
return false;
@@ -152,21 +147,13 @@
}
@Override
- public boolean onStopJob(JobParameters params) {
+ public boolean onStopJob(@NonNull JobParameters params) {
if (mWorkManagerImpl == null) {
Logger.get().debug(TAG, "WorkManager is not initialized; requesting retry.");
return true;
}
- PersistableBundle extras = params.getExtras();
- // This can be null, possibly on a device-specific/API-specific (23) situation. b/134028277
- //noinspection ConstantConditions
- if (extras == null) {
- Logger.get().error(TAG, "No extras in JobParameters.");
- return false;
- }
-
- String workSpecId = extras.getString(SystemJobInfoConverter.EXTRA_WORK_SPEC_ID);
+ String workSpecId = getWorkSpecIdFromJobParameters(params);
if (TextUtils.isEmpty(workSpecId)) {
Logger.get().error(TAG, "WorkSpec id not found!");
return false;
@@ -192,4 +179,18 @@
jobFinished(parameters, needsReschedule);
}
}
+
+ @Nullable
+ @SuppressWarnings("ConstantConditions")
+ private static String getWorkSpecIdFromJobParameters(@NonNull JobParameters parameters) {
+ try {
+ PersistableBundle extras = parameters.getExtras();
+ if (extras != null && extras.containsKey(EXTRA_WORK_SPEC_ID)) {
+ return extras.getString(EXTRA_WORK_SPEC_ID);
+ }
+ } catch (NullPointerException e) {
+ // b/138441699: BaseBundle.getString sometimes throws an NPE. Ignore and return null.
+ }
+ return null;
+ }
}
diff --git a/work/workmanager/src/main/java/androidx/work/impl/utils/SerialExecutor.java b/work/workmanager/src/main/java/androidx/work/impl/utils/SerialExecutor.java
index 90e9adc..982c865 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/utils/SerialExecutor.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/utils/SerialExecutor.java
@@ -17,6 +17,7 @@
package androidx.work.impl.utils;
import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
import java.util.ArrayDeque;
import java.util.concurrent.Executor;
@@ -57,6 +58,21 @@
}
/**
+ * @return {@code true} if there are tasks to execute in the queue.
+ */
+ public boolean hasPendingTasks() {
+ synchronized (mLock) {
+ return !mTasks.isEmpty();
+ }
+ }
+
+ @NonNull
+ @VisibleForTesting
+ public Executor getDelegatedExecutor() {
+ return mExecutor;
+ }
+
+ /**
* A {@link Runnable} which tells the {@link SerialExecutor} to schedule the next command
* after completion.
*/
diff --git a/work/workmanager/src/main/java/androidx/work/impl/utils/taskexecutor/TaskExecutor.java b/work/workmanager/src/main/java/androidx/work/impl/utils/taskexecutor/TaskExecutor.java
index e5df2d9..2542e40 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/utils/taskexecutor/TaskExecutor.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/utils/taskexecutor/TaskExecutor.java
@@ -17,6 +17,7 @@
package androidx.work.impl.utils.taskexecutor;
import androidx.annotation.RestrictTo;
+import androidx.work.impl.utils.SerialExecutor;
import java.util.concurrent.Executor;
@@ -44,7 +45,7 @@
void executeOnBackgroundThread(Runnable runnable);
/**
- * @return The {@link Executor} for background task processing
+ * @return The {@link SerialExecutor} for background task processing
*/
- Executor getBackgroundExecutor();
+ SerialExecutor getBackgroundExecutor();
}
diff --git a/work/workmanager/src/main/java/androidx/work/impl/utils/taskexecutor/WorkManagerTaskExecutor.java b/work/workmanager/src/main/java/androidx/work/impl/utils/taskexecutor/WorkManagerTaskExecutor.java
index d689d89..6717838 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/utils/taskexecutor/WorkManagerTaskExecutor.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/utils/taskexecutor/WorkManagerTaskExecutor.java
@@ -32,7 +32,7 @@
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public class WorkManagerTaskExecutor implements TaskExecutor {
- private final Executor mBackgroundExecutor;
+ private final SerialExecutor mBackgroundExecutor;
public WorkManagerTaskExecutor(@NonNull Executor backgroundExecutor) {
// Wrap it with a serial executor so we have ordering guarantees on commands
@@ -65,7 +65,8 @@
}
@Override
- public Executor getBackgroundExecutor() {
+ @NonNull
+ public SerialExecutor getBackgroundExecutor() {
return mBackgroundExecutor;
}
}