Merge "Fixed lint warnings" 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/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..36959fc 100644
--- a/activity/activity/build.gradle
+++ b/activity/activity/build.gradle
@@ -18,10 +18,11 @@
dependencies {
api("androidx.annotation:annotation:1.1.0")
- api("androidx.core:core:1.1.0-rc01")
+ api("androidx.core:core:1.1.0")
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/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/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..535bbde
--- /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")
+ 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/ads/ads-identifier-provider/integration-tests/testapp/build.gradle b/ads/ads-identifier-provider/integration-tests/testapp/build.gradle
new file mode 100644
index 0000000..bd185ba
--- /dev/null
+++ b/ads/ads-identifier-provider/integration-tests/testapp/build.gradle
@@ -0,0 +1,34 @@
+/*
+ * 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.*
+
+plugins {
+ id("AndroidXPlugin")
+ id("com.android.application")
+}
+
+android {
+ defaultConfig {
+ applicationId "androidx.ads.identifier.provider.testapp"
+ minSdkVersion 14
+ }
+}
+
+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..3a7221b4
--- /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/ads/ads-identifier-provider/src/main/java/androidx/ads/identifier/provider/internal/package-info.java b/ads/ads-identifier-provider/src/main/java/androidx/ads/identifier/provider/internal/package-info.java
new file mode 100644
index 0000000..9ffd5e1
--- /dev/null
+++ b/ads/ads-identifier-provider/src/main/java/androidx/ads/identifier/provider/internal/package-info.java
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+/**
+ * @hide
+ */
+@RestrictTo(LIBRARY)
+package androidx.ads.identifier.provider.internal;
+
+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..3ab81a6 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")
+ 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/ads/ads-identifier/integration-tests/testapp/build.gradle b/ads/ads-identifier/integration-tests/testapp/build.gradle
new file mode 100644
index 0000000..c0fd225
--- /dev/null
+++ b/ads/ads-identifier/integration-tests/testapp/build.gradle
@@ -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.
+ */
+
+import static androidx.build.dependencies.DependenciesKt.*
+
+plugins {
+ id("AndroidXPlugin")
+ id("com.android.application")
+}
+
+android {
+ defaultConfig {
+ applicationId "androidx.ads.identifier.testapp"
+ minSdkVersion 14
+ }
+}
+
+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/ads/ads-identifier/src/main/java/androidx/ads/identifier/internal/package-info.java b/ads/ads-identifier/src/main/java/androidx/ads/identifier/internal/package-info.java
new file mode 100644
index 0000000..e9ad310
--- /dev/null
+++ b/ads/ads-identifier/src/main/java/androidx/ads/identifier/internal/package-info.java
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+/**
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+package androidx.ads.identifier.internal;
+
+import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import androidx.annotation.RestrictTo;
diff --git a/animation/build.gradle b/animation/build.gradle
index 65781d0..1d1800e 100644
--- a/animation/build.gradle
+++ b/animation/build.gradle
@@ -26,7 +26,7 @@
dependencies {
implementation("androidx.annotation:annotation:1.1.0")
- implementation("androidx.core:core:1.1.0-rc01")
+ implementation("androidx.core:core:1.1.0")
implementation("androidx.collection:collection:1.1.0")
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT, libs.exclude_for_espresso)
diff --git a/animation/integration-tests/testapp/build.gradle b/animation/integration-tests/testapp/build.gradle
index 82a6fe05..a9992a4 100644
--- a/animation/integration-tests/testapp/build.gradle
+++ b/animation/integration-tests/testapp/build.gradle
@@ -23,7 +23,7 @@
dependencies {
implementation("androidx.annotation:annotation:1.1.0")
- implementation("androidx.core:core:1.1.0-rc01")
+ implementation("androidx.core:core:1.1.0")
implementation(project(":animation"))
implementation(project(":animation:testing"))
diff --git a/animation/testing/build.gradle b/animation/testing/build.gradle
index f33191e..ad98a4c 100644
--- a/animation/testing/build.gradle
+++ b/animation/testing/build.gradle
@@ -26,7 +26,7 @@
dependencies {
implementation("androidx.annotation:annotation:1.1.0")
- implementation("androidx.core:core:1.1.0-rc01")
+ implementation("androidx.core:core:1.1.0")
implementation(project(":animation"))
implementation(ANDROIDX_TEST_EXT_JUNIT)
implementation(ANDROIDX_TEST_CORE)
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/appcompat/api/1.2.0-alpha01.txt b/appcompat/api/1.2.0-alpha01.txt
index e0c9d81..22cb389 100644
--- a/appcompat/api/1.2.0-alpha01.txt
+++ b/appcompat/api/1.2.0-alpha01.txt
@@ -424,8 +424,8 @@
package androidx.appcompat.widget {
public class ActionMenuView extends androidx.appcompat.widget.LinearLayoutCompat {
- ctor public ActionMenuView(android.content.Context!);
- ctor public ActionMenuView(android.content.Context!, android.util.AttributeSet!);
+ ctor public ActionMenuView(android.content.Context);
+ ctor public ActionMenuView(android.content.Context, android.util.AttributeSet?);
method public void dismissPopupMenus();
method protected androidx.appcompat.widget.ActionMenuView.LayoutParams! generateDefaultLayoutParams();
method public androidx.appcompat.widget.ActionMenuView.LayoutParams! generateLayoutParams(android.util.AttributeSet!);
@@ -460,9 +460,9 @@
}
public class AppCompatAutoCompleteTextView extends android.widget.AutoCompleteTextView implements androidx.core.view.TintableBackgroundView {
- ctor public AppCompatAutoCompleteTextView(android.content.Context!);
- ctor public AppCompatAutoCompleteTextView(android.content.Context!, android.util.AttributeSet!);
- ctor public AppCompatAutoCompleteTextView(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public AppCompatAutoCompleteTextView(android.content.Context);
+ ctor public AppCompatAutoCompleteTextView(android.content.Context, android.util.AttributeSet?);
+ ctor public AppCompatAutoCompleteTextView(android.content.Context, android.util.AttributeSet?, int);
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.res.ColorStateList? getSupportBackgroundTintList();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.graphics.PorterDuff.Mode? getSupportBackgroundTintMode();
method public void setBackgroundDrawable(android.graphics.drawable.Drawable!);
@@ -472,9 +472,9 @@
}
public class AppCompatButton extends android.widget.Button implements androidx.core.widget.AutoSizeableTextView androidx.core.view.TintableBackgroundView {
- ctor public AppCompatButton(android.content.Context!);
- ctor public AppCompatButton(android.content.Context!, android.util.AttributeSet!);
- ctor public AppCompatButton(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public AppCompatButton(android.content.Context);
+ ctor public AppCompatButton(android.content.Context, android.util.AttributeSet?);
+ ctor public AppCompatButton(android.content.Context, android.util.AttributeSet?, int);
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.res.ColorStateList? getSupportBackgroundTintList();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.graphics.PorterDuff.Mode? getSupportBackgroundTintMode();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) throws java.lang.IllegalArgumentException;
@@ -487,9 +487,9 @@
}
public class AppCompatCheckBox extends android.widget.CheckBox implements androidx.core.view.TintableBackgroundView androidx.core.widget.TintableCompoundButton {
- ctor public AppCompatCheckBox(android.content.Context!);
- ctor public AppCompatCheckBox(android.content.Context!, android.util.AttributeSet!);
- ctor public AppCompatCheckBox(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public AppCompatCheckBox(android.content.Context);
+ ctor public AppCompatCheckBox(android.content.Context, android.util.AttributeSet?);
+ ctor public AppCompatCheckBox(android.content.Context, android.util.AttributeSet?, int);
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.res.ColorStateList? getSupportBackgroundTintList();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.graphics.PorterDuff.Mode? getSupportBackgroundTintMode();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.res.ColorStateList? getSupportButtonTintList();
@@ -502,16 +502,16 @@
}
public class AppCompatCheckedTextView extends android.widget.CheckedTextView {
- ctor public AppCompatCheckedTextView(android.content.Context!);
- ctor public AppCompatCheckedTextView(android.content.Context!, android.util.AttributeSet!);
- ctor public AppCompatCheckedTextView(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public AppCompatCheckedTextView(android.content.Context);
+ ctor public AppCompatCheckedTextView(android.content.Context, android.util.AttributeSet?);
+ ctor public AppCompatCheckedTextView(android.content.Context, android.util.AttributeSet?, int);
method public void setTextAppearance(android.content.Context!, int);
}
public class AppCompatEditText extends android.widget.EditText implements androidx.core.view.TintableBackgroundView {
- ctor public AppCompatEditText(android.content.Context!);
- ctor public AppCompatEditText(android.content.Context!, android.util.AttributeSet!);
- ctor public AppCompatEditText(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public AppCompatEditText(android.content.Context);
+ ctor public AppCompatEditText(android.content.Context, android.util.AttributeSet?);
+ ctor public AppCompatEditText(android.content.Context, android.util.AttributeSet?, int);
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.res.ColorStateList? getSupportBackgroundTintList();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.graphics.PorterDuff.Mode? getSupportBackgroundTintMode();
method public void setBackgroundDrawable(android.graphics.drawable.Drawable!);
@@ -521,9 +521,9 @@
}
public class AppCompatImageButton extends android.widget.ImageButton implements androidx.core.view.TintableBackgroundView androidx.core.widget.TintableImageSourceView {
- ctor public AppCompatImageButton(android.content.Context!);
- ctor public AppCompatImageButton(android.content.Context!, android.util.AttributeSet!);
- ctor public AppCompatImageButton(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public AppCompatImageButton(android.content.Context);
+ ctor public AppCompatImageButton(android.content.Context, android.util.AttributeSet?);
+ ctor public AppCompatImageButton(android.content.Context, android.util.AttributeSet?, int);
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.res.ColorStateList? getSupportBackgroundTintList();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.graphics.PorterDuff.Mode? getSupportBackgroundTintMode();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.res.ColorStateList? getSupportImageTintList();
@@ -536,9 +536,9 @@
}
public class AppCompatImageView extends android.widget.ImageView implements androidx.core.view.TintableBackgroundView androidx.core.widget.TintableImageSourceView {
- ctor public AppCompatImageView(android.content.Context!);
- ctor public AppCompatImageView(android.content.Context!, android.util.AttributeSet!);
- ctor public AppCompatImageView(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public AppCompatImageView(android.content.Context);
+ ctor public AppCompatImageView(android.content.Context, android.util.AttributeSet?);
+ ctor public AppCompatImageView(android.content.Context, android.util.AttributeSet?, int);
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.res.ColorStateList? getSupportBackgroundTintList();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.graphics.PorterDuff.Mode? getSupportBackgroundTintMode();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.res.ColorStateList? getSupportImageTintList();
@@ -551,9 +551,9 @@
}
public class AppCompatMultiAutoCompleteTextView extends android.widget.MultiAutoCompleteTextView implements androidx.core.view.TintableBackgroundView {
- ctor public AppCompatMultiAutoCompleteTextView(android.content.Context!);
- ctor public AppCompatMultiAutoCompleteTextView(android.content.Context!, android.util.AttributeSet!);
- ctor public AppCompatMultiAutoCompleteTextView(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public AppCompatMultiAutoCompleteTextView(android.content.Context);
+ ctor public AppCompatMultiAutoCompleteTextView(android.content.Context, android.util.AttributeSet?);
+ ctor public AppCompatMultiAutoCompleteTextView(android.content.Context, android.util.AttributeSet?, int);
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.res.ColorStateList? getSupportBackgroundTintList();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.graphics.PorterDuff.Mode? getSupportBackgroundTintMode();
method public void setBackgroundDrawable(android.graphics.drawable.Drawable!);
@@ -564,8 +564,8 @@
public class AppCompatRadioButton extends android.widget.RadioButton implements androidx.core.view.TintableBackgroundView androidx.core.widget.TintableCompoundButton {
ctor public AppCompatRadioButton(android.content.Context!);
- ctor public AppCompatRadioButton(android.content.Context!, android.util.AttributeSet!);
- ctor public AppCompatRadioButton(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public AppCompatRadioButton(android.content.Context!, android.util.AttributeSet?);
+ ctor public AppCompatRadioButton(android.content.Context!, android.util.AttributeSet?, int);
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.res.ColorStateList? getSupportBackgroundTintList();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.graphics.PorterDuff.Mode? getSupportBackgroundTintMode();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.res.ColorStateList? getSupportButtonTintList();
@@ -578,24 +578,24 @@
}
public class AppCompatRatingBar extends android.widget.RatingBar {
- ctor public AppCompatRatingBar(android.content.Context!);
- ctor public AppCompatRatingBar(android.content.Context!, android.util.AttributeSet!);
- ctor public AppCompatRatingBar(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public AppCompatRatingBar(android.content.Context);
+ ctor public AppCompatRatingBar(android.content.Context, android.util.AttributeSet?);
+ ctor public AppCompatRatingBar(android.content.Context, android.util.AttributeSet?, int);
}
public class AppCompatSeekBar extends android.widget.SeekBar {
- ctor public AppCompatSeekBar(android.content.Context!);
- ctor public AppCompatSeekBar(android.content.Context!, android.util.AttributeSet!);
- ctor public AppCompatSeekBar(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public AppCompatSeekBar(android.content.Context);
+ ctor public AppCompatSeekBar(android.content.Context, android.util.AttributeSet?);
+ ctor public AppCompatSeekBar(android.content.Context, android.util.AttributeSet?, int);
}
public class AppCompatSpinner extends android.widget.Spinner implements androidx.core.view.TintableBackgroundView {
- ctor public AppCompatSpinner(android.content.Context!);
- ctor public AppCompatSpinner(android.content.Context!, int);
- ctor public AppCompatSpinner(android.content.Context!, android.util.AttributeSet!);
- ctor public AppCompatSpinner(android.content.Context!, android.util.AttributeSet!, int);
- ctor public AppCompatSpinner(android.content.Context!, android.util.AttributeSet!, int, int);
- ctor public AppCompatSpinner(android.content.Context!, android.util.AttributeSet!, int, int, android.content.res.Resources.Theme!);
+ ctor public AppCompatSpinner(android.content.Context);
+ ctor public AppCompatSpinner(android.content.Context, int);
+ ctor public AppCompatSpinner(android.content.Context, android.util.AttributeSet?);
+ ctor public AppCompatSpinner(android.content.Context, android.util.AttributeSet?, int);
+ ctor public AppCompatSpinner(android.content.Context, android.util.AttributeSet?, int, int);
+ ctor public AppCompatSpinner(android.content.Context, android.util.AttributeSet?, int, int, android.content.res.Resources.Theme!);
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.res.ColorStateList? getSupportBackgroundTintList();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.graphics.PorterDuff.Mode? getSupportBackgroundTintMode();
method public void setBackgroundDrawable(android.graphics.drawable.Drawable!);
@@ -604,9 +604,9 @@
}
public class AppCompatTextView extends android.widget.TextView implements androidx.core.widget.AutoSizeableTextView androidx.core.view.TintableBackgroundView androidx.core.widget.TintableCompoundDrawablesView {
- ctor public AppCompatTextView(android.content.Context!);
- ctor public AppCompatTextView(android.content.Context!, android.util.AttributeSet!);
- ctor public AppCompatTextView(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public AppCompatTextView(android.content.Context);
+ ctor public AppCompatTextView(android.content.Context, android.util.AttributeSet?);
+ ctor public AppCompatTextView(android.content.Context, android.util.AttributeSet?, int);
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.res.ColorStateList? getSupportBackgroundTintList();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.graphics.PorterDuff.Mode? getSupportBackgroundTintMode();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.res.ColorStateList? getSupportCompoundDrawablesTintList();
@@ -626,15 +626,15 @@
}
public class AppCompatToggleButton extends android.widget.ToggleButton {
- ctor public AppCompatToggleButton(android.content.Context!);
- ctor public AppCompatToggleButton(android.content.Context!, android.util.AttributeSet!);
- ctor public AppCompatToggleButton(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public AppCompatToggleButton(android.content.Context);
+ ctor public AppCompatToggleButton(android.content.Context, android.util.AttributeSet?);
+ ctor public AppCompatToggleButton(android.content.Context, android.util.AttributeSet?, int);
}
public class LinearLayoutCompat extends android.view.ViewGroup {
- ctor public LinearLayoutCompat(android.content.Context!);
- ctor public LinearLayoutCompat(android.content.Context!, android.util.AttributeSet!);
- ctor public LinearLayoutCompat(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public LinearLayoutCompat(android.content.Context);
+ ctor public LinearLayoutCompat(android.content.Context, android.util.AttributeSet?);
+ ctor public LinearLayoutCompat(android.content.Context, android.util.AttributeSet?, int);
method protected androidx.appcompat.widget.LinearLayoutCompat.LayoutParams! generateDefaultLayoutParams();
method public androidx.appcompat.widget.LinearLayoutCompat.LayoutParams! generateLayoutParams(android.util.AttributeSet!);
method protected androidx.appcompat.widget.LinearLayoutCompat.LayoutParams! generateLayoutParams(android.view.ViewGroup.LayoutParams!);
@@ -766,9 +766,9 @@
}
public class SearchView extends androidx.appcompat.widget.LinearLayoutCompat implements androidx.appcompat.view.CollapsibleActionView {
- ctor public SearchView(android.content.Context!);
- ctor public SearchView(android.content.Context!, android.util.AttributeSet!);
- ctor public SearchView(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public SearchView(android.content.Context);
+ ctor public SearchView(android.content.Context, android.util.AttributeSet?);
+ ctor public SearchView(android.content.Context, android.util.AttributeSet?, int);
method public int getImeOptions();
method public int getInputType();
method public int getMaxWidth();
@@ -827,9 +827,9 @@
}
public class SwitchCompat extends android.widget.CompoundButton {
- ctor public SwitchCompat(android.content.Context!);
- ctor public SwitchCompat(android.content.Context!, android.util.AttributeSet!);
- ctor public SwitchCompat(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public SwitchCompat(android.content.Context);
+ ctor public SwitchCompat(android.content.Context, android.util.AttributeSet?);
+ ctor public SwitchCompat(android.content.Context, android.util.AttributeSet?, int);
method public boolean getShowText();
method public boolean getSplitTrack();
method public int getSwitchMinWidth();
@@ -877,9 +877,9 @@
}
public class Toolbar extends android.view.ViewGroup {
- ctor public Toolbar(android.content.Context!);
- ctor public Toolbar(android.content.Context!, android.util.AttributeSet?);
- ctor public Toolbar(android.content.Context!, android.util.AttributeSet?, int);
+ ctor public Toolbar(android.content.Context);
+ ctor public Toolbar(android.content.Context, android.util.AttributeSet?);
+ ctor public Toolbar(android.content.Context, android.util.AttributeSet?, int);
method public void collapseActionView();
method public void dismissPopupMenus();
method protected androidx.appcompat.widget.Toolbar.LayoutParams! generateDefaultLayoutParams();
diff --git a/appcompat/api/current.txt b/appcompat/api/current.txt
index e0c9d81..22cb389 100644
--- a/appcompat/api/current.txt
+++ b/appcompat/api/current.txt
@@ -424,8 +424,8 @@
package androidx.appcompat.widget {
public class ActionMenuView extends androidx.appcompat.widget.LinearLayoutCompat {
- ctor public ActionMenuView(android.content.Context!);
- ctor public ActionMenuView(android.content.Context!, android.util.AttributeSet!);
+ ctor public ActionMenuView(android.content.Context);
+ ctor public ActionMenuView(android.content.Context, android.util.AttributeSet?);
method public void dismissPopupMenus();
method protected androidx.appcompat.widget.ActionMenuView.LayoutParams! generateDefaultLayoutParams();
method public androidx.appcompat.widget.ActionMenuView.LayoutParams! generateLayoutParams(android.util.AttributeSet!);
@@ -460,9 +460,9 @@
}
public class AppCompatAutoCompleteTextView extends android.widget.AutoCompleteTextView implements androidx.core.view.TintableBackgroundView {
- ctor public AppCompatAutoCompleteTextView(android.content.Context!);
- ctor public AppCompatAutoCompleteTextView(android.content.Context!, android.util.AttributeSet!);
- ctor public AppCompatAutoCompleteTextView(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public AppCompatAutoCompleteTextView(android.content.Context);
+ ctor public AppCompatAutoCompleteTextView(android.content.Context, android.util.AttributeSet?);
+ ctor public AppCompatAutoCompleteTextView(android.content.Context, android.util.AttributeSet?, int);
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.res.ColorStateList? getSupportBackgroundTintList();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.graphics.PorterDuff.Mode? getSupportBackgroundTintMode();
method public void setBackgroundDrawable(android.graphics.drawable.Drawable!);
@@ -472,9 +472,9 @@
}
public class AppCompatButton extends android.widget.Button implements androidx.core.widget.AutoSizeableTextView androidx.core.view.TintableBackgroundView {
- ctor public AppCompatButton(android.content.Context!);
- ctor public AppCompatButton(android.content.Context!, android.util.AttributeSet!);
- ctor public AppCompatButton(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public AppCompatButton(android.content.Context);
+ ctor public AppCompatButton(android.content.Context, android.util.AttributeSet?);
+ ctor public AppCompatButton(android.content.Context, android.util.AttributeSet?, int);
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.res.ColorStateList? getSupportBackgroundTintList();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.graphics.PorterDuff.Mode? getSupportBackgroundTintMode();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) throws java.lang.IllegalArgumentException;
@@ -487,9 +487,9 @@
}
public class AppCompatCheckBox extends android.widget.CheckBox implements androidx.core.view.TintableBackgroundView androidx.core.widget.TintableCompoundButton {
- ctor public AppCompatCheckBox(android.content.Context!);
- ctor public AppCompatCheckBox(android.content.Context!, android.util.AttributeSet!);
- ctor public AppCompatCheckBox(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public AppCompatCheckBox(android.content.Context);
+ ctor public AppCompatCheckBox(android.content.Context, android.util.AttributeSet?);
+ ctor public AppCompatCheckBox(android.content.Context, android.util.AttributeSet?, int);
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.res.ColorStateList? getSupportBackgroundTintList();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.graphics.PorterDuff.Mode? getSupportBackgroundTintMode();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.res.ColorStateList? getSupportButtonTintList();
@@ -502,16 +502,16 @@
}
public class AppCompatCheckedTextView extends android.widget.CheckedTextView {
- ctor public AppCompatCheckedTextView(android.content.Context!);
- ctor public AppCompatCheckedTextView(android.content.Context!, android.util.AttributeSet!);
- ctor public AppCompatCheckedTextView(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public AppCompatCheckedTextView(android.content.Context);
+ ctor public AppCompatCheckedTextView(android.content.Context, android.util.AttributeSet?);
+ ctor public AppCompatCheckedTextView(android.content.Context, android.util.AttributeSet?, int);
method public void setTextAppearance(android.content.Context!, int);
}
public class AppCompatEditText extends android.widget.EditText implements androidx.core.view.TintableBackgroundView {
- ctor public AppCompatEditText(android.content.Context!);
- ctor public AppCompatEditText(android.content.Context!, android.util.AttributeSet!);
- ctor public AppCompatEditText(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public AppCompatEditText(android.content.Context);
+ ctor public AppCompatEditText(android.content.Context, android.util.AttributeSet?);
+ ctor public AppCompatEditText(android.content.Context, android.util.AttributeSet?, int);
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.res.ColorStateList? getSupportBackgroundTintList();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.graphics.PorterDuff.Mode? getSupportBackgroundTintMode();
method public void setBackgroundDrawable(android.graphics.drawable.Drawable!);
@@ -521,9 +521,9 @@
}
public class AppCompatImageButton extends android.widget.ImageButton implements androidx.core.view.TintableBackgroundView androidx.core.widget.TintableImageSourceView {
- ctor public AppCompatImageButton(android.content.Context!);
- ctor public AppCompatImageButton(android.content.Context!, android.util.AttributeSet!);
- ctor public AppCompatImageButton(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public AppCompatImageButton(android.content.Context);
+ ctor public AppCompatImageButton(android.content.Context, android.util.AttributeSet?);
+ ctor public AppCompatImageButton(android.content.Context, android.util.AttributeSet?, int);
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.res.ColorStateList? getSupportBackgroundTintList();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.graphics.PorterDuff.Mode? getSupportBackgroundTintMode();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.res.ColorStateList? getSupportImageTintList();
@@ -536,9 +536,9 @@
}
public class AppCompatImageView extends android.widget.ImageView implements androidx.core.view.TintableBackgroundView androidx.core.widget.TintableImageSourceView {
- ctor public AppCompatImageView(android.content.Context!);
- ctor public AppCompatImageView(android.content.Context!, android.util.AttributeSet!);
- ctor public AppCompatImageView(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public AppCompatImageView(android.content.Context);
+ ctor public AppCompatImageView(android.content.Context, android.util.AttributeSet?);
+ ctor public AppCompatImageView(android.content.Context, android.util.AttributeSet?, int);
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.res.ColorStateList? getSupportBackgroundTintList();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.graphics.PorterDuff.Mode? getSupportBackgroundTintMode();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.res.ColorStateList? getSupportImageTintList();
@@ -551,9 +551,9 @@
}
public class AppCompatMultiAutoCompleteTextView extends android.widget.MultiAutoCompleteTextView implements androidx.core.view.TintableBackgroundView {
- ctor public AppCompatMultiAutoCompleteTextView(android.content.Context!);
- ctor public AppCompatMultiAutoCompleteTextView(android.content.Context!, android.util.AttributeSet!);
- ctor public AppCompatMultiAutoCompleteTextView(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public AppCompatMultiAutoCompleteTextView(android.content.Context);
+ ctor public AppCompatMultiAutoCompleteTextView(android.content.Context, android.util.AttributeSet?);
+ ctor public AppCompatMultiAutoCompleteTextView(android.content.Context, android.util.AttributeSet?, int);
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.res.ColorStateList? getSupportBackgroundTintList();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.graphics.PorterDuff.Mode? getSupportBackgroundTintMode();
method public void setBackgroundDrawable(android.graphics.drawable.Drawable!);
@@ -564,8 +564,8 @@
public class AppCompatRadioButton extends android.widget.RadioButton implements androidx.core.view.TintableBackgroundView androidx.core.widget.TintableCompoundButton {
ctor public AppCompatRadioButton(android.content.Context!);
- ctor public AppCompatRadioButton(android.content.Context!, android.util.AttributeSet!);
- ctor public AppCompatRadioButton(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public AppCompatRadioButton(android.content.Context!, android.util.AttributeSet?);
+ ctor public AppCompatRadioButton(android.content.Context!, android.util.AttributeSet?, int);
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.res.ColorStateList? getSupportBackgroundTintList();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.graphics.PorterDuff.Mode? getSupportBackgroundTintMode();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.res.ColorStateList? getSupportButtonTintList();
@@ -578,24 +578,24 @@
}
public class AppCompatRatingBar extends android.widget.RatingBar {
- ctor public AppCompatRatingBar(android.content.Context!);
- ctor public AppCompatRatingBar(android.content.Context!, android.util.AttributeSet!);
- ctor public AppCompatRatingBar(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public AppCompatRatingBar(android.content.Context);
+ ctor public AppCompatRatingBar(android.content.Context, android.util.AttributeSet?);
+ ctor public AppCompatRatingBar(android.content.Context, android.util.AttributeSet?, int);
}
public class AppCompatSeekBar extends android.widget.SeekBar {
- ctor public AppCompatSeekBar(android.content.Context!);
- ctor public AppCompatSeekBar(android.content.Context!, android.util.AttributeSet!);
- ctor public AppCompatSeekBar(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public AppCompatSeekBar(android.content.Context);
+ ctor public AppCompatSeekBar(android.content.Context, android.util.AttributeSet?);
+ ctor public AppCompatSeekBar(android.content.Context, android.util.AttributeSet?, int);
}
public class AppCompatSpinner extends android.widget.Spinner implements androidx.core.view.TintableBackgroundView {
- ctor public AppCompatSpinner(android.content.Context!);
- ctor public AppCompatSpinner(android.content.Context!, int);
- ctor public AppCompatSpinner(android.content.Context!, android.util.AttributeSet!);
- ctor public AppCompatSpinner(android.content.Context!, android.util.AttributeSet!, int);
- ctor public AppCompatSpinner(android.content.Context!, android.util.AttributeSet!, int, int);
- ctor public AppCompatSpinner(android.content.Context!, android.util.AttributeSet!, int, int, android.content.res.Resources.Theme!);
+ ctor public AppCompatSpinner(android.content.Context);
+ ctor public AppCompatSpinner(android.content.Context, int);
+ ctor public AppCompatSpinner(android.content.Context, android.util.AttributeSet?);
+ ctor public AppCompatSpinner(android.content.Context, android.util.AttributeSet?, int);
+ ctor public AppCompatSpinner(android.content.Context, android.util.AttributeSet?, int, int);
+ ctor public AppCompatSpinner(android.content.Context, android.util.AttributeSet?, int, int, android.content.res.Resources.Theme!);
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.res.ColorStateList? getSupportBackgroundTintList();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.graphics.PorterDuff.Mode? getSupportBackgroundTintMode();
method public void setBackgroundDrawable(android.graphics.drawable.Drawable!);
@@ -604,9 +604,9 @@
}
public class AppCompatTextView extends android.widget.TextView implements androidx.core.widget.AutoSizeableTextView androidx.core.view.TintableBackgroundView androidx.core.widget.TintableCompoundDrawablesView {
- ctor public AppCompatTextView(android.content.Context!);
- ctor public AppCompatTextView(android.content.Context!, android.util.AttributeSet!);
- ctor public AppCompatTextView(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public AppCompatTextView(android.content.Context);
+ ctor public AppCompatTextView(android.content.Context, android.util.AttributeSet?);
+ ctor public AppCompatTextView(android.content.Context, android.util.AttributeSet?, int);
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.res.ColorStateList? getSupportBackgroundTintList();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.graphics.PorterDuff.Mode? getSupportBackgroundTintMode();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.res.ColorStateList? getSupportCompoundDrawablesTintList();
@@ -626,15 +626,15 @@
}
public class AppCompatToggleButton extends android.widget.ToggleButton {
- ctor public AppCompatToggleButton(android.content.Context!);
- ctor public AppCompatToggleButton(android.content.Context!, android.util.AttributeSet!);
- ctor public AppCompatToggleButton(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public AppCompatToggleButton(android.content.Context);
+ ctor public AppCompatToggleButton(android.content.Context, android.util.AttributeSet?);
+ ctor public AppCompatToggleButton(android.content.Context, android.util.AttributeSet?, int);
}
public class LinearLayoutCompat extends android.view.ViewGroup {
- ctor public LinearLayoutCompat(android.content.Context!);
- ctor public LinearLayoutCompat(android.content.Context!, android.util.AttributeSet!);
- ctor public LinearLayoutCompat(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public LinearLayoutCompat(android.content.Context);
+ ctor public LinearLayoutCompat(android.content.Context, android.util.AttributeSet?);
+ ctor public LinearLayoutCompat(android.content.Context, android.util.AttributeSet?, int);
method protected androidx.appcompat.widget.LinearLayoutCompat.LayoutParams! generateDefaultLayoutParams();
method public androidx.appcompat.widget.LinearLayoutCompat.LayoutParams! generateLayoutParams(android.util.AttributeSet!);
method protected androidx.appcompat.widget.LinearLayoutCompat.LayoutParams! generateLayoutParams(android.view.ViewGroup.LayoutParams!);
@@ -766,9 +766,9 @@
}
public class SearchView extends androidx.appcompat.widget.LinearLayoutCompat implements androidx.appcompat.view.CollapsibleActionView {
- ctor public SearchView(android.content.Context!);
- ctor public SearchView(android.content.Context!, android.util.AttributeSet!);
- ctor public SearchView(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public SearchView(android.content.Context);
+ ctor public SearchView(android.content.Context, android.util.AttributeSet?);
+ ctor public SearchView(android.content.Context, android.util.AttributeSet?, int);
method public int getImeOptions();
method public int getInputType();
method public int getMaxWidth();
@@ -827,9 +827,9 @@
}
public class SwitchCompat extends android.widget.CompoundButton {
- ctor public SwitchCompat(android.content.Context!);
- ctor public SwitchCompat(android.content.Context!, android.util.AttributeSet!);
- ctor public SwitchCompat(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public SwitchCompat(android.content.Context);
+ ctor public SwitchCompat(android.content.Context, android.util.AttributeSet?);
+ ctor public SwitchCompat(android.content.Context, android.util.AttributeSet?, int);
method public boolean getShowText();
method public boolean getSplitTrack();
method public int getSwitchMinWidth();
@@ -877,9 +877,9 @@
}
public class Toolbar extends android.view.ViewGroup {
- ctor public Toolbar(android.content.Context!);
- ctor public Toolbar(android.content.Context!, android.util.AttributeSet?);
- ctor public Toolbar(android.content.Context!, android.util.AttributeSet?, int);
+ ctor public Toolbar(android.content.Context);
+ ctor public Toolbar(android.content.Context, android.util.AttributeSet?);
+ ctor public Toolbar(android.content.Context, android.util.AttributeSet?, int);
method public void collapseActionView();
method public void dismissPopupMenus();
method protected androidx.appcompat.widget.Toolbar.LayoutParams! generateDefaultLayoutParams();
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/build.gradle b/appcompat/build.gradle
index 5ce2ab6..e18e0bd9 100644
--- a/appcompat/build.gradle
+++ b/appcompat/build.gradle
@@ -12,7 +12,7 @@
dependencies {
api("androidx.annotation:annotation:1.1.0")
- api("androidx.core:core:1.1.0-rc01")
+ api("androidx.core:core:1.1.0")
implementation("androidx.collection:collection:1.0.0")
api("androidx.cursoradapter:cursoradapter:1.0.0")
api("androidx.fragment:fragment:1.1.0-rc01")
diff --git a/appcompat/lint-baseline.xml b/appcompat/lint-baseline.xml
index 9ac6bc0..222abda 100644
--- a/appcompat/lint-baseline.xml
+++ b/appcompat/lint-baseline.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 3.5.0-beta04" client="gradle" variant="debug" version="3.5.0-beta04">
+<issues format="5" by="lint 3.5.0-beta05" client="gradle" variant="debug" version="3.5.0-beta05">
<issue
id="KotlinPropertyAccess"
@@ -23,7 +23,7 @@
errorLine2=" ~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarOverlayLayout.java"
- line="194"
+ line="196"
column="20"/>
</issue>
@@ -1025,66 +1025,11 @@
<issue
id="UnknownNullness"
message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public ActionBarContextView(Context context) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/ActionBarContextView.java"
- line="57"
- column="33"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public ActionBarContextView(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/ActionBarContextView.java"
- line="61"
- column="33"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public ActionBarContextView(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/ActionBarContextView.java"
- line="61"
- column="50"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public ActionBarContextView(Context context, AttributeSet attrs, int defStyle) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/ActionBarContextView.java"
- line="65"
- column="33"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public ActionBarContextView(Context context, AttributeSet attrs, int defStyle) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/ActionBarContextView.java"
- line="65"
- column="50"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
errorLine1=" public void setCustomView(View view) {"
errorLine2=" ~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarContextView.java"
- line="100"
+ line="101"
column="31"/>
</issue>
@@ -1095,7 +1040,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarContextView.java"
- line="115"
+ line="116"
column="26"/>
</issue>
@@ -1106,7 +1051,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarContextView.java"
- line="120"
+ line="121"
column="29"/>
</issue>
@@ -1117,7 +1062,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarContextView.java"
- line="125"
+ line="126"
column="12"/>
</issue>
@@ -1128,7 +1073,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarContextView.java"
- line="129"
+ line="130"
column="12"/>
</issue>
@@ -1139,7 +1084,7 @@
errorLine2=" ~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarContextView.java"
- line="160"
+ line="161"
column="35"/>
</issue>
@@ -1150,7 +1095,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarContextView.java"
- line="230"
+ line="231"
column="15"/>
</issue>
@@ -1161,7 +1106,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarContextView.java"
- line="237"
+ line="238"
column="12"/>
</issue>
@@ -1172,7 +1117,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarContextView.java"
- line="237"
+ line="238"
column="56"/>
</issue>
@@ -1183,7 +1128,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarContextView.java"
- line="358"
+ line="359"
column="48"/>
</issue>
@@ -1366,44 +1311,11 @@
<issue
id="UnknownNullness"
message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public ActionBarOverlayLayout(Context context) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/ActionBarOverlayLayout.java"
- line="138"
- column="35"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public ActionBarOverlayLayout(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/ActionBarOverlayLayout.java"
- line="142"
- column="35"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public ActionBarOverlayLayout(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/ActionBarOverlayLayout.java"
- line="142"
- column="52"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
errorLine1=" public void setActionBarVisibilityCallback(ActionBarVisibilityCallback cb) {"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarOverlayLayout.java"
- line="168"
+ line="170"
column="48"/>
</issue>
@@ -1414,7 +1326,7 @@
errorLine2=" ~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarOverlayLayout.java"
- line="224"
+ line="226"
column="43"/>
</issue>
@@ -1425,7 +1337,7 @@
errorLine2=" ~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarOverlayLayout.java"
- line="288"
+ line="290"
column="40"/>
</issue>
@@ -1436,7 +1348,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarOverlayLayout.java"
- line="321"
+ line="323"
column="15"/>
</issue>
@@ -1447,7 +1359,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarOverlayLayout.java"
- line="326"
+ line="328"
column="12"/>
</issue>
@@ -1458,7 +1370,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarOverlayLayout.java"
- line="326"
+ line="328"
column="46"/>
</issue>
@@ -1469,7 +1381,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarOverlayLayout.java"
- line="331"
+ line="333"
column="15"/>
</issue>
@@ -1480,7 +1392,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarOverlayLayout.java"
- line="331"
+ line="333"
column="59"/>
</issue>
@@ -1491,7 +1403,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarOverlayLayout.java"
- line="336"
+ line="338"
column="41"/>
</issue>
@@ -1502,7 +1414,7 @@
errorLine2=" ~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarOverlayLayout.java"
- line="452"
+ line="454"
column="22"/>
</issue>
@@ -1513,7 +1425,7 @@
errorLine2=" ~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarOverlayLayout.java"
- line="472"
+ line="474"
column="32"/>
</issue>
@@ -1524,7 +1436,7 @@
errorLine2=" ~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarOverlayLayout.java"
- line="473"
+ line="475"
column="41"/>
</issue>
@@ -1535,7 +1447,7 @@
errorLine2=" ~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarOverlayLayout.java"
- line="480"
+ line="482"
column="40"/>
</issue>
@@ -1546,7 +1458,7 @@
errorLine2=" ~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarOverlayLayout.java"
- line="480"
+ line="482"
column="52"/>
</issue>
@@ -1557,7 +1469,7 @@
errorLine2=" ~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarOverlayLayout.java"
- line="485"
+ line="487"
column="40"/>
</issue>
@@ -1568,7 +1480,7 @@
errorLine2=" ~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarOverlayLayout.java"
- line="485"
+ line="487"
column="52"/>
</issue>
@@ -1579,7 +1491,7 @@
errorLine2=" ~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarOverlayLayout.java"
- line="492"
+ line="494"
column="36"/>
</issue>
@@ -1590,7 +1502,7 @@
errorLine2=" ~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarOverlayLayout.java"
- line="499"
+ line="501"
column="32"/>
</issue>
@@ -1601,7 +1513,7 @@
errorLine2=" ~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarOverlayLayout.java"
- line="507"
+ line="509"
column="35"/>
</issue>
@@ -1612,7 +1524,7 @@
errorLine2=" ~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarOverlayLayout.java"
- line="507"
+ line="509"
column="64"/>
</issue>
@@ -1623,7 +1535,7 @@
errorLine2=" ~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarOverlayLayout.java"
- line="516"
+ line="518"
column="40"/>
</issue>
@@ -1634,7 +1546,7 @@
errorLine2=" ~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarOverlayLayout.java"
- line="516"
+ line="518"
column="52"/>
</issue>
@@ -1645,7 +1557,7 @@
errorLine2=" ~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarOverlayLayout.java"
- line="524"
+ line="526"
column="40"/>
</issue>
@@ -1656,7 +1568,7 @@
errorLine2=" ~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarOverlayLayout.java"
- line="524"
+ line="526"
column="52"/>
</issue>
@@ -1667,7 +1579,7 @@
errorLine2=" ~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarOverlayLayout.java"
- line="534"
+ line="536"
column="32"/>
</issue>
@@ -1678,7 +1590,7 @@
errorLine2=" ~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarOverlayLayout.java"
- line="541"
+ line="543"
column="36"/>
</issue>
@@ -1689,7 +1601,7 @@
errorLine2=" ~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarOverlayLayout.java"
- line="555"
+ line="557"
column="34"/>
</issue>
@@ -1700,7 +1612,7 @@
errorLine2=" ~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarOverlayLayout.java"
- line="569"
+ line="571"
column="35"/>
</issue>
@@ -1711,7 +1623,7 @@
errorLine2=" ~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarOverlayLayout.java"
- line="569"
+ line="571"
column="64"/>
</issue>
@@ -1722,7 +1634,7 @@
errorLine2=" ~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarOverlayLayout.java"
- line="574"
+ line="576"
column="37"/>
</issue>
@@ -1733,7 +1645,7 @@
errorLine2=" ~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarOverlayLayout.java"
- line="662"
+ line="664"
column="35"/>
</issue>
@@ -1744,7 +1656,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarOverlayLayout.java"
- line="668"
+ line="670"
column="32"/>
</issue>
@@ -1755,7 +1667,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarOverlayLayout.java"
- line="674"
+ line="676"
column="12"/>
</issue>
@@ -1766,7 +1678,7 @@
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarOverlayLayout.java"
- line="719"
+ line="721"
column="25"/>
</issue>
@@ -1777,7 +1689,7 @@
errorLine2=" ~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarOverlayLayout.java"
- line="767"
+ line="769"
column="25"/>
</issue>
@@ -1788,7 +1700,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarOverlayLayout.java"
- line="767"
+ line="769"
column="36"/>
</issue>
@@ -1799,7 +1711,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarOverlayLayout.java"
- line="773"
+ line="775"
column="43"/>
</issue>
@@ -1810,7 +1722,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarOverlayLayout.java"
- line="779"
+ line="781"
column="46"/>
</issue>
@@ -1821,7 +1733,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarOverlayLayout.java"
- line="791"
+ line="793"
column="29"/>
</issue>
@@ -1832,7 +1744,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarOverlayLayout.java"
- line="791"
+ line="793"
column="40"/>
</issue>
@@ -1843,7 +1755,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarOverlayLayout.java"
- line="799"
+ line="801"
column="29"/>
</issue>
@@ -1854,7 +1766,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionBarOverlayLayout.java"
- line="803"
+ line="805"
column="29"/>
</issue>
@@ -2664,44 +2576,11 @@
<issue
id="UnknownNullness"
message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public ActionMenuView(Context context) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/ActionMenuView.java"
- line="75"
- column="27"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public ActionMenuView(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/ActionMenuView.java"
- line="79"
- column="27"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public ActionMenuView(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/ActionMenuView.java"
- line="79"
- column="44"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
errorLine1=" public void setPresenter(ActionMenuPresenter presenter) {"
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionMenuView.java"
- line="121"
+ line="122"
column="30"/>
</issue>
@@ -2712,7 +2591,7 @@
errorLine2=" ~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionMenuView.java"
- line="127"
+ line="128"
column="40"/>
</issue>
@@ -2723,7 +2602,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionMenuView.java"
- line="140"
+ line="141"
column="44"/>
</issue>
@@ -2734,7 +2613,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionMenuView.java"
- line="582"
+ line="583"
column="15"/>
</issue>
@@ -2745,7 +2624,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionMenuView.java"
- line="590"
+ line="591"
column="12"/>
</issue>
@@ -2756,7 +2635,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionMenuView.java"
- line="590"
+ line="591"
column="46"/>
</issue>
@@ -2767,7 +2646,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionMenuView.java"
- line="595"
+ line="596"
column="15"/>
</issue>
@@ -2778,7 +2657,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionMenuView.java"
- line="595"
+ line="596"
column="49"/>
</issue>
@@ -2789,7 +2668,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionMenuView.java"
- line="609"
+ line="610"
column="41"/>
</issue>
@@ -2800,7 +2679,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionMenuView.java"
- line="615"
+ line="616"
column="12"/>
</issue>
@@ -2811,7 +2690,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionMenuView.java"
- line="624"
+ line="625"
column="31"/>
</issue>
@@ -2822,7 +2701,7 @@
errorLine2=" ~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionMenuView.java"
- line="638"
+ line="639"
column="28"/>
</issue>
@@ -2833,7 +2712,7 @@
errorLine2=" ~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionMenuView.java"
- line="650"
+ line="651"
column="12"/>
</issue>
@@ -2844,7 +2723,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionMenuView.java"
- line="671"
+ line="672"
column="34"/>
</issue>
@@ -2855,7 +2734,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionMenuView.java"
- line="671"
+ line="672"
column="62"/>
</issue>
@@ -2866,7 +2745,7 @@
errorLine2=" ~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionMenuView.java"
- line="681"
+ line="682"
column="12"/>
</issue>
@@ -2877,7 +2756,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionMenuView.java"
- line="749"
+ line="750"
column="55"/>
</issue>
@@ -2888,7 +2767,7 @@
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionMenuView.java"
- line="771"
+ line="772"
column="40"/>
</issue>
@@ -2899,7 +2778,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionMenuView.java"
- line="832"
+ line="833"
column="29"/>
</issue>
@@ -2910,7 +2789,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionMenuView.java"
- line="832"
+ line="833"
column="40"/>
</issue>
@@ -2921,7 +2800,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionMenuView.java"
- line="836"
+ line="837"
column="29"/>
</issue>
@@ -2932,7 +2811,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActionMenuView.java"
- line="840"
+ line="841"
column="29"/>
</issue>
@@ -3236,66 +3115,11 @@
<issue
id="UnknownNullness"
message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public ActivityChooserView(Context context) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/ActivityChooserView.java"
- line="200"
- column="32"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public ActivityChooserView(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/ActivityChooserView.java"
- line="210"
- column="32"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public ActivityChooserView(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/ActivityChooserView.java"
- line="210"
- column="49"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public ActivityChooserView(Context context, AttributeSet attrs, int defStyle) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/ActivityChooserView.java"
- line="221"
- column="32"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public ActivityChooserView(Context context, AttributeSet attrs, int defStyle) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/ActivityChooserView.java"
- line="221"
- column="49"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
errorLine1=" public void setActivityChooserModel(ActivityChooserModel dataModel) {"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActivityChooserView.java"
- line="303"
+ line="306"
column="41"/>
</issue>
@@ -3306,7 +3130,7 @@
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActivityChooserView.java"
- line="322"
+ line="325"
column="57"/>
</issue>
@@ -3317,7 +3141,7 @@
errorLine2=" ~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActivityChooserView.java"
- line="346"
+ line="349"
column="29"/>
</issue>
@@ -3328,7 +3152,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActivityChooserView.java"
- line="483"
+ line="486"
column="12"/>
</issue>
@@ -3339,7 +3163,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActivityChooserView.java"
- line="492"
+ line="495"
column="38"/>
</issue>
@@ -3350,7 +3174,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActivityChooserView.java"
- line="873"
+ line="876"
column="28"/>
</issue>
@@ -3361,7 +3185,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ActivityChooserView.java"
- line="873"
+ line="876"
column="45"/>
</issue>
@@ -5040,66 +4864,11 @@
<issue
id="UnknownNullness"
message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatAutoCompleteTextView(Context context) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatAutoCompleteTextView.java"
- line="65"
- column="42"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatAutoCompleteTextView(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatAutoCompleteTextView.java"
- line="69"
- column="42"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatAutoCompleteTextView(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatAutoCompleteTextView.java"
- line="69"
- column="59"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatAutoCompleteTextView(Context context, AttributeSet attrs, int defStyleAttr) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatAutoCompleteTextView.java"
- line="73"
- column="42"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatAutoCompleteTextView(Context context, AttributeSet attrs, int defStyleAttr) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatAutoCompleteTextView.java"
- line="73"
- column="59"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
errorLine1=" public void setBackgroundDrawable(Drawable background) {"
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatAutoCompleteTextView.java"
- line="105"
+ line="107"
column="39"/>
</issue>
@@ -5110,7 +4879,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatAutoCompleteTextView.java"
- line="180"
+ line="182"
column="35"/>
</issue>
@@ -5121,7 +4890,7 @@
errorLine2=" ~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatAutoCompleteTextView.java"
- line="188"
+ line="190"
column="12"/>
</issue>
@@ -5132,7 +4901,7 @@
errorLine2=" ~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatAutoCompleteTextView.java"
- line="188"
+ line="190"
column="52"/>
</issue>
@@ -5143,73 +4912,18 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatAutoCompleteTextView.java"
- line="198"
+ line="200"
column="54"/>
</issue>
<issue
id="UnknownNullness"
message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatButton(Context context) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatButton.java"
- line="63"
- column="28"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatButton(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatButton.java"
- line="67"
- column="28"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatButton(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatButton.java"
- line="67"
- column="45"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatButton(Context context, AttributeSet attrs, int defStyleAttr) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatButton.java"
- line="71"
- column="28"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatButton(Context context, AttributeSet attrs, int defStyleAttr) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatButton.java"
- line="71"
- column="45"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
errorLine1=" public void setBackgroundDrawable(Drawable background) {"
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatButton.java"
- line="91"
+ line="92"
column="39"/>
</issue>
@@ -5220,7 +4934,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatButton.java"
- line="166"
+ line="167"
column="35"/>
</issue>
@@ -5231,7 +4945,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatButton.java"
- line="174"
+ line="175"
column="48"/>
</issue>
@@ -5242,7 +4956,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatButton.java"
- line="180"
+ line="181"
column="51"/>
</issue>
@@ -5253,7 +4967,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatButton.java"
- line="205"
+ line="206"
column="34"/>
</issue>
@@ -5264,7 +4978,7 @@
errorLine2=" ~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatButton.java"
- line="339"
+ line="340"
column="12"/>
</issue>
@@ -5275,7 +4989,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatButton.java"
- line="370"
+ line="371"
column="54"/>
</issue>
@@ -5315,66 +5029,11 @@
<issue
id="UnknownNullness"
message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatCheckBox(Context context) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatCheckBox.java"
- line="60"
- column="30"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatCheckBox(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatCheckBox.java"
- line="64"
- column="30"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatCheckBox(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatCheckBox.java"
- line="64"
- column="47"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatCheckBox(Context context, AttributeSet attrs, int defStyleAttr) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatCheckBox.java"
- line="68"
- column="30"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatCheckBox(Context context, AttributeSet attrs, int defStyleAttr) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatCheckBox.java"
- line="68"
- column="47"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
errorLine1=" public void setButtonDrawable(Drawable buttonDrawable) {"
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatCheckBox.java"
- line="81"
+ line="83"
column="35"/>
</issue>
@@ -5385,73 +5044,18 @@
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatCheckBox.java"
- line="207"
+ line="209"
column="39"/>
</issue>
<issue
id="UnknownNullness"
message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatCheckedTextView(Context context) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatCheckedTextView.java"
- line="46"
- column="37"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatCheckedTextView(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatCheckedTextView.java"
- line="50"
- column="37"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatCheckedTextView(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatCheckedTextView.java"
- line="50"
- column="54"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatCheckedTextView(Context context, AttributeSet attrs, int defStyleAttr) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatCheckedTextView.java"
- line="54"
- column="37"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatCheckedTextView(Context context, AttributeSet attrs, int defStyleAttr) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatCheckedTextView.java"
- line="54"
- column="54"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
errorLine1=" public void setTextAppearance(Context context, int resId) {"
errorLine2=" ~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatCheckedTextView.java"
- line="73"
+ line="76"
column="35"/>
</issue>
@@ -5462,7 +5066,7 @@
errorLine2=" ~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatCheckedTextView.java"
- line="89"
+ line="92"
column="12"/>
</issue>
@@ -5473,7 +5077,7 @@
errorLine2=" ~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatCheckedTextView.java"
- line="89"
+ line="92"
column="52"/>
</issue>
@@ -5484,7 +5088,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatCheckedTextView.java"
- line="99"
+ line="102"
column="54"/>
</issue>
@@ -5865,66 +5469,11 @@
<issue
id="UnknownNullness"
message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatEditText(Context context) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatEditText.java"
- line="64"
- column="30"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatEditText(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatEditText.java"
- line="68"
- column="30"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatEditText(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatEditText.java"
- line="68"
- column="47"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatEditText(Context context, AttributeSet attrs, int defStyleAttr) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatEditText.java"
- line="72"
- column="30"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatEditText(Context context, AttributeSet attrs, int defStyleAttr) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatEditText.java"
- line="72"
- column="47"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
errorLine1=" public void setBackgroundDrawable(Drawable background) {"
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatEditText.java"
- line="108"
+ line="109"
column="39"/>
</issue>
@@ -5935,7 +5484,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatEditText.java"
- line="183"
+ line="184"
column="35"/>
</issue>
@@ -5946,7 +5495,7 @@
errorLine2=" ~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatEditText.java"
- line="191"
+ line="192"
column="12"/>
</issue>
@@ -5957,7 +5506,7 @@
errorLine2=" ~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatEditText.java"
- line="191"
+ line="192"
column="52"/>
</issue>
@@ -5968,73 +5517,18 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatEditText.java"
- line="201"
+ line="202"
column="54"/>
</issue>
<issue
id="UnknownNullness"
message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatImageButton(Context context) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatImageButton.java"
- line="64"
- column="33"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatImageButton(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatImageButton.java"
- line="68"
- column="33"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatImageButton(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatImageButton.java"
- line="68"
- column="50"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatImageButton(Context context, AttributeSet attrs, int defStyleAttr) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatImageButton.java"
- line="72"
- column="33"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatImageButton(Context context, AttributeSet attrs, int defStyleAttr) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatImageButton.java"
- line="72"
- column="50"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
errorLine1=" public void setImageBitmap(Bitmap bm) {"
errorLine2=" ~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatImageButton.java"
- line="97"
+ line="99"
column="32"/>
</issue>
@@ -6045,95 +5539,29 @@
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatImageButton.java"
- line="121"
+ line="123"
column="39"/>
</issue>
<issue
id="UnknownNullness"
message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatImageHelper(ImageView view) {"
- errorLine2=" ~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatImageHelper.java"
- line="45"
- column="33"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
errorLine1=" public void loadFromAttributes(AttributeSet attrs, int defStyleAttr) {"
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatImageHelper.java"
- line="49"
+ line="50"
column="36"/>
</issue>
<issue
id="UnknownNullness"
message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatImageView(Context context) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatImageView.java"
- line="63"
- column="31"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatImageView(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatImageView.java"
- line="67"
- column="31"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatImageView(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatImageView.java"
- line="67"
- column="48"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatImageView(Context context, AttributeSet attrs, int defStyleAttr) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatImageView.java"
- line="71"
- column="31"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatImageView(Context context, AttributeSet attrs, int defStyleAttr) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatImageView.java"
- line="71"
- column="48"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
errorLine1=" public void setImageBitmap(Bitmap bm) {"
errorLine2=" ~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatImageView.java"
- line="107"
+ line="109"
column="32"/>
</issue>
@@ -6144,73 +5572,18 @@
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatImageView.java"
- line="131"
+ line="133"
column="39"/>
</issue>
<issue
id="UnknownNullness"
message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatMultiAutoCompleteTextView(Context context) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatMultiAutoCompleteTextView.java"
- line="62"
- column="47"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatMultiAutoCompleteTextView(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatMultiAutoCompleteTextView.java"
- line="66"
- column="47"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatMultiAutoCompleteTextView(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatMultiAutoCompleteTextView.java"
- line="66"
- column="64"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatMultiAutoCompleteTextView(Context context, AttributeSet attrs, int defStyleAttr) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatMultiAutoCompleteTextView.java"
- line="70"
- column="47"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatMultiAutoCompleteTextView(Context context, AttributeSet attrs, int defStyleAttr) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatMultiAutoCompleteTextView.java"
- line="70"
- column="64"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
errorLine1=" public void setBackgroundDrawable(Drawable background) {"
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatMultiAutoCompleteTextView.java"
- line="102"
+ line="105"
column="39"/>
</issue>
@@ -6221,7 +5594,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatMultiAutoCompleteTextView.java"
- line="177"
+ line="180"
column="35"/>
</issue>
@@ -6232,7 +5605,7 @@
errorLine2=" ~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatMultiAutoCompleteTextView.java"
- line="185"
+ line="188"
column="12"/>
</issue>
@@ -6243,7 +5616,7 @@
errorLine2=" ~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatMultiAutoCompleteTextView.java"
- line="185"
+ line="188"
column="52"/>
</issue>
@@ -6261,7 +5634,7 @@
<issue
id="UnknownNullness"
message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatRadioButton(Context context, AttributeSet attrs) {"
+ errorLine1=" public AppCompatRadioButton(Context context, @Nullable AttributeSet attrs) {"
errorLine2=" ~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatRadioButton.java"
@@ -6272,18 +5645,7 @@
<issue
id="UnknownNullness"
message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatRadioButton(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatRadioButton.java"
- line="64"
- column="50"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatRadioButton(Context context, AttributeSet attrs, int defStyleAttr) {"
+ errorLine1=" public AppCompatRadioButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {"
errorLine2=" ~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatRadioButton.java"
@@ -6294,17 +5656,6 @@
<issue
id="UnknownNullness"
message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatRadioButton(Context context, AttributeSet attrs, int defStyleAttr) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatRadioButton.java"
- line="68"
- column="50"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
errorLine1=" public void setButtonDrawable(Drawable buttonDrawable) {"
errorLine2=" ~~~~~~~~">
<location
@@ -6327,243 +5678,23 @@
<issue
id="UnknownNullness"
message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatRatingBar(Context context) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatRatingBar.java"
- line="39"
- column="31"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatRatingBar(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatRatingBar.java"
- line="43"
- column="31"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatRatingBar(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatRatingBar.java"
- line="43"
- column="48"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatRatingBar(Context context, AttributeSet attrs, int defStyleAttr) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatRatingBar.java"
- line="47"
- column="31"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatRatingBar(Context context, AttributeSet attrs, int defStyleAttr) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatRatingBar.java"
- line="47"
- column="48"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatSeekBar(Context context) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatSeekBar.java"
- line="38"
- column="29"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatSeekBar(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatSeekBar.java"
- line="42"
- column="29"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatSeekBar(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatSeekBar.java"
- line="42"
- column="46"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatSeekBar.java"
- line="46"
- column="29"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatSeekBar.java"
- line="46"
- column="46"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
errorLine1=" protected synchronized void onDraw(Canvas canvas) {"
errorLine2=" ~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatSeekBar.java"
- line="54"
+ line="57"
column="40"/>
</issue>
<issue
id="UnknownNullness"
message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatSpinner(Context context) {"
- errorLine2=" ~~~~~~~">
+ errorLine1=" int defStyleAttr, int mode, Resources.Theme popupTheme) {"
+ errorLine2=" ~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatSpinner.java"
- line="113"
- column="29"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatSpinner(Context context, int mode) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatSpinner.java"
- line="128"
- column="29"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatSpinner(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatSpinner.java"
- line="139"
- column="29"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatSpinner(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatSpinner.java"
- line="139"
- column="46"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatSpinner(Context context, AttributeSet attrs, int defStyleAttr) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatSpinner.java"
- line="154"
- column="29"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatSpinner(Context context, AttributeSet attrs, int defStyleAttr) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatSpinner.java"
- line="154"
- column="46"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatSpinner(Context context, AttributeSet attrs, int defStyleAttr, int mode) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatSpinner.java"
- line="173"
- column="29"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatSpinner(Context context, AttributeSet attrs, int defStyleAttr, int mode) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatSpinner.java"
- line="173"
- column="46"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatSpinner(Context context, AttributeSet attrs, int defStyleAttr, int mode,"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatSpinner.java"
- line="201"
- column="29"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatSpinner(Context context, AttributeSet attrs, int defStyleAttr, int mode,"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatSpinner.java"
- line="201"
- column="46"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" Resources.Theme popupTheme) {"
- errorLine2=" ~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatSpinner.java"
- line="202"
- column="13"/>
+ line="208"
+ column="41"/>
</issue>
<issue
@@ -6573,7 +5704,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatSpinner.java"
- line="300"
+ line="306"
column="12"/>
</issue>
@@ -6584,7 +5715,7 @@
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatSpinner.java"
- line="305"
+ line="311"
column="44"/>
</issue>
@@ -6595,7 +5726,7 @@
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatSpinner.java"
- line="319"
+ line="325"
column="12"/>
</issue>
@@ -6606,7 +5737,7 @@
errorLine2=" ~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatSpinner.java"
- line="393"
+ line="399"
column="28"/>
</issue>
@@ -6617,7 +5748,7 @@
errorLine2=" ~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatSpinner.java"
- line="419"
+ line="425"
column="33"/>
</issue>
@@ -6628,7 +5759,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatSpinner.java"
- line="454"
+ line="460"
column="27"/>
</issue>
@@ -6639,7 +5770,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatSpinner.java"
- line="463"
+ line="469"
column="12"/>
</issue>
@@ -6650,7 +5781,7 @@
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatSpinner.java"
- line="476"
+ line="482"
column="39"/>
</issue>
@@ -6661,7 +5792,7 @@
errorLine2=" ~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatSpinner.java"
- line="608"
+ line="614"
column="12"/>
</issue>
@@ -6672,73 +5803,18 @@
errorLine2=" ~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatSpinner.java"
- line="616"
+ line="622"
column="40"/>
</issue>
<issue
id="UnknownNullness"
message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatTextView(Context context) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatTextView.java"
- line="90"
- column="30"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatTextView(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatTextView.java"
- line="94"
- column="30"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatTextView(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatTextView.java"
- line="94"
- column="47"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatTextView(Context context, AttributeSet attrs, int defStyleAttr) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatTextView.java"
- line="98"
- column="30"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatTextView(Context context, AttributeSet attrs, int defStyleAttr) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatTextView.java"
- line="98"
- column="47"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
errorLine1=" public void setBackgroundDrawable(Drawable background) {"
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatTextView.java"
- line="120"
+ line="121"
column="39"/>
</issue>
@@ -6749,7 +5825,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatTextView.java"
- line="184"
+ line="185"
column="35"/>
</issue>
@@ -6760,7 +5836,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatTextView.java"
- line="222"
+ line="223"
column="34"/>
</issue>
@@ -6771,7 +5847,7 @@
errorLine2=" ~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatTextView.java"
- line="383"
+ line="384"
column="12"/>
</issue>
@@ -6782,7 +5858,7 @@
errorLine2=" ~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatTextView.java"
- line="395"
+ line="396"
column="12"/>
</issue>
@@ -6793,7 +5869,7 @@
errorLine2=" ~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatTextView.java"
- line="395"
+ line="396"
column="52"/>
</issue>
@@ -6804,7 +5880,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatTextView.java"
- line="440"
+ line="441"
column="54"/>
</issue>
@@ -6815,68 +5891,13 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/AppCompatTextView.java"
- line="494"
+ line="495"
column="12"/>
</issue>
<issue
id="UnknownNullness"
message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatToggleButton(Context context) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatToggleButton.java"
- line="37"
- column="34"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatToggleButton(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatToggleButton.java"
- line="41"
- column="34"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatToggleButton(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatToggleButton.java"
- line="41"
- column="51"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatToggleButton(Context context, AttributeSet attrs, int defStyleAttr) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatToggleButton.java"
- line="45"
- column="34"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public AppCompatToggleButton(Context context, AttributeSet attrs, int defStyleAttr) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/AppCompatToggleButton.java"
- line="45"
- column="51"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
errorLine1=" protected AppCompatTextView createTextView(Context context, AttributeSet attrs) {"
errorLine2=" ~~~~~~~">
<location
@@ -7548,88 +6569,11 @@
<issue
id="UnknownNullness"
message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public ButtonBarLayout(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/ButtonBarLayout.java"
- line="50"
- column="28"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public ButtonBarLayout(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/ButtonBarLayout.java"
- line="50"
- column="45"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public ContentFrameLayout(Context context) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/ContentFrameLayout.java"
- line="58"
- column="31"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public ContentFrameLayout(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/ContentFrameLayout.java"
- line="62"
- column="31"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public ContentFrameLayout(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/ContentFrameLayout.java"
- line="62"
- column="48"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public ContentFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/ContentFrameLayout.java"
- line="66"
- column="31"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public ContentFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/ContentFrameLayout.java"
- line="66"
- column="48"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
errorLine1=" public void dispatchFitSystemWindows(Rect insets) {"
errorLine2=" ~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ContentFrameLayout.java"
- line="75"
+ line="78"
column="42"/>
</issue>
@@ -7640,7 +6584,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ContentFrameLayout.java"
- line="79"
+ line="82"
column="35"/>
</issue>
@@ -7651,7 +6595,7 @@
errorLine2=" ~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ContentFrameLayout.java"
- line="174"
+ line="177"
column="12"/>
</issue>
@@ -7662,7 +6606,7 @@
errorLine2=" ~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ContentFrameLayout.java"
- line="179"
+ line="182"
column="12"/>
</issue>
@@ -7673,7 +6617,7 @@
errorLine2=" ~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ContentFrameLayout.java"
- line="184"
+ line="187"
column="12"/>
</issue>
@@ -7684,7 +6628,7 @@
errorLine2=" ~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ContentFrameLayout.java"
- line="189"
+ line="192"
column="12"/>
</issue>
@@ -7695,7 +6639,7 @@
errorLine2=" ~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ContentFrameLayout.java"
- line="194"
+ line="197"
column="12"/>
</issue>
@@ -7706,7 +6650,7 @@
errorLine2=" ~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ContentFrameLayout.java"
- line="199"
+ line="202"
column="12"/>
</issue>
@@ -8219,61 +7163,6 @@
<issue
id="UnknownNullness"
message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public DialogTitle(Context context, AttributeSet attrs, int defStyleAttr) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/DialogTitle.java"
- line="39"
- column="24"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public DialogTitle(Context context, AttributeSet attrs, int defStyleAttr) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/DialogTitle.java"
- line="39"
- column="41"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public DialogTitle(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/DialogTitle.java"
- line="43"
- column="24"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public DialogTitle(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/DialogTitle.java"
- line="43"
- column="41"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public DialogTitle(Context context) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/DialogTitle.java"
- line="47"
- column="24"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
errorLine1=" public DrawerArrowDrawable(Context context) {"
errorLine2=" ~~~~~~~">
<location
@@ -8406,44 +7295,11 @@
<issue
id="UnknownNullness"
message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public FitWindowsFrameLayout(Context context) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/FitWindowsFrameLayout.java"
- line="36"
- column="34"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public FitWindowsFrameLayout(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/FitWindowsFrameLayout.java"
- line="40"
- column="34"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public FitWindowsFrameLayout(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/FitWindowsFrameLayout.java"
- line="40"
- column="51"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
errorLine1=" public void setOnFitSystemWindowsListener(OnFitSystemWindowsListener listener) {"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/FitWindowsFrameLayout.java"
- line="45"
+ line="47"
column="47"/>
</issue>
@@ -8454,51 +7310,18 @@
errorLine2=" ~~~~">
<location
file="src/main/java/androidx/appcompat/widget/FitWindowsFrameLayout.java"
- line="50"
+ line="52"
column="40"/>
</issue>
<issue
id="UnknownNullness"
message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public FitWindowsLinearLayout(Context context) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/FitWindowsLinearLayout.java"
- line="36"
- column="35"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public FitWindowsLinearLayout(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/FitWindowsLinearLayout.java"
- line="40"
- column="35"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public FitWindowsLinearLayout(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/FitWindowsLinearLayout.java"
- line="40"
- column="52"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
errorLine1=" public void setOnFitSystemWindowsListener(OnFitSystemWindowsListener listener) {"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/FitWindowsLinearLayout.java"
- line="45"
+ line="47"
column="47"/>
</issue>
@@ -8509,7 +7332,7 @@
errorLine2=" ~~~~">
<location
file="src/main/java/androidx/appcompat/widget/FitWindowsLinearLayout.java"
- line="50"
+ line="52"
column="40"/>
</issue>
@@ -8604,66 +7427,11 @@
<issue
id="UnknownNullness"
message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public LinearLayoutCompat(Context context) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/LinearLayoutCompat.java"
- line="148"
- column="31"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public LinearLayoutCompat(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/LinearLayoutCompat.java"
- line="152"
- column="31"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public LinearLayoutCompat(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/LinearLayoutCompat.java"
- line="152"
- column="48"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public LinearLayoutCompat(Context context, AttributeSet attrs, int defStyleAttr) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/LinearLayoutCompat.java"
- line="156"
- column="31"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public LinearLayoutCompat(Context context, AttributeSet attrs, int defStyleAttr) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/LinearLayoutCompat.java"
- line="156"
- column="48"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
errorLine1=" public Drawable getDividerDrawable() {"
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/LinearLayoutCompat.java"
- line="224"
+ line="233"
column="12"/>
</issue>
@@ -8674,7 +7442,7 @@
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/LinearLayoutCompat.java"
- line="235"
+ line="244"
column="36"/>
</issue>
@@ -8685,7 +7453,7 @@
errorLine2=" ~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/LinearLayoutCompat.java"
- line="286"
+ line="295"
column="27"/>
</issue>
@@ -8696,7 +7464,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/LinearLayoutCompat.java"
- line="1724"
+ line="1733"
column="12"/>
</issue>
@@ -8707,7 +7475,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/LinearLayoutCompat.java"
- line="1724"
+ line="1733"
column="46"/>
</issue>
@@ -8718,7 +7486,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/LinearLayoutCompat.java"
- line="1737"
+ line="1746"
column="15"/>
</issue>
@@ -8729,7 +7497,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/LinearLayoutCompat.java"
- line="1747"
+ line="1756"
column="15"/>
</issue>
@@ -8740,7 +7508,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/LinearLayoutCompat.java"
- line="1747"
+ line="1756"
column="49"/>
</issue>
@@ -8751,7 +7519,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/LinearLayoutCompat.java"
- line="1754"
+ line="1763"
column="41"/>
</issue>
@@ -8762,7 +7530,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/LinearLayoutCompat.java"
- line="1759"
+ line="1768"
column="48"/>
</issue>
@@ -8773,7 +7541,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/LinearLayoutCompat.java"
- line="1765"
+ line="1774"
column="51"/>
</issue>
@@ -8784,7 +7552,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/LinearLayoutCompat.java"
- line="1792"
+ line="1801"
column="29"/>
</issue>
@@ -8795,7 +7563,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/LinearLayoutCompat.java"
- line="1792"
+ line="1801"
column="40"/>
</issue>
@@ -8806,7 +7574,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/LinearLayoutCompat.java"
- line="1829"
+ line="1838"
column="29"/>
</issue>
@@ -8817,7 +7585,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/LinearLayoutCompat.java"
- line="1836"
+ line="1845"
column="29"/>
</issue>
@@ -8828,7 +7596,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/LinearLayoutCompat.java"
- line="1846"
+ line="1855"
column="29"/>
</issue>
@@ -11101,33 +9869,11 @@
<issue
id="UnknownNullness"
message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public MenuPopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/MenuPopupWindow.java"
- line="71"
- column="28"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public MenuPopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/MenuPopupWindow.java"
- line="71"
- column="45"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
errorLine1=" public void setEnterTransition(Object enterTransition) {"
errorLine2=" ~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/MenuPopupWindow.java"
- line="82"
+ line="85"
column="36"/>
</issue>
@@ -11138,7 +9884,7 @@
errorLine2=" ~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/MenuPopupWindow.java"
- line="88"
+ line="91"
column="35"/>
</issue>
@@ -11149,7 +9895,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/MenuPopupWindow.java"
- line="94"
+ line="97"
column="34"/>
</issue>
@@ -11160,7 +9906,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/MenuPopupWindow.java"
- line="143"
+ line="146"
column="37"/>
</issue>
@@ -11171,7 +9917,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/MenuPopupWindow.java"
- line="158"
+ line="161"
column="38"/>
</issue>
@@ -11182,7 +9928,7 @@
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/MenuPopupWindow.java"
- line="167"
+ line="170"
column="47"/>
</issue>
@@ -11193,7 +9939,7 @@
errorLine2=" ~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/MenuPopupWindow.java"
- line="188"
+ line="191"
column="37"/>
</issue>
@@ -11688,29 +10434,18 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ScrollingTabContainerView.java"
- line="74"
+ line="75"
column="15"/>
</issue>
<issue
id="UnknownNullness"
message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public ScrollingTabContainerView(Context context) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/ScrollingTabContainerView.java"
- line="81"
- column="38"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
errorLine1=" protected void onConfigurationChanged(Configuration newConfig) {"
errorLine2=" ~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ScrollingTabContainerView.java"
- line="224"
+ line="225"
column="43"/>
</issue>
@@ -11721,7 +10456,7 @@
errorLine2=" ~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ScrollingTabContainerView.java"
- line="309"
+ line="310"
column="24"/>
</issue>
@@ -11732,7 +10467,7 @@
errorLine2=" ~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ScrollingTabContainerView.java"
- line="324"
+ line="325"
column="24"/>
</issue>
@@ -11743,7 +10478,7 @@
errorLine2=" ~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ScrollingTabContainerView.java"
- line="370"
+ line="371"
column="32"/>
</issue>
@@ -11754,7 +10489,7 @@
errorLine2=" ~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ScrollingTabContainerView.java"
- line="370"
+ line="371"
column="60"/>
</issue>
@@ -11765,7 +10500,7 @@
errorLine2=" ~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ScrollingTabContainerView.java"
- line="376"
+ line="377"
column="35"/>
</issue>
@@ -11776,7 +10511,7 @@
errorLine2=" ~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/SearchView.java"
- line="216"
+ line="217"
column="35"/>
</issue>
@@ -11787,73 +10522,18 @@
errorLine2=" ~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/SearchView.java"
- line="226"
+ line="227"
column="35"/>
</issue>
<issue
id="UnknownNullness"
message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public SearchView(Context context) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/SearchView.java"
- line="266"
- column="23"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public SearchView(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/SearchView.java"
- line="270"
- column="23"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public SearchView(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/SearchView.java"
- line="270"
- column="40"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public SearchView(Context context, AttributeSet attrs, int defStyleAttr) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/SearchView.java"
- line="274"
- column="23"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public SearchView(Context context, AttributeSet attrs, int defStyleAttr) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/SearchView.java"
- line="274"
- column="40"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
errorLine1=" public void setSearchableInfo(SearchableInfo searchable) {"
errorLine2=" ~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/SearchView.java"
- line="405"
+ line="406"
column="35"/>
</issue>
@@ -11864,7 +10544,7 @@
errorLine2=" ~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/SearchView.java"
- line="428"
+ line="429"
column="34"/>
</issue>
@@ -11875,7 +10555,7 @@
errorLine2=" ~~~~">
<location
file="src/main/java/androidx/appcompat/widget/SearchView.java"
- line="478"
+ line="479"
column="48"/>
</issue>
@@ -11886,7 +10566,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/SearchView.java"
- line="510"
+ line="511"
column="40"/>
</issue>
@@ -11897,7 +10577,7 @@
errorLine2=" ~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/SearchView.java"
- line="519"
+ line="520"
column="36"/>
</issue>
@@ -11908,7 +10588,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/SearchView.java"
- line="528"
+ line="529"
column="51"/>
</issue>
@@ -11919,7 +10599,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/SearchView.java"
- line="537"
+ line="538"
column="41"/>
</issue>
@@ -11930,7 +10610,7 @@
errorLine2=" ~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/SearchView.java"
- line="549"
+ line="550"
column="42"/>
</issue>
@@ -11941,7 +10621,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/SearchView.java"
- line="558"
+ line="559"
column="12"/>
</issue>
@@ -11952,7 +10632,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/SearchView.java"
- line="570"
+ line="571"
column="26"/>
</issue>
@@ -11963,7 +10643,7 @@
errorLine2=" ~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/SearchView.java"
- line="744"
+ line="745"
column="39"/>
</issue>
@@ -11974,7 +10654,7 @@
errorLine2=" ~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/SearchView.java"
- line="754"
+ line="755"
column="12"/>
</issue>
@@ -11985,7 +10665,7 @@
errorLine2=" ~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/SearchView.java"
- line="1344"
+ line="1345"
column="15"/>
</issue>
@@ -11996,7 +10676,7 @@
errorLine2=" ~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/SearchView.java"
- line="1352"
+ line="1353"
column="43"/>
</issue>
@@ -12007,7 +10687,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/SearchView.java"
- line="1841"
+ line="1842"
column="35"/>
</issue>
@@ -12018,7 +10698,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/SearchView.java"
- line="1845"
+ line="1846"
column="35"/>
</issue>
@@ -12029,7 +10709,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/SearchView.java"
- line="1845"
+ line="1846"
column="52"/>
</issue>
@@ -12040,7 +10720,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/SearchView.java"
- line="1849"
+ line="1850"
column="35"/>
</issue>
@@ -12051,7 +10731,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/SearchView.java"
- line="1849"
+ line="1850"
column="52"/>
</issue>
@@ -12062,7 +10742,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/SearchView.java"
- line="1884"
+ line="1885"
column="36"/>
</issue>
@@ -12073,7 +10753,7 @@
errorLine2=" ~~~~">
<location
file="src/main/java/androidx/appcompat/widget/SearchView.java"
- line="1920"
+ line="1921"
column="71"/>
</issue>
@@ -12084,7 +10764,7 @@
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/SearchView.java"
- line="1935"
+ line="1936"
column="49"/>
</issue>
@@ -12095,7 +10775,7 @@
errorLine2=" ~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/SearchView.java"
- line="1982"
+ line="1983"
column="16"/>
</issue>
@@ -12106,7 +10786,7 @@
errorLine2=" ~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/SearchView.java"
- line="1982"
+ line="1983"
column="56"/>
</issue>
@@ -12905,66 +11585,11 @@
<issue
id="UnknownNullness"
message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public SwitchCompat(Context context) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/SwitchCompat.java"
- line="193"
- column="25"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public SwitchCompat(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/SwitchCompat.java"
- line="204"
- column="25"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public SwitchCompat(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/SwitchCompat.java"
- line="204"
- column="42"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public SwitchCompat(Context context, AttributeSet attrs, int defStyleAttr) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/SwitchCompat.java"
- line="218"
- column="25"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public SwitchCompat(Context context, AttributeSet attrs, int defStyleAttr) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/SwitchCompat.java"
- line="218"
- column="42"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
errorLine1=" public void setSwitchTextAppearance(Context context, int resid) {"
errorLine2=" ~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/SwitchCompat.java"
- line="303"
+ line="310"
column="41"/>
</issue>
@@ -12975,7 +11600,7 @@
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/SwitchCompat.java"
- line="367"
+ line="374"
column="35"/>
</issue>
@@ -12986,7 +11611,7 @@
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/SwitchCompat.java"
- line="395"
+ line="402"
column="35"/>
</issue>
@@ -12997,7 +11622,7 @@
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/SwitchCompat.java"
- line="483"
+ line="490"
column="34"/>
</issue>
@@ -13008,7 +11633,7 @@
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/SwitchCompat.java"
- line="512"
+ line="519"
column="12"/>
</issue>
@@ -13019,7 +11644,7 @@
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/SwitchCompat.java"
- line="602"
+ line="609"
column="34"/>
</issue>
@@ -13030,7 +11655,7 @@
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/SwitchCompat.java"
- line="633"
+ line="640"
column="12"/>
</issue>
@@ -13041,7 +11666,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/SwitchCompat.java"
- line="745"
+ line="752"
column="12"/>
</issue>
@@ -13052,7 +11677,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/SwitchCompat.java"
- line="754"
+ line="761"
column="27"/>
</issue>
@@ -13063,7 +11688,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/SwitchCompat.java"
- line="764"
+ line="771"
column="12"/>
</issue>
@@ -13074,7 +11699,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/SwitchCompat.java"
- line="773"
+ line="780"
column="28"/>
</issue>
@@ -13085,7 +11710,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/SwitchCompat.java"
- line="868"
+ line="875"
column="46"/>
</issue>
@@ -13096,7 +11721,7 @@
errorLine2=" ~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/SwitchCompat.java"
- line="909"
+ line="916"
column="33"/>
</issue>
@@ -13107,7 +11732,7 @@
errorLine2=" ~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/SwitchCompat.java"
- line="1136"
+ line="1143"
column="22"/>
</issue>
@@ -13118,7 +11743,7 @@
errorLine2=" ~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/SwitchCompat.java"
- line="1201"
+ line="1208"
column="27"/>
</issue>
@@ -13129,7 +11754,7 @@
errorLine2=" ~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/SwitchCompat.java"
- line="1325"
+ line="1332"
column="15"/>
</issue>
@@ -13140,7 +11765,7 @@
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/SwitchCompat.java"
- line="1371"
+ line="1378"
column="38"/>
</issue>
@@ -13151,7 +11776,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/SwitchCompat.java"
- line="1394"
+ line="1401"
column="48"/>
</issue>
@@ -13162,7 +11787,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/SwitchCompat.java"
- line="1400"
+ line="1407"
column="51"/>
</issue>
@@ -13173,7 +11798,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/SwitchCompat.java"
- line="1421"
+ line="1428"
column="54"/>
</issue>
@@ -13301,11 +11926,22 @@
<issue
id="UnknownNullness"
message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public TypedArray getWrappedTypeArray() {"
+ errorLine2=" ~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/appcompat/widget/TintTypedArray.java"
+ line="75"
+ column="12"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
errorLine1=" public Drawable getDrawable(int index) {"
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/TintTypedArray.java"
- line="72"
+ line="79"
column="12"/>
</issue>
@@ -13316,7 +11952,7 @@
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/TintTypedArray.java"
- line="82"
+ line="89"
column="12"/>
</issue>
@@ -13327,7 +11963,7 @@
errorLine2=" ~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/TintTypedArray.java"
- line="134"
+ line="141"
column="12"/>
</issue>
@@ -13338,7 +11974,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/TintTypedArray.java"
- line="138"
+ line="145"
column="12"/>
</issue>
@@ -13349,7 +11985,7 @@
errorLine2=" ~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/TintTypedArray.java"
- line="142"
+ line="149"
column="12"/>
</issue>
@@ -13360,7 +11996,7 @@
errorLine2=" ~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/TintTypedArray.java"
- line="146"
+ line="153"
column="12"/>
</issue>
@@ -13371,7 +12007,7 @@
errorLine2=" ~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/TintTypedArray.java"
- line="166"
+ line="173"
column="12"/>
</issue>
@@ -13382,7 +12018,7 @@
errorLine2=" ~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/TintTypedArray.java"
- line="196"
+ line="203"
column="46"/>
</issue>
@@ -13393,7 +12029,7 @@
errorLine2=" ~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/TintTypedArray.java"
- line="212"
+ line="219"
column="12"/>
</issue>
@@ -13404,7 +12040,7 @@
errorLine2=" ~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/TintTypedArray.java"
- line="216"
+ line="223"
column="40"/>
</issue>
@@ -13415,7 +12051,7 @@
errorLine2=" ~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/TintTypedArray.java"
- line="236"
+ line="243"
column="12"/>
</issue>
@@ -13426,51 +12062,18 @@
errorLine2=" ~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/TintTypedArray.java"
- line="240"
+ line="247"
column="12"/>
</issue>
<issue
id="UnknownNullness"
message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public Toolbar(Context context) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/Toolbar.java"
- line="227"
- column="20"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public Toolbar(Context context, @Nullable AttributeSet attrs) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/Toolbar.java"
- line="231"
- column="20"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public Toolbar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/Toolbar.java"
- line="235"
- column="20"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
errorLine1=" public void setMenu(MenuBuilder menu, ActionMenuPresenter outerPresenter) {"
errorLine2=" ~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/Toolbar.java"
- line="554"
+ line="559"
column="25"/>
</issue>
@@ -13481,7 +12084,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/Toolbar.java"
- line="554"
+ line="559"
column="43"/>
</issue>
@@ -13492,7 +12095,7 @@
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/Toolbar.java"
- line="628"
+ line="633"
column="25"/>
</issue>
@@ -13503,7 +12106,7 @@
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/Toolbar.java"
- line="650"
+ line="655"
column="12"/>
</issue>
@@ -13514,7 +12117,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/Toolbar.java"
- line="674"
+ line="679"
column="36"/>
</issue>
@@ -13525,7 +12128,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/Toolbar.java"
- line="688"
+ line="693"
column="12"/>
</issue>
@@ -13536,7 +12139,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/Toolbar.java"
- line="735"
+ line="740"
column="12"/>
</issue>
@@ -13547,7 +12150,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/Toolbar.java"
- line="759"
+ line="764"
column="26"/>
</issue>
@@ -13558,7 +12161,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/Toolbar.java"
- line="791"
+ line="796"
column="12"/>
</issue>
@@ -13569,7 +12172,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/Toolbar.java"
- line="813"
+ line="818"
column="29"/>
</issue>
@@ -13580,7 +12183,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/Toolbar.java"
- line="844"
+ line="849"
column="40"/>
</issue>
@@ -13591,7 +12194,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/Toolbar.java"
- line="855"
+ line="860"
column="43"/>
</issue>
@@ -13602,7 +12205,7 @@
errorLine2=" ~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/Toolbar.java"
- line="1019"
+ line="1024"
column="46"/>
</issue>
@@ -13613,7 +12216,7 @@
errorLine2=" ~~~~">
<location
file="src/main/java/androidx/appcompat/widget/Toolbar.java"
- line="1125"
+ line="1130"
column="12"/>
</issue>
@@ -13624,7 +12227,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/Toolbar.java"
- line="1202"
+ line="1207"
column="44"/>
</issue>
@@ -13635,7 +12238,7 @@
errorLine2=" ~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/Toolbar.java"
- line="1530"
+ line="1535"
column="15"/>
</issue>
@@ -13646,7 +12249,7 @@
errorLine2=" ~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/Toolbar.java"
- line="1542"
+ line="1547"
column="43"/>
</issue>
@@ -13657,7 +12260,7 @@
errorLine2=" ~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/Toolbar.java"
- line="1576"
+ line="1581"
column="33"/>
</issue>
@@ -13668,7 +12271,7 @@
errorLine2=" ~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/Toolbar.java"
- line="1602"
+ line="1607"
column="33"/>
</issue>
@@ -13679,7 +12282,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/Toolbar.java"
- line="2197"
+ line="2202"
column="12"/>
</issue>
@@ -13690,7 +12293,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/Toolbar.java"
- line="2197"
+ line="2202"
column="46"/>
</issue>
@@ -13701,7 +12304,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/Toolbar.java"
- line="2202"
+ line="2207"
column="15"/>
</issue>
@@ -13712,7 +12315,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/Toolbar.java"
- line="2202"
+ line="2207"
column="49"/>
</issue>
@@ -13723,7 +12326,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/Toolbar.java"
- line="2215"
+ line="2220"
column="15"/>
</issue>
@@ -13734,7 +12337,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/Toolbar.java"
- line="2220"
+ line="2225"
column="41"/>
</issue>
@@ -13745,7 +12348,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/Toolbar.java"
- line="2230"
+ line="2235"
column="12"/>
</issue>
@@ -13756,7 +12359,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/Toolbar.java"
- line="2279"
+ line="2284"
column="34"/>
</issue>
@@ -13767,7 +12370,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/Toolbar.java"
- line="2279"
+ line="2284"
column="62"/>
</issue>
@@ -13778,7 +12381,7 @@
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/Toolbar.java"
- line="2334"
+ line="2339"
column="40"/>
</issue>
@@ -13789,7 +12392,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/Toolbar.java"
- line="2353"
+ line="2358"
column="49"/>
</issue>
@@ -13800,7 +12403,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/Toolbar.java"
- line="2371"
+ line="2376"
column="29"/>
</issue>
@@ -13811,7 +12414,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/Toolbar.java"
- line="2377"
+ line="2382"
column="29"/>
</issue>
@@ -13822,7 +12425,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/Toolbar.java"
- line="2381"
+ line="2386"
column="29"/>
</issue>
@@ -13833,7 +12436,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/Toolbar.java"
- line="2388"
+ line="2393"
column="29"/>
</issue>
@@ -13844,7 +12447,7 @@
errorLine2=" ~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/Toolbar.java"
- line="2404"
+ line="2409"
column="27"/>
</issue>
@@ -13855,7 +12458,7 @@
errorLine2=" ~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/Toolbar.java"
- line="2408"
+ line="2413"
column="27"/>
</issue>
@@ -13866,7 +12469,7 @@
errorLine2=" ~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/Toolbar.java"
- line="2408"
+ line="2413"
column="42"/>
</issue>
@@ -13877,7 +12480,7 @@
errorLine2=" ~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/Toolbar.java"
- line="2414"
+ line="2419"
column="27"/>
</issue>
@@ -13888,7 +12491,7 @@
errorLine2=" ~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/Toolbar.java"
- line="2419"
+ line="2424"
column="35"/>
</issue>
@@ -14324,55 +12927,11 @@
<issue
id="UnknownNullness"
message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public ViewStubCompat(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/ViewStubCompat.java"
- line="52"
- column="27"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public ViewStubCompat(Context context, AttributeSet attrs) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/ViewStubCompat.java"
- line="52"
- column="44"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public ViewStubCompat(Context context, AttributeSet attrs, int defStyle) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/ViewStubCompat.java"
- line="56"
- column="27"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public ViewStubCompat(Context context, AttributeSet attrs, int defStyle) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/appcompat/widget/ViewStubCompat.java"
- line="56"
- column="44"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
errorLine1=" public void setLayoutInflater(LayoutInflater inflater) {"
errorLine2=" ~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ViewStubCompat.java"
- line="136"
+ line="138"
column="35"/>
</issue>
@@ -14383,7 +12942,7 @@
errorLine2=" ~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ViewStubCompat.java"
- line="143"
+ line="145"
column="12"/>
</issue>
@@ -14394,7 +12953,7 @@
errorLine2=" ~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ViewStubCompat.java"
- line="154"
+ line="156"
column="22"/>
</issue>
@@ -14405,7 +12964,7 @@
errorLine2=" ~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ViewStubCompat.java"
- line="158"
+ line="160"
column="33"/>
</issue>
@@ -14416,7 +12975,7 @@
errorLine2=" ~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ViewStubCompat.java"
- line="195"
+ line="197"
column="12"/>
</issue>
@@ -14427,7 +12986,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ViewStubCompat.java"
- line="247"
+ line="249"
column="38"/>
</issue>
@@ -14438,7 +12997,7 @@
errorLine2=" ~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ViewStubCompat.java"
- line="266"
+ line="268"
column="24"/>
</issue>
@@ -14449,7 +13008,7 @@
errorLine2=" ~~~~">
<location
file="src/main/java/androidx/appcompat/widget/ViewStubCompat.java"
- line="266"
+ line="268"
column="45"/>
</issue>
diff --git a/appcompat/resources/api/1.2.0-alpha01.ignore b/appcompat/resources/api/1.2.0-alpha01.ignore
deleted file mode 100644
index 31e100e..0000000
--- a/appcompat/resources/api/1.2.0-alpha01.ignore
+++ /dev/null
@@ -1,3 +0,0 @@
-// Baseline format: 1.0
-HiddenSuperclass: androidx.appcompat.graphics.drawable.AnimatedStateListDrawableCompat:
- Public class androidx.appcompat.graphics.drawable.AnimatedStateListDrawableCompat stripped of unavailable superclass androidx.appcompat.graphics.drawable.StateListDrawable
diff --git a/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatAttributeTest.kt b/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatAttributeTest.kt
new file mode 100644
index 0000000..8ec0a32
--- /dev/null
+++ b/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatAttributeTest.kt
@@ -0,0 +1,166 @@
+/*
+ * 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.appcompat.widget
+
+import android.view.ViewGroup
+import android.widget.CheckBox
+import android.widget.ImageView
+import android.widget.SeekBar
+import android.widget.TextView
+import androidx.appcompat.test.R
+import androidx.appcompat.app.AppCompatActivity
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.rule.ActivityTestRule
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@MediumTest
+@SdkSuppress(minSdkVersion = 29)
+class AppCompatAttributeTest {
+ @get:Rule
+ val activityRule = ActivityTestRule(AppCompatActivity::class.java, true, false)
+
+ @Before
+ fun setup() {
+ InstrumentationRegistry.getInstrumentation().uiAutomation.executeShellCommand(
+ "settings put global debug_view_attributes_application_package " +
+ "androidx.appcompat.test"
+ )
+ activityRule.launchActivity(null)
+ }
+
+ @After
+ fun tearDown() {
+ InstrumentationRegistry.getInstrumentation().uiAutomation.executeShellCommand(
+ "settings delete global debug_view_attributes_application_package"
+ )
+ }
+
+ @Test
+ fun testAppCompatImageViewAttributes() {
+ val root = activityRule.activity.layoutInflater.inflate(
+ R.layout.view_attribute_layout,
+ null
+ ) as ViewGroup
+ val imageView = root.findViewById<ImageView>(R.id.image_view)
+ assertTrue(imageView.attributeSourceResourceMap.isNotEmpty())
+ assertEquals(
+ R.layout.view_attribute_layout,
+ imageView.attributeSourceResourceMap[R.attr.srcCompat]
+ )
+ assertEquals(
+ R.layout.view_attribute_layout,
+ imageView.attributeSourceResourceMap[R.attr.backgroundTint]
+ )
+ assertEquals(
+ R.layout.view_attribute_layout,
+ imageView.attributeSourceResourceMap[R.attr.backgroundTintMode]
+ )
+ }
+
+ @Test
+ fun testAppCompatCheckBoxAttributes() {
+ val root = activityRule.activity.layoutInflater.inflate(
+ R.layout.view_attribute_layout,
+ null
+ ) as ViewGroup
+ val checkBox = root.findViewById<CheckBox>(R.id.check_box)
+ assertTrue(checkBox.attributeSourceResourceMap.isNotEmpty())
+ assertEquals(
+ R.layout.view_attribute_layout,
+ checkBox.attributeSourceResourceMap[R.attr.buttonTint]
+ )
+ }
+
+ @Test
+ fun testAppCompatSeekBarAttributes() {
+ val root = activityRule.activity.layoutInflater.inflate(
+ R.layout.view_attribute_layout,
+ null
+ ) as ViewGroup
+ val seekBar = root.findViewById<SeekBar>(R.id.seek_bar)
+ assertTrue(seekBar.attributeSourceResourceMap.isNotEmpty())
+ assertEquals(
+ R.layout.view_attribute_layout,
+ seekBar.attributeSourceResourceMap[R.attr.tickMarkTint]
+ )
+ }
+
+ @Test
+ fun testAppCompatTextViewAttributes() {
+ val root = activityRule.activity.layoutInflater.inflate(
+ R.layout.view_attribute_layout,
+ null
+ ) as ViewGroup
+ val textView = root.findViewById<TextView>(R.id.text_view)
+ assertTrue(textView.attributeSourceResourceMap.isNotEmpty())
+ assertEquals(
+ R.layout.view_attribute_layout,
+ textView.attributeSourceResourceMap[R.attr.autoSizeTextType]
+ )
+ }
+
+ @Test
+ fun testSwitchCompatAttributes() {
+ val root = activityRule.activity.layoutInflater.inflate(
+ R.layout.view_attribute_layout,
+ null
+ ) as ViewGroup
+ val switchCompat = root.findViewById<SwitchCompat>(R.id.switch_compat)
+ assertTrue(switchCompat.attributeSourceResourceMap.isNotEmpty())
+ assertEquals(
+ R.layout.view_attribute_layout,
+ switchCompat.attributeSourceResourceMap[R.attr.thumbTint]
+ )
+ }
+
+ @Test
+ fun testToolbarAttributes() {
+ val root = activityRule.activity.layoutInflater.inflate(
+ R.layout.view_attribute_layout,
+ null
+ ) as ViewGroup
+ val toolbar = root.findViewById<Toolbar>(R.id.toolbar)
+ assertTrue(toolbar.attributeSourceResourceMap.isNotEmpty())
+ assertEquals(
+ R.layout.view_attribute_layout,
+ toolbar.attributeSourceResourceMap[R.attr.titleMargin]
+ )
+ }
+
+ @Test
+ fun testLinearLayoutCompatAttributes() {
+ val root = activityRule.activity.layoutInflater.inflate(
+ R.layout.view_attribute_layout,
+ null
+ ) as LinearLayoutCompat
+ assertTrue(root.attributeSourceResourceMap.isNotEmpty())
+ assertEquals(
+ R.layout.view_attribute_layout,
+ root.attributeSourceResourceMap[R.attr.showDividers]
+ )
+ }
+}
\ No newline at end of file
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/androidTest/res/layout/view_attribute_layout.xml b/appcompat/src/androidTest/res/layout/view_attribute_layout.xml
new file mode 100644
index 0000000..fb7859f
--- /dev/null
+++ b/appcompat/src/androidTest/res/layout/view_attribute_layout.xml
@@ -0,0 +1,58 @@
+<?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.
+ -->
+
+<androidx.appcompat.widget.LinearLayoutCompat
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ app:showDividers="none">
+ <ImageView
+ android:id="@+id/image_view"
+ android:layout_width="1dp"
+ android:layout_height="1dp"
+ app:srcCompat="@drawable/avd_heart_empty"
+ app:backgroundTint="#ff00ff"
+ app:backgroundTintMode="add" />
+ <CheckBox
+ android:id="@+id/check_box"
+ android:layout_width="1dp"
+ android:layout_height="1dp"
+ app:buttonTint="#00ff00"/>
+ <SeekBar
+ android:id="@+id/seek_bar"
+ android:layout_width="1dp"
+ android:layout_height="1dp"
+ app:tickMarkTint="#ff0000"/>
+ <TextView
+ android:id="@+id/text_view"
+ android:layout_width="1dp"
+ android:layout_height="1dp"
+ app:autoSizeTextType="none" />
+
+ <androidx.appcompat.widget.SwitchCompat
+ android:id="@+id/switch_compat"
+ android:layout_width="1dp"
+ android:layout_height="1dp"
+ app:thumbTint="#0000ff" />
+ <androidx.appcompat.widget.Toolbar
+ android:id="@+id/toolbar"
+ android:layout_width="1dp"
+ android:layout_height="1dp"
+ app:titleMargin="1dp" />
+</androidx.appcompat.widget.LinearLayoutCompat>
\ No newline at end of file
diff --git a/appcompat/src/main/java/androidx/appcompat/app/AlertController.java b/appcompat/src/main/java/androidx/appcompat/app/AlertController.java
index cfb1f1a..9a24db5 100644
--- a/appcompat/src/main/java/androidx/appcompat/app/AlertController.java
+++ b/appcompat/src/main/java/androidx/appcompat/app/AlertController.java
@@ -797,9 +797,9 @@
mButtonNeutral.setVisibility(View.GONE);
} else {
mButtonNeutral.setText(mButtonNeutralText);
- if (mButtonPositiveIcon != null) {
- mButtonPositiveIcon.setBounds(0, 0, mButtonIconDimen, mButtonIconDimen);
- mButtonPositive.setCompoundDrawables(mButtonPositiveIcon, null, null, null);
+ if (mButtonNeutralIcon != null) {
+ mButtonNeutralIcon.setBounds(0, 0, mButtonIconDimen, mButtonIconDimen);
+ mButtonNeutral.setCompoundDrawables(mButtonNeutralIcon, null, null, null);
}
mButtonNeutral.setVisibility(View.VISIBLE);
whichButtons = whichButtons | BIT_BUTTON_NEUTRAL;
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/appcompat/src/main/java/androidx/appcompat/widget/AbsActionBarView.java b/appcompat/src/main/java/androidx/appcompat/widget/AbsActionBarView.java
index 5949668..3ed4a46 100644
--- a/appcompat/src/main/java/androidx/appcompat/widget/AbsActionBarView.java
+++ b/appcompat/src/main/java/androidx/appcompat/widget/AbsActionBarView.java
@@ -26,6 +26,8 @@
import android.view.View;
import android.view.ViewGroup;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.appcompat.R;
import androidx.core.view.ViewCompat;
import androidx.core.view.ViewPropertyAnimatorCompat;
@@ -48,15 +50,15 @@
private boolean mEatingTouch;
private boolean mEatingHover;
- AbsActionBarView(Context context) {
+ AbsActionBarView(@NonNull Context context) {
this(context, null);
}
- AbsActionBarView(Context context, AttributeSet attrs) {
+ AbsActionBarView(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
- AbsActionBarView(Context context, AttributeSet attrs, int defStyle) {
+ AbsActionBarView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
final TypedValue tv = new TypedValue();
diff --git a/appcompat/src/main/java/androidx/appcompat/widget/ActionBarContextView.java b/appcompat/src/main/java/androidx/appcompat/widget/ActionBarContextView.java
index 9bc598c..996ce82 100644
--- a/appcompat/src/main/java/androidx/appcompat/widget/ActionBarContextView.java
+++ b/appcompat/src/main/java/androidx/appcompat/widget/ActionBarContextView.java
@@ -28,6 +28,8 @@
import android.widget.LinearLayout;
import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.appcompat.R;
import androidx.appcompat.view.ActionMode;
@@ -39,8 +41,6 @@
*/
@RestrictTo(LIBRARY_GROUP_PREFIX)
public class ActionBarContextView extends AbsActionBarView {
- private static final String TAG = "ActionBarContextView";
-
private CharSequence mTitle;
private CharSequence mSubtitle;
@@ -54,15 +54,16 @@
private boolean mTitleOptional;
private int mCloseItemLayout;
- public ActionBarContextView(Context context) {
+ public ActionBarContextView(@NonNull Context context) {
this(context, null);
}
- public ActionBarContextView(Context context, AttributeSet attrs) {
+ public ActionBarContextView(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, R.attr.actionModeStyle);
}
- public ActionBarContextView(Context context, AttributeSet attrs, int defStyle) {
+ public ActionBarContextView(
+ @NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
final TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs,
diff --git a/appcompat/src/main/java/androidx/appcompat/widget/ActionBarOverlayLayout.java b/appcompat/src/main/java/androidx/appcompat/widget/ActionBarOverlayLayout.java
index b381680..f95483b 100644
--- a/appcompat/src/main/java/androidx/appcompat/widget/ActionBarOverlayLayout.java
+++ b/appcompat/src/main/java/androidx/appcompat/widget/ActionBarOverlayLayout.java
@@ -37,6 +37,8 @@
import android.view.Window;
import android.widget.OverScroller;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.appcompat.R;
import androidx.appcompat.app.AppCompatDelegate;
@@ -135,11 +137,11 @@
private final NestedScrollingParentHelper mParentHelper;
- public ActionBarOverlayLayout(Context context) {
+ public ActionBarOverlayLayout(@NonNull Context context) {
this(context, null);
}
- public ActionBarOverlayLayout(Context context, AttributeSet attrs) {
+ public ActionBarOverlayLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context);
diff --git a/appcompat/src/main/java/androidx/appcompat/widget/ActionMenuView.java b/appcompat/src/main/java/androidx/appcompat/widget/ActionMenuView.java
index 4b82d87..14fa5b4 100644
--- a/appcompat/src/main/java/androidx/appcompat/widget/ActionMenuView.java
+++ b/appcompat/src/main/java/androidx/appcompat/widget/ActionMenuView.java
@@ -30,6 +30,7 @@
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.annotation.StyleRes;
@@ -72,11 +73,11 @@
OnMenuItemClickListener mOnMenuItemClickListener;
- public ActionMenuView(Context context) {
+ public ActionMenuView(@NonNull Context context) {
this(context, null);
}
- public ActionMenuView(Context context, AttributeSet attrs) {
+ public ActionMenuView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
setBaselineAligned(false);
final float density = context.getResources().getDisplayMetrics().density;
diff --git a/appcompat/src/main/java/androidx/appcompat/widget/ActivityChooserView.java b/appcompat/src/main/java/androidx/appcompat/widget/ActivityChooserView.java
index 8f9962792..8f70560 100644
--- a/appcompat/src/main/java/androidx/appcompat/widget/ActivityChooserView.java
+++ b/appcompat/src/main/java/androidx/appcompat/widget/ActivityChooserView.java
@@ -44,6 +44,8 @@
import android.widget.PopupWindow;
import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.appcompat.R;
import androidx.appcompat.view.menu.ShowableListMenu;
@@ -197,7 +199,7 @@
*
* @param context The application environment.
*/
- public ActivityChooserView(Context context) {
+ public ActivityChooserView(@NonNull Context context) {
this(context, null);
}
@@ -207,7 +209,7 @@
* @param context The application environment.
* @param attrs A collection of attributes.
*/
- public ActivityChooserView(Context context, AttributeSet attrs) {
+ public ActivityChooserView(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
@@ -218,7 +220,8 @@
* @param attrs A collection of attributes.
* @param defStyle The default style to apply to this view.
*/
- public ActivityChooserView(Context context, AttributeSet attrs, int defStyle) {
+ public ActivityChooserView(
+ @NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray attributesArray = context.obtainStyledAttributes(attrs,
diff --git a/appcompat/src/main/java/androidx/appcompat/widget/AppCompatAutoCompleteTextView.java b/appcompat/src/main/java/androidx/appcompat/widget/AppCompatAutoCompleteTextView.java
index 030c41e..b45dd08 100644
--- a/appcompat/src/main/java/androidx/appcompat/widget/AppCompatAutoCompleteTextView.java
+++ b/appcompat/src/main/java/androidx/appcompat/widget/AppCompatAutoCompleteTextView.java
@@ -30,6 +30,7 @@
import android.widget.TextView;
import androidx.annotation.DrawableRes;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.appcompat.R;
@@ -62,15 +63,16 @@
private final AppCompatBackgroundHelper mBackgroundTintHelper;
private final AppCompatTextHelper mTextHelper;
- public AppCompatAutoCompleteTextView(Context context) {
+ public AppCompatAutoCompleteTextView(@NonNull Context context) {
this(context, null);
}
- public AppCompatAutoCompleteTextView(Context context, AttributeSet attrs) {
+ public AppCompatAutoCompleteTextView(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, R.attr.autoCompleteTextViewStyle);
}
- public AppCompatAutoCompleteTextView(Context context, AttributeSet attrs, int defStyleAttr) {
+ public AppCompatAutoCompleteTextView(
+ @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(TintContextWrapper.wrap(context), attrs, defStyleAttr);
TintTypedArray a = TintTypedArray.obtainStyledAttributes(getContext(), attrs,
diff --git a/appcompat/src/main/java/androidx/appcompat/widget/AppCompatBackgroundHelper.java b/appcompat/src/main/java/androidx/appcompat/widget/AppCompatBackgroundHelper.java
index 496efde..310005c 100644
--- a/appcompat/src/main/java/androidx/appcompat/widget/AppCompatBackgroundHelper.java
+++ b/appcompat/src/main/java/androidx/appcompat/widget/AppCompatBackgroundHelper.java
@@ -24,11 +24,13 @@
import android.view.View;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.appcompat.R;
import androidx.core.view.ViewCompat;
class AppCompatBackgroundHelper {
+ @NonNull
private final View mView;
private final AppCompatDrawableManager mDrawableManager;
@@ -38,14 +40,19 @@
private TintInfo mBackgroundTint;
private TintInfo mTmpInfo;
- AppCompatBackgroundHelper(View view) {
+ AppCompatBackgroundHelper(@NonNull View view) {
mView = view;
mDrawableManager = AppCompatDrawableManager.get();
}
- void loadFromAttributes(AttributeSet attrs, int defStyleAttr) {
+ void loadFromAttributes(@Nullable AttributeSet attrs, int defStyleAttr) {
TintTypedArray a = TintTypedArray.obtainStyledAttributes(mView.getContext(), attrs,
R.styleable.ViewBackgroundHelper, defStyleAttr, 0);
+ if (Build.VERSION.SDK_INT >= 29) {
+ mView.saveAttributeDataForStyleable(mView.getContext(),
+ R.styleable.ViewBackgroundHelper, attrs, a.getWrappedTypeArray(),
+ defStyleAttr, 0);
+ }
try {
if (a.hasValue(R.styleable.ViewBackgroundHelper_android_background)) {
mBackgroundResId = a.getResourceId(
diff --git a/appcompat/src/main/java/androidx/appcompat/widget/AppCompatButton.java b/appcompat/src/main/java/androidx/appcompat/widget/AppCompatButton.java
index fb1a1f8..202f867 100644
--- a/appcompat/src/main/java/androidx/appcompat/widget/AppCompatButton.java
+++ b/appcompat/src/main/java/androidx/appcompat/widget/AppCompatButton.java
@@ -60,15 +60,16 @@
private final AppCompatBackgroundHelper mBackgroundTintHelper;
private final AppCompatTextHelper mTextHelper;
- public AppCompatButton(Context context) {
+ public AppCompatButton(@NonNull Context context) {
this(context, null);
}
- public AppCompatButton(Context context, AttributeSet attrs) {
+ public AppCompatButton(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, R.attr.buttonStyle);
}
- public AppCompatButton(Context context, AttributeSet attrs, int defStyleAttr) {
+ public AppCompatButton(
+ @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(TintContextWrapper.wrap(context), attrs, defStyleAttr);
mBackgroundTintHelper = new AppCompatBackgroundHelper(this);
diff --git a/appcompat/src/main/java/androidx/appcompat/widget/AppCompatCheckBox.java b/appcompat/src/main/java/androidx/appcompat/widget/AppCompatCheckBox.java
index d5d4019..a544365 100644
--- a/appcompat/src/main/java/androidx/appcompat/widget/AppCompatCheckBox.java
+++ b/appcompat/src/main/java/androidx/appcompat/widget/AppCompatCheckBox.java
@@ -27,6 +27,7 @@
import android.widget.CheckBox;
import androidx.annotation.DrawableRes;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.appcompat.R;
@@ -57,15 +58,16 @@
private final AppCompatBackgroundHelper mBackgroundTintHelper;
private final AppCompatTextHelper mTextHelper;
- public AppCompatCheckBox(Context context) {
+ public AppCompatCheckBox(@NonNull Context context) {
this(context, null);
}
- public AppCompatCheckBox(Context context, AttributeSet attrs) {
+ public AppCompatCheckBox(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, R.attr.checkboxStyle);
}
- public AppCompatCheckBox(Context context, AttributeSet attrs, int defStyleAttr) {
+ public AppCompatCheckBox(
+ @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(TintContextWrapper.wrap(context), attrs, defStyleAttr);
mCompoundButtonHelper = new AppCompatCompoundButtonHelper(this);
mCompoundButtonHelper.loadFromAttributes(attrs, defStyleAttr);
diff --git a/appcompat/src/main/java/androidx/appcompat/widget/AppCompatCheckedTextView.java b/appcompat/src/main/java/androidx/appcompat/widget/AppCompatCheckedTextView.java
index 52772b3..155d413a 100644
--- a/appcompat/src/main/java/androidx/appcompat/widget/AppCompatCheckedTextView.java
+++ b/appcompat/src/main/java/androidx/appcompat/widget/AppCompatCheckedTextView.java
@@ -24,6 +24,8 @@
import android.widget.CheckedTextView;
import androidx.annotation.DrawableRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.core.widget.TextViewCompat;
@@ -43,15 +45,16 @@
private final AppCompatTextHelper mTextHelper;
- public AppCompatCheckedTextView(Context context) {
+ public AppCompatCheckedTextView(@NonNull Context context) {
this(context, null);
}
- public AppCompatCheckedTextView(Context context, AttributeSet attrs) {
+ public AppCompatCheckedTextView(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, android.R.attr.checkedTextViewStyle);
}
- public AppCompatCheckedTextView(Context context, AttributeSet attrs, int defStyleAttr) {
+ public AppCompatCheckedTextView(
+ @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(TintContextWrapper.wrap(context), attrs, defStyleAttr);
mTextHelper = new AppCompatTextHelper(this);
diff --git a/appcompat/src/main/java/androidx/appcompat/widget/AppCompatCompoundButtonHelper.java b/appcompat/src/main/java/androidx/appcompat/widget/AppCompatCompoundButtonHelper.java
index 05c0532..aa15632 100644
--- a/appcompat/src/main/java/androidx/appcompat/widget/AppCompatCompoundButtonHelper.java
+++ b/appcompat/src/main/java/androidx/appcompat/widget/AppCompatCompoundButtonHelper.java
@@ -25,6 +25,7 @@
import android.util.AttributeSet;
import android.widget.CompoundButton;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.R;
import androidx.appcompat.content.res.AppCompatResources;
@@ -32,7 +33,7 @@
import androidx.core.widget.CompoundButtonCompat;
class AppCompatCompoundButtonHelper {
-
+ @NonNull
private final CompoundButton mView;
private ColorStateList mButtonTintList = null;
@@ -42,20 +43,17 @@
private boolean mSkipNextApply;
- /**
- * Interface which allows us to directly set a button, bypass any calls back to ourselves.
- */
- interface DirectSetButtonDrawableInterface {
- void setButtonDrawable(Drawable buttonDrawable);
- }
-
- AppCompatCompoundButtonHelper(CompoundButton view) {
+ AppCompatCompoundButtonHelper(@NonNull CompoundButton view) {
mView = view;
}
- void loadFromAttributes(AttributeSet attrs, int defStyleAttr) {
+ void loadFromAttributes(@Nullable AttributeSet attrs, int defStyleAttr) {
TypedArray a = mView.getContext().obtainStyledAttributes(attrs, R.styleable.CompoundButton,
defStyleAttr, 0);
+ if (Build.VERSION.SDK_INT >= 29) {
+ mView.saveAttributeDataForStyleable(mView.getContext(),
+ R.styleable.CompoundButton, attrs, a, defStyleAttr, 0);
+ }
try {
boolean buttonDrawableLoaded = false;
if (a.hasValue(R.styleable.CompoundButton_buttonCompat)) {
diff --git a/appcompat/src/main/java/androidx/appcompat/widget/AppCompatEditText.java b/appcompat/src/main/java/androidx/appcompat/widget/AppCompatEditText.java
index 6de13fe..686ba3d 100644
--- a/appcompat/src/main/java/androidx/appcompat/widget/AppCompatEditText.java
+++ b/appcompat/src/main/java/androidx/appcompat/widget/AppCompatEditText.java
@@ -61,15 +61,16 @@
private final AppCompatTextHelper mTextHelper;
private final AppCompatTextClassifierHelper mTextClassifierHelper;
- public AppCompatEditText(Context context) {
+ public AppCompatEditText(@NonNull Context context) {
this(context, null);
}
- public AppCompatEditText(Context context, AttributeSet attrs) {
+ public AppCompatEditText(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, R.attr.editTextStyle);
}
- public AppCompatEditText(Context context, AttributeSet attrs, int defStyleAttr) {
+ public AppCompatEditText(
+ @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(TintContextWrapper.wrap(context), attrs, defStyleAttr);
mBackgroundTintHelper = new AppCompatBackgroundHelper(this);
diff --git a/appcompat/src/main/java/androidx/appcompat/widget/AppCompatImageButton.java b/appcompat/src/main/java/androidx/appcompat/widget/AppCompatImageButton.java
index 8f41021..bf825bd 100644
--- a/appcompat/src/main/java/androidx/appcompat/widget/AppCompatImageButton.java
+++ b/appcompat/src/main/java/androidx/appcompat/widget/AppCompatImageButton.java
@@ -29,6 +29,7 @@
import android.widget.ImageView;
import androidx.annotation.DrawableRes;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.appcompat.R;
@@ -61,15 +62,16 @@
private final AppCompatBackgroundHelper mBackgroundTintHelper;
private final AppCompatImageHelper mImageHelper;
- public AppCompatImageButton(Context context) {
+ public AppCompatImageButton(@NonNull Context context) {
this(context, null);
}
- public AppCompatImageButton(Context context, AttributeSet attrs) {
+ public AppCompatImageButton(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, R.attr.imageButtonStyle);
}
- public AppCompatImageButton(Context context, AttributeSet attrs, int defStyleAttr) {
+ public AppCompatImageButton(
+ @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(TintContextWrapper.wrap(context), attrs, defStyleAttr);
mBackgroundTintHelper = new AppCompatBackgroundHelper(this);
diff --git a/appcompat/src/main/java/androidx/appcompat/widget/AppCompatImageHelper.java b/appcompat/src/main/java/androidx/appcompat/widget/AppCompatImageHelper.java
index faf62f7..0ff5247 100644
--- a/appcompat/src/main/java/androidx/appcompat/widget/AppCompatImageHelper.java
+++ b/appcompat/src/main/java/androidx/appcompat/widget/AppCompatImageHelper.java
@@ -36,19 +36,25 @@
*/
@RestrictTo(LIBRARY_GROUP_PREFIX)
public class AppCompatImageHelper {
+ @NonNull
private final ImageView mView;
private TintInfo mInternalImageTint;
private TintInfo mImageTint;
private TintInfo mTmpInfo;
- public AppCompatImageHelper(ImageView view) {
+ public AppCompatImageHelper(@NonNull ImageView view) {
mView = view;
}
public void loadFromAttributes(AttributeSet attrs, int defStyleAttr) {
TintTypedArray a = TintTypedArray.obtainStyledAttributes(mView.getContext(), attrs,
R.styleable.AppCompatImageView, defStyleAttr, 0);
+ if (Build.VERSION.SDK_INT >= 29) {
+ mView.saveAttributeDataForStyleable(
+ mView.getContext(), R.styleable.AppCompatImageView, attrs,
+ a.getWrappedTypeArray(), defStyleAttr, 0);
+ }
try {
Drawable drawable = mView.getDrawable();
if (drawable == null) {
diff --git a/appcompat/src/main/java/androidx/appcompat/widget/AppCompatImageView.java b/appcompat/src/main/java/androidx/appcompat/widget/AppCompatImageView.java
index f0a8452..82f0f7e 100644
--- a/appcompat/src/main/java/androidx/appcompat/widget/AppCompatImageView.java
+++ b/appcompat/src/main/java/androidx/appcompat/widget/AppCompatImageView.java
@@ -28,9 +28,9 @@
import android.widget.ImageView;
import androidx.annotation.DrawableRes;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
-import androidx.appcompat.R;
import androidx.core.view.TintableBackgroundView;
import androidx.core.widget.ImageViewCompat;
import androidx.core.widget.TintableImageSourceView;
@@ -41,12 +41,13 @@
* <ul>
* <li>Allows dynamic tint of its background via the background tint methods in
* {@link androidx.core.view.ViewCompat}.</li>
- * <li>Allows setting of the background tint using {@link R.attr#backgroundTint} and
- * {@link R.attr#backgroundTintMode}.</li>
+ * <li>Allows setting of the background tint using
+ * {@link androidx.appcompat.R.attr#backgroundTint} and
+ * {@link androidx.appcompat.R.attr#backgroundTintMode}.</li>
* <li>Allows dynamic tint of its image via the image tint methods in
* {@link ImageViewCompat}.</li>
- * <li>Allows setting of the image tint using {@link R.attr#tint} and
- * {@link R.attr#tintMode}.</li>
+ * <li>Allows setting of the image tint using {@link androidx.appcompat.R.attr#tint} and
+ * {@link androidx.appcompat.R.attr#tintMode}.</li>
* </ul>
*
* <p>This will automatically be used when you use {@link ImageView} in your layouts
@@ -60,15 +61,16 @@
private final AppCompatBackgroundHelper mBackgroundTintHelper;
private final AppCompatImageHelper mImageHelper;
- public AppCompatImageView(Context context) {
+ public AppCompatImageView(@NonNull Context context) {
this(context, null);
}
- public AppCompatImageView(Context context, AttributeSet attrs) {
+ public AppCompatImageView(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
- public AppCompatImageView(Context context, AttributeSet attrs, int defStyleAttr) {
+ public AppCompatImageView(
+ @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(TintContextWrapper.wrap(context), attrs, defStyleAttr);
mBackgroundTintHelper = new AppCompatBackgroundHelper(this);
@@ -85,7 +87,7 @@
*
* @param resId the resource identifier of the drawable
* @see ImageView#setImageResource(int)
- * {@link R.attr#srcCompat}
+ * {@link androidx.appcompat.R.attr#srcCompat}
*/
@Override
public void setImageResource(@DrawableRes int resId) {
diff --git a/appcompat/src/main/java/androidx/appcompat/widget/AppCompatMultiAutoCompleteTextView.java b/appcompat/src/main/java/androidx/appcompat/widget/AppCompatMultiAutoCompleteTextView.java
index c92eb4a..0a08c1b 100644
--- a/appcompat/src/main/java/androidx/appcompat/widget/AppCompatMultiAutoCompleteTextView.java
+++ b/appcompat/src/main/java/androidx/appcompat/widget/AppCompatMultiAutoCompleteTextView.java
@@ -28,6 +28,7 @@
import android.widget.MultiAutoCompleteTextView;
import androidx.annotation.DrawableRes;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.appcompat.R;
@@ -59,15 +60,17 @@
private final AppCompatBackgroundHelper mBackgroundTintHelper;
private final AppCompatTextHelper mTextHelper;
- public AppCompatMultiAutoCompleteTextView(Context context) {
+ public AppCompatMultiAutoCompleteTextView(@NonNull Context context) {
this(context, null);
}
- public AppCompatMultiAutoCompleteTextView(Context context, AttributeSet attrs) {
+ public AppCompatMultiAutoCompleteTextView(
+ @NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, R.attr.autoCompleteTextViewStyle);
}
- public AppCompatMultiAutoCompleteTextView(Context context, AttributeSet attrs, int defStyleAttr) {
+ public AppCompatMultiAutoCompleteTextView(
+ @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(TintContextWrapper.wrap(context), attrs, defStyleAttr);
TintTypedArray a = TintTypedArray.obtainStyledAttributes(getContext(), attrs,
diff --git a/appcompat/src/main/java/androidx/appcompat/widget/AppCompatRadioButton.java b/appcompat/src/main/java/androidx/appcompat/widget/AppCompatRadioButton.java
index 399bf5b..e57bab5 100644
--- a/appcompat/src/main/java/androidx/appcompat/widget/AppCompatRadioButton.java
+++ b/appcompat/src/main/java/androidx/appcompat/widget/AppCompatRadioButton.java
@@ -61,11 +61,11 @@
this(context, null);
}
- public AppCompatRadioButton(Context context, AttributeSet attrs) {
+ public AppCompatRadioButton(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, R.attr.radioButtonStyle);
}
- public AppCompatRadioButton(Context context, AttributeSet attrs, int defStyleAttr) {
+ public AppCompatRadioButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(TintContextWrapper.wrap(context), attrs, defStyleAttr);
mCompoundButtonHelper = new AppCompatCompoundButtonHelper(this);
mCompoundButtonHelper.loadFromAttributes(attrs, defStyleAttr);
diff --git a/appcompat/src/main/java/androidx/appcompat/widget/AppCompatRatingBar.java b/appcompat/src/main/java/androidx/appcompat/widget/AppCompatRatingBar.java
index 957c57e..0a5afa5 100644
--- a/appcompat/src/main/java/androidx/appcompat/widget/AppCompatRatingBar.java
+++ b/appcompat/src/main/java/androidx/appcompat/widget/AppCompatRatingBar.java
@@ -22,6 +22,8 @@
import android.view.View;
import android.widget.RatingBar;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.appcompat.R;
/**
@@ -36,15 +38,16 @@
private final AppCompatProgressBarHelper mAppCompatProgressBarHelper;
- public AppCompatRatingBar(Context context) {
+ public AppCompatRatingBar(@NonNull Context context) {
this(context, null);
}
- public AppCompatRatingBar(Context context, AttributeSet attrs) {
+ public AppCompatRatingBar(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, R.attr.ratingBarStyle);
}
- public AppCompatRatingBar(Context context, AttributeSet attrs, int defStyleAttr) {
+ public AppCompatRatingBar(
+ @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mAppCompatProgressBarHelper = new AppCompatProgressBarHelper(this);
diff --git a/appcompat/src/main/java/androidx/appcompat/widget/AppCompatSeekBar.java b/appcompat/src/main/java/androidx/appcompat/widget/AppCompatSeekBar.java
index bcf37b7..bd362f6 100644
--- a/appcompat/src/main/java/androidx/appcompat/widget/AppCompatSeekBar.java
+++ b/appcompat/src/main/java/androidx/appcompat/widget/AppCompatSeekBar.java
@@ -21,6 +21,8 @@
import android.util.AttributeSet;
import android.widget.SeekBar;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.appcompat.R;
/**
@@ -35,15 +37,16 @@
private final AppCompatSeekBarHelper mAppCompatSeekBarHelper;
- public AppCompatSeekBar(Context context) {
+ public AppCompatSeekBar(@NonNull Context context) {
this(context, null);
}
- public AppCompatSeekBar(Context context, AttributeSet attrs) {
+ public AppCompatSeekBar(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, R.attr.seekBarStyle);
}
- public AppCompatSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
+ public AppCompatSeekBar(
+ @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mAppCompatSeekBarHelper = new AppCompatSeekBarHelper(this);
diff --git a/appcompat/src/main/java/androidx/appcompat/widget/AppCompatSeekBarHelper.java b/appcompat/src/main/java/androidx/appcompat/widget/AppCompatSeekBarHelper.java
index 9d1ffc3..1f3ce48 100644
--- a/appcompat/src/main/java/androidx/appcompat/widget/AppCompatSeekBarHelper.java
+++ b/appcompat/src/main/java/androidx/appcompat/widget/AppCompatSeekBarHelper.java
@@ -20,6 +20,7 @@
import android.graphics.Canvas;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
+import android.os.Build;
import android.util.AttributeSet;
import android.widget.SeekBar;
@@ -49,6 +50,11 @@
TintTypedArray a = TintTypedArray.obtainStyledAttributes(mView.getContext(), attrs,
R.styleable.AppCompatSeekBar, defStyleAttr, 0);
+ if (Build.VERSION.SDK_INT >= 29) {
+ mView.saveAttributeDataForStyleable(mView.getContext(),
+ R.styleable.AppCompatSeekBar, attrs, a.getWrappedTypeArray(),
+ defStyleAttr, 0);
+ }
final Drawable drawable = a.getDrawableIfKnown(R.styleable.AppCompatSeekBar_android_thumb);
if (drawable != null) {
mView.setThumb(drawable);
diff --git a/appcompat/src/main/java/androidx/appcompat/widget/AppCompatSpinner.java b/appcompat/src/main/java/androidx/appcompat/widget/AppCompatSpinner.java
index 01d7375..92e8aea 100644
--- a/appcompat/src/main/java/androidx/appcompat/widget/AppCompatSpinner.java
+++ b/appcompat/src/main/java/androidx/appcompat/widget/AppCompatSpinner.java
@@ -47,6 +47,7 @@
import android.widget.SpinnerAdapter;
import androidx.annotation.DrawableRes;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.annotation.VisibleForTesting;
@@ -110,7 +111,8 @@
* @param context The Context the view is running in, through which it can
* access the current theme, resources, etc.
*/
- public AppCompatSpinner(Context context) {
+ public AppCompatSpinner(
+ @NonNull Context context) {
this(context, null);
}
@@ -125,7 +127,8 @@
* @see #MODE_DIALOG
* @see #MODE_DROPDOWN
*/
- public AppCompatSpinner(Context context, int mode) {
+ public AppCompatSpinner(
+ @NonNull Context context, int mode) {
this(context, null, R.attr.spinnerStyle, mode);
}
@@ -136,7 +139,8 @@
* access the current theme, resources, etc.
* @param attrs The attributes of the XML tag that is inflating the view.
*/
- public AppCompatSpinner(Context context, AttributeSet attrs) {
+ public AppCompatSpinner(
+ @NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, R.attr.spinnerStyle);
}
@@ -151,7 +155,8 @@
* reference to a style resource that supplies default values for
* the view. Can be 0 to not look for defaults.
*/
- public AppCompatSpinner(Context context, AttributeSet attrs, int defStyleAttr) {
+ public AppCompatSpinner(
+ @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, MODE_THEME);
}
@@ -170,7 +175,8 @@
* @see #MODE_DIALOG
* @see #MODE_DROPDOWN
*/
- public AppCompatSpinner(Context context, AttributeSet attrs, int defStyleAttr, int mode) {
+ public AppCompatSpinner(
+ @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int mode) {
this(context, attrs, defStyleAttr, mode, null);
}
@@ -198,8 +204,8 @@
* @see #MODE_DIALOG
* @see #MODE_DROPDOWN
*/
- public AppCompatSpinner(Context context, AttributeSet attrs, int defStyleAttr, int mode,
- Resources.Theme popupTheme) {
+ public AppCompatSpinner(@NonNull Context context, @Nullable AttributeSet attrs,
+ int defStyleAttr, int mode, Resources.Theme popupTheme) {
super(context, attrs, defStyleAttr);
TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs,
diff --git a/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextClassifierHelper.java b/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextClassifierHelper.java
index 1470149..c3df776 100644
--- a/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextClassifierHelper.java
+++ b/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextClassifierHelper.java
@@ -39,7 +39,7 @@
@Nullable
private TextClassifier mTextClassifier;
- AppCompatTextClassifierHelper(TextView textView) {
+ AppCompatTextClassifierHelper(@NonNull TextView textView) {
mTextView = Preconditions.checkNotNull(textView);
}
diff --git a/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextHelper.java b/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextHelper.java
index fb97e5e..697995e 100644
--- a/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextHelper.java
+++ b/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextHelper.java
@@ -52,6 +52,7 @@
private static final int SERIF = 2;
private static final int MONOSPACE = 3;
+ @NonNull
private final TextView mView;
private TintInfo mDrawableLeftTint;
@@ -70,19 +71,25 @@
private Typeface mFontTypeface;
private boolean mAsyncFontPending;
- AppCompatTextHelper(TextView view) {
+ AppCompatTextHelper(@NonNull TextView view) {
mView = view;
mAutoSizeTextHelper = new AppCompatTextViewAutoSizeHelper(mView);
}
@SuppressLint("NewApi")
- void loadFromAttributes(AttributeSet attrs, int defStyleAttr) {
+ void loadFromAttributes(@Nullable AttributeSet attrs, int defStyleAttr) {
final Context context = mView.getContext();
final AppCompatDrawableManager drawableManager = AppCompatDrawableManager.get();
// First read the TextAppearance style id
TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs,
R.styleable.AppCompatTextHelper, defStyleAttr, 0);
+ if (Build.VERSION.SDK_INT >= 29) {
+ mView.saveAttributeDataForStyleable(mView.getContext(),
+ R.styleable.AppCompatTextHelper, attrs, a.getWrappedTypeArray(),
+ defStyleAttr, 0);
+ }
+
final int ap = a.getResourceId(R.styleable.AppCompatTextHelper_android_textAppearance, -1);
// Now read the compound drawable and grab any tints
if (a.hasValue(R.styleable.AppCompatTextHelper_android_drawableLeft)) {
diff --git a/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextView.java b/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextView.java
index a8e5110..194d121 100644
--- a/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextView.java
+++ b/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextView.java
@@ -87,15 +87,16 @@
@Nullable
private Future<PrecomputedTextCompat> mPrecomputedTextFuture;
- public AppCompatTextView(Context context) {
+ public AppCompatTextView(@NonNull Context context) {
this(context, null);
}
- public AppCompatTextView(Context context, AttributeSet attrs) {
+ public AppCompatTextView(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, android.R.attr.textViewStyle);
}
- public AppCompatTextView(Context context, AttributeSet attrs, int defStyleAttr) {
+ public AppCompatTextView(
+ @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(TintContextWrapper.wrap(context), attrs, defStyleAttr);
mBackgroundTintHelper = new AppCompatBackgroundHelper(this);
diff --git a/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextViewAutoSizeHelper.java b/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextViewAutoSizeHelper.java
index f5a6c11a..9866069 100644
--- a/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextViewAutoSizeHelper.java
+++ b/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextViewAutoSizeHelper.java
@@ -99,21 +99,27 @@
private boolean mHasPresetAutoSizeValues = false;
private TextPaint mTempTextPaint;
+ @NonNull
private final TextView mTextView;
private final Context mContext;
- AppCompatTextViewAutoSizeHelper(TextView textView) {
+ AppCompatTextViewAutoSizeHelper(@NonNull TextView textView) {
mTextView = textView;
mContext = mTextView.getContext();
}
- void loadFromAttributes(AttributeSet attrs, int defStyleAttr) {
+ void loadFromAttributes(@Nullable AttributeSet attrs, int defStyleAttr) {
float autoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
float autoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
float autoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.AppCompatTextView,
defStyleAttr, 0);
+ if (Build.VERSION.SDK_INT >= 29) {
+ mTextView.saveAttributeDataForStyleable(mTextView.getContext(),
+ R.styleable.AppCompatTextView, attrs, a,
+ defStyleAttr, 0);
+ }
if (a.hasValue(R.styleable.AppCompatTextView_autoSizeTextType)) {
mAutoSizeTextType = a.getInt(R.styleable.AppCompatTextView_autoSizeTextType,
TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE);
diff --git a/appcompat/src/main/java/androidx/appcompat/widget/AppCompatToggleButton.java b/appcompat/src/main/java/androidx/appcompat/widget/AppCompatToggleButton.java
index 29e5387..da2e649 100644
--- a/appcompat/src/main/java/androidx/appcompat/widget/AppCompatToggleButton.java
+++ b/appcompat/src/main/java/androidx/appcompat/widget/AppCompatToggleButton.java
@@ -20,6 +20,9 @@
import android.util.AttributeSet;
import android.widget.ToggleButton;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
/**
* A {@link ToggleButton} which supports compatible features on older versions of the platform,
* including:
@@ -34,15 +37,16 @@
private final AppCompatTextHelper mTextHelper;
- public AppCompatToggleButton(Context context) {
+ public AppCompatToggleButton(@NonNull Context context) {
this(context, null);
}
- public AppCompatToggleButton(Context context, AttributeSet attrs) {
+ public AppCompatToggleButton(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, android.R.attr.buttonStyleToggle);
}
- public AppCompatToggleButton(Context context, AttributeSet attrs, int defStyleAttr) {
+ public AppCompatToggleButton(
+ @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mTextHelper = new AppCompatTextHelper(this);
diff --git a/appcompat/src/main/java/androidx/appcompat/widget/ButtonBarLayout.java b/appcompat/src/main/java/androidx/appcompat/widget/ButtonBarLayout.java
index 22b3993..bf647a2 100644
--- a/appcompat/src/main/java/androidx/appcompat/widget/ButtonBarLayout.java
+++ b/appcompat/src/main/java/androidx/appcompat/widget/ButtonBarLayout.java
@@ -25,6 +25,8 @@
import android.view.View;
import android.widget.LinearLayout;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.appcompat.R;
import androidx.core.view.ViewCompat;
@@ -47,7 +49,7 @@
private int mMinimumHeight = 0;
- public ButtonBarLayout(Context context, AttributeSet attrs) {
+ public ButtonBarLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
final TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ButtonBarLayout);
if (Build.VERSION.SDK_INT >= 29) {
diff --git a/appcompat/src/main/java/androidx/appcompat/widget/ContentFrameLayout.java b/appcompat/src/main/java/androidx/appcompat/widget/ContentFrameLayout.java
index 3d77e91..fcefd9e 100644
--- a/appcompat/src/main/java/androidx/appcompat/widget/ContentFrameLayout.java
+++ b/appcompat/src/main/java/androidx/appcompat/widget/ContentFrameLayout.java
@@ -30,6 +30,8 @@
import android.util.TypedValue;
import android.widget.FrameLayout;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.core.view.ViewCompat;
@@ -55,15 +57,16 @@
private OnAttachListener mAttachListener;
- public ContentFrameLayout(Context context) {
+ public ContentFrameLayout(@NonNull Context context) {
this(context, null);
}
- public ContentFrameLayout(Context context, AttributeSet attrs) {
+ public ContentFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
- public ContentFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ public ContentFrameLayout(
+ @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mDecorPadding = new Rect();
}
diff --git a/appcompat/src/main/java/androidx/appcompat/widget/DialogTitle.java b/appcompat/src/main/java/androidx/appcompat/widget/DialogTitle.java
index 63b117b..26dd7d3 100644
--- a/appcompat/src/main/java/androidx/appcompat/widget/DialogTitle.java
+++ b/appcompat/src/main/java/androidx/appcompat/widget/DialogTitle.java
@@ -24,6 +24,8 @@
import android.util.AttributeSet;
import android.util.TypedValue;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.appcompat.R;
@@ -36,15 +38,15 @@
@RestrictTo(LIBRARY_GROUP_PREFIX)
public class DialogTitle extends AppCompatTextView {
- public DialogTitle(Context context, AttributeSet attrs, int defStyleAttr) {
+ public DialogTitle(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
- public DialogTitle(Context context, AttributeSet attrs) {
+ public DialogTitle(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
- public DialogTitle(Context context) {
+ public DialogTitle(@NonNull Context context) {
super(context);
}
diff --git a/appcompat/src/main/java/androidx/appcompat/widget/DropDownListView.java b/appcompat/src/main/java/androidx/appcompat/widget/DropDownListView.java
index be2504e..3acff2c 100644
--- a/appcompat/src/main/java/androidx/appcompat/widget/DropDownListView.java
+++ b/appcompat/src/main/java/androidx/appcompat/widget/DropDownListView.java
@@ -114,7 +114,7 @@
*
* @param context this view's context
*/
- DropDownListView(Context context, boolean hijackFocus) {
+ DropDownListView(@NonNull Context context, boolean hijackFocus) {
super(context, null, R.attr.dropDownListViewStyle);
mHijackFocus = hijackFocus;
setCacheColorHint(0); // Transparent, since the background drawable could be anything.
diff --git a/appcompat/src/main/java/androidx/appcompat/widget/FitWindowsFrameLayout.java b/appcompat/src/main/java/androidx/appcompat/widget/FitWindowsFrameLayout.java
index 59f590f..2ce02c6 100644
--- a/appcompat/src/main/java/androidx/appcompat/widget/FitWindowsFrameLayout.java
+++ b/appcompat/src/main/java/androidx/appcompat/widget/FitWindowsFrameLayout.java
@@ -23,6 +23,8 @@
import android.util.AttributeSet;
import android.widget.FrameLayout;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
/**
@@ -33,11 +35,11 @@
private OnFitSystemWindowsListener mListener;
- public FitWindowsFrameLayout(Context context) {
+ public FitWindowsFrameLayout(@NonNull Context context) {
super(context);
}
- public FitWindowsFrameLayout(Context context, AttributeSet attrs) {
+ public FitWindowsFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
diff --git a/appcompat/src/main/java/androidx/appcompat/widget/FitWindowsLinearLayout.java b/appcompat/src/main/java/androidx/appcompat/widget/FitWindowsLinearLayout.java
index dbea5ba..e578bbe 100644
--- a/appcompat/src/main/java/androidx/appcompat/widget/FitWindowsLinearLayout.java
+++ b/appcompat/src/main/java/androidx/appcompat/widget/FitWindowsLinearLayout.java
@@ -23,6 +23,8 @@
import android.util.AttributeSet;
import android.widget.LinearLayout;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
/**
@@ -33,11 +35,11 @@
private OnFitSystemWindowsListener mListener;
- public FitWindowsLinearLayout(Context context) {
+ public FitWindowsLinearLayout(@NonNull Context context) {
super(context);
}
- public FitWindowsLinearLayout(Context context, AttributeSet attrs) {
+ public FitWindowsLinearLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
diff --git a/appcompat/src/main/java/androidx/appcompat/widget/LinearLayoutCompat.java b/appcompat/src/main/java/androidx/appcompat/widget/LinearLayoutCompat.java
index bd16798..ff3f2b8 100644
--- a/appcompat/src/main/java/androidx/appcompat/widget/LinearLayoutCompat.java
+++ b/appcompat/src/main/java/androidx/appcompat/widget/LinearLayoutCompat.java
@@ -23,6 +23,7 @@
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
+import android.os.Build;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
@@ -31,6 +32,8 @@
import android.view.accessibility.AccessibilityNodeInfo;
import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.appcompat.R;
import androidx.core.view.GravityCompat;
@@ -145,19 +148,25 @@
private int mShowDividers;
private int mDividerPadding;
- public LinearLayoutCompat(Context context) {
+ public LinearLayoutCompat(@NonNull Context context) {
this(context, null);
}
- public LinearLayoutCompat(Context context, AttributeSet attrs) {
+ public LinearLayoutCompat(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
- public LinearLayoutCompat(Context context, AttributeSet attrs, int defStyleAttr) {
+ public LinearLayoutCompat(
+ @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
final TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs,
R.styleable.LinearLayoutCompat, defStyleAttr, 0);
+ if (Build.VERSION.SDK_INT >= 29) {
+ saveAttributeDataForStyleable(
+ context, R.styleable.LinearLayoutCompat, attrs,
+ a.getWrappedTypeArray(), defStyleAttr, 0);
+ }
int index = a.getInt(R.styleable.LinearLayoutCompat_android_orientation, -1);
if (index >= 0) {
diff --git a/appcompat/src/main/java/androidx/appcompat/widget/MenuPopupWindow.java b/appcompat/src/main/java/androidx/appcompat/widget/MenuPopupWindow.java
index 8a80b87..b415cd1 100644
--- a/appcompat/src/main/java/androidx/appcompat/widget/MenuPopupWindow.java
+++ b/appcompat/src/main/java/androidx/appcompat/widget/MenuPopupWindow.java
@@ -33,6 +33,7 @@
import android.widget.PopupWindow;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.appcompat.view.menu.ListMenuItemView;
import androidx.appcompat.view.menu.MenuAdapter;
@@ -68,10 +69,12 @@
private MenuItemHoverListener mHoverListener;
- public MenuPopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ public MenuPopupWindow(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
+ @NonNull
@Override
DropDownListView createDropDownListView(Context context, boolean hijackFocus) {
MenuDropDownListView view = new MenuDropDownListView(context, hijackFocus);
diff --git a/appcompat/src/main/java/androidx/appcompat/widget/ScrollingTabContainerView.java b/appcompat/src/main/java/androidx/appcompat/widget/ScrollingTabContainerView.java
index 07da6b9..cdd46c0 100644
--- a/appcompat/src/main/java/androidx/appcompat/widget/ScrollingTabContainerView.java
+++ b/appcompat/src/main/java/androidx/appcompat/widget/ScrollingTabContainerView.java
@@ -42,6 +42,7 @@
import android.widget.Spinner;
import android.widget.TextView;
+import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
import androidx.appcompat.R;
import androidx.appcompat.app.ActionBar;
@@ -78,7 +79,7 @@
private static final int FADE_DURATION = 200;
- public ScrollingTabContainerView(Context context) {
+ public ScrollingTabContainerView(@NonNull Context context) {
super(context);
setHorizontalScrollBarEnabled(false);
diff --git a/appcompat/src/main/java/androidx/appcompat/widget/SearchView.java b/appcompat/src/main/java/androidx/appcompat/widget/SearchView.java
index 4d0ae54..e1fc231 100644
--- a/appcompat/src/main/java/androidx/appcompat/widget/SearchView.java
+++ b/appcompat/src/main/java/androidx/appcompat/widget/SearchView.java
@@ -68,6 +68,7 @@
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.appcompat.R;
@@ -263,15 +264,15 @@
boolean onSuggestionClick(int position);
}
- public SearchView(Context context) {
+ public SearchView(@NonNull Context context) {
this(context, null);
}
- public SearchView(Context context, AttributeSet attrs) {
+ public SearchView(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, R.attr.searchViewStyle);
}
- public SearchView(Context context, AttributeSet attrs, int defStyleAttr) {
+ public SearchView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
final TintTypedArray a = TintTypedArray.obtainStyledAttributes(context,
diff --git a/appcompat/src/main/java/androidx/appcompat/widget/SwitchCompat.java b/appcompat/src/main/java/androidx/appcompat/widget/SwitchCompat.java
index f6964a2..fd237f0 100644
--- a/appcompat/src/main/java/androidx/appcompat/widget/SwitchCompat.java
+++ b/appcompat/src/main/java/androidx/appcompat/widget/SwitchCompat.java
@@ -46,6 +46,7 @@
import android.widget.CompoundButton;
import android.widget.TextView;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.R;
import androidx.appcompat.content.res.AppCompatResources;
@@ -190,7 +191,7 @@
*
* @param context The Context that will determine this widget's theming.
*/
- public SwitchCompat(Context context) {
+ public SwitchCompat(@NonNull Context context) {
this(context, null);
}
@@ -201,7 +202,7 @@
* @param context The Context that will determine this widget's theming.
* @param attrs Specification of attributes that should deviate from default styling.
*/
- public SwitchCompat(Context context, AttributeSet attrs) {
+ public SwitchCompat(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, R.attr.switchStyle);
}
@@ -215,7 +216,7 @@
* reference to a style resource that supplies default values for
* the view. Can be 0 to not look for defaults.
*/
- public SwitchCompat(Context context, AttributeSet attrs, int defStyleAttr) {
+ public SwitchCompat(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
@@ -225,6 +226,12 @@
final TintTypedArray a = TintTypedArray.obtainStyledAttributes(context,
attrs, R.styleable.SwitchCompat, defStyleAttr, 0);
+ if (Build.VERSION.SDK_INT >= 29) {
+ saveAttributeDataForStyleable(
+ context, R.styleable.SwitchCompat, attrs,
+ a.getWrappedTypeArray(), defStyleAttr, 0);
+ }
+
mThumbDrawable = a.getDrawable(R.styleable.SwitchCompat_android_thumb);
if (mThumbDrawable != null) {
mThumbDrawable.setCallback(this);
diff --git a/appcompat/src/main/java/androidx/appcompat/widget/TintTypedArray.java b/appcompat/src/main/java/androidx/appcompat/widget/TintTypedArray.java
index d0038bc..a365942 100644
--- a/appcompat/src/main/java/androidx/appcompat/widget/TintTypedArray.java
+++ b/appcompat/src/main/java/androidx/appcompat/widget/TintTypedArray.java
@@ -69,6 +69,13 @@
mWrapped = array;
}
+ /**
+ * Beware, you very likely do not intend to this method. Proceed with caution.
+ */
+ public TypedArray getWrappedTypeArray() {
+ return mWrapped;
+ }
+
public Drawable getDrawable(int index) {
if (mWrapped.hasValue(index)) {
final int resourceId = mWrapped.getResourceId(index, 0);
diff --git a/appcompat/src/main/java/androidx/appcompat/widget/Toolbar.java b/appcompat/src/main/java/androidx/appcompat/widget/Toolbar.java
index 9b4a11d..5ba7545 100644
--- a/appcompat/src/main/java/androidx/appcompat/widget/Toolbar.java
+++ b/appcompat/src/main/java/androidx/appcompat/widget/Toolbar.java
@@ -224,20 +224,25 @@
}
};
- public Toolbar(Context context) {
+ public Toolbar(@NonNull Context context) {
this(context, null);
}
- public Toolbar(Context context, @Nullable AttributeSet attrs) {
+ public Toolbar(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, R.attr.toolbarStyle);
}
- public Toolbar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ public Toolbar(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// Need to use getContext() here so that we use the themed context
final TintTypedArray a = TintTypedArray.obtainStyledAttributes(getContext(), attrs,
R.styleable.Toolbar, defStyleAttr, 0);
+ if (Build.VERSION.SDK_INT >= 29) {
+ saveAttributeDataForStyleable(
+ context, R.styleable.Toolbar, attrs,
+ a.getWrappedTypeArray(), defStyleAttr, 0);
+ }
mTitleTextAppearance = a.getResourceId(R.styleable.Toolbar_titleTextAppearance, 0);
mSubtitleTextAppearance = a.getResourceId(R.styleable.Toolbar_subtitleTextAppearance, 0);
diff --git a/appcompat/src/main/java/androidx/appcompat/widget/TooltipPopup.java b/appcompat/src/main/java/androidx/appcompat/widget/TooltipPopup.java
index 6697a11..2186447 100644
--- a/appcompat/src/main/java/androidx/appcompat/widget/TooltipPopup.java
+++ b/appcompat/src/main/java/androidx/appcompat/widget/TooltipPopup.java
@@ -33,6 +33,7 @@
import android.view.WindowManager;
import android.widget.TextView;
+import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
import androidx.appcompat.R;
@@ -55,7 +56,7 @@
private final int[] mTmpAnchorPos = new int[2];
private final int[] mTmpAppPos = new int[2];
- TooltipPopup(Context context) {
+ TooltipPopup(@NonNull Context context) {
mContext = context;
mContentView = LayoutInflater.from(mContext).inflate(R.layout.abc_tooltip, null);
diff --git a/appcompat/src/main/java/androidx/appcompat/widget/ViewStubCompat.java b/appcompat/src/main/java/androidx/appcompat/widget/ViewStubCompat.java
index 4473648..c43a87b 100644
--- a/appcompat/src/main/java/androidx/appcompat/widget/ViewStubCompat.java
+++ b/appcompat/src/main/java/androidx/appcompat/widget/ViewStubCompat.java
@@ -28,6 +28,8 @@
import android.view.ViewGroup;
import android.view.ViewParent;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.appcompat.R;
@@ -49,11 +51,11 @@
private LayoutInflater mInflater;
private OnInflateListener mInflateListener;
- public ViewStubCompat(Context context, AttributeSet attrs) {
+ public ViewStubCompat(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
- public ViewStubCompat(Context context, AttributeSet attrs, int defStyle) {
+ public ViewStubCompat(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ViewStubCompat,
diff --git a/asynclayoutinflater/build.gradle b/asynclayoutinflater/build.gradle
index 2c12378..3dcd2f0 100644
--- a/asynclayoutinflater/build.gradle
+++ b/asynclayoutinflater/build.gradle
@@ -9,7 +9,7 @@
dependencies {
api("androidx.annotation:annotation:1.1.0")
- api("androidx.core:core:1.1.0-rc01")
+ api("androidx.core:core:1.1.0")
}
androidx {
diff --git a/autofill/api/1.0.0-alpha02.txt b/autofill/api/1.0.0-alpha02.txt
index b193fb7..a56ee53 100644
--- a/autofill/api/1.0.0-alpha02.txt
+++ b/autofill/api/1.0.0-alpha02.txt
@@ -38,6 +38,15 @@
field public static final String AUTOFILL_HINT_POSTAL_ADDRESS_REGION = "addressRegion";
field public static final String AUTOFILL_HINT_POSTAL_ADDRESS_STREET_ADDRESS = "streetAddress";
field public static final String AUTOFILL_HINT_POSTAL_CODE = "postalCode";
+ field public static final String AUTOFILL_HINT_SMS_OTP = "smsOTPCode";
+ field public static final String AUTOFILL_HINT_SMS_OTP_1 = "smsOTPCode1";
+ field public static final String AUTOFILL_HINT_SMS_OTP_2 = "smsOTPCode2";
+ field public static final String AUTOFILL_HINT_SMS_OTP_3 = "smsOTPCode3";
+ field public static final String AUTOFILL_HINT_SMS_OTP_4 = "smsOTPCode4";
+ field public static final String AUTOFILL_HINT_SMS_OTP_5 = "smsOTPCode5";
+ field public static final String AUTOFILL_HINT_SMS_OTP_6 = "smsOTPCode6";
+ field public static final String AUTOFILL_HINT_SMS_OTP_7 = "smsOTPCode7";
+ field public static final String AUTOFILL_HINT_SMS_OTP_8 = "smsOTPCode8";
field public static final String AUTOFILL_HINT_USERNAME = "username";
}
diff --git a/autofill/api/current.txt b/autofill/api/current.txt
index b193fb7..a56ee53 100644
--- a/autofill/api/current.txt
+++ b/autofill/api/current.txt
@@ -38,6 +38,15 @@
field public static final String AUTOFILL_HINT_POSTAL_ADDRESS_REGION = "addressRegion";
field public static final String AUTOFILL_HINT_POSTAL_ADDRESS_STREET_ADDRESS = "streetAddress";
field public static final String AUTOFILL_HINT_POSTAL_CODE = "postalCode";
+ field public static final String AUTOFILL_HINT_SMS_OTP = "smsOTPCode";
+ field public static final String AUTOFILL_HINT_SMS_OTP_1 = "smsOTPCode1";
+ field public static final String AUTOFILL_HINT_SMS_OTP_2 = "smsOTPCode2";
+ field public static final String AUTOFILL_HINT_SMS_OTP_3 = "smsOTPCode3";
+ field public static final String AUTOFILL_HINT_SMS_OTP_4 = "smsOTPCode4";
+ field public static final String AUTOFILL_HINT_SMS_OTP_5 = "smsOTPCode5";
+ field public static final String AUTOFILL_HINT_SMS_OTP_6 = "smsOTPCode6";
+ field public static final String AUTOFILL_HINT_SMS_OTP_7 = "smsOTPCode7";
+ field public static final String AUTOFILL_HINT_SMS_OTP_8 = "smsOTPCode8";
field public static final String AUTOFILL_HINT_USERNAME = "username";
}
diff --git a/autofill/api/restricted_1.0.0-alpha02.txt b/autofill/api/restricted_1.0.0-alpha02.txt
index b193fb7..a56ee53 100644
--- a/autofill/api/restricted_1.0.0-alpha02.txt
+++ b/autofill/api/restricted_1.0.0-alpha02.txt
@@ -38,6 +38,15 @@
field public static final String AUTOFILL_HINT_POSTAL_ADDRESS_REGION = "addressRegion";
field public static final String AUTOFILL_HINT_POSTAL_ADDRESS_STREET_ADDRESS = "streetAddress";
field public static final String AUTOFILL_HINT_POSTAL_CODE = "postalCode";
+ field public static final String AUTOFILL_HINT_SMS_OTP = "smsOTPCode";
+ field public static final String AUTOFILL_HINT_SMS_OTP_1 = "smsOTPCode1";
+ field public static final String AUTOFILL_HINT_SMS_OTP_2 = "smsOTPCode2";
+ field public static final String AUTOFILL_HINT_SMS_OTP_3 = "smsOTPCode3";
+ field public static final String AUTOFILL_HINT_SMS_OTP_4 = "smsOTPCode4";
+ field public static final String AUTOFILL_HINT_SMS_OTP_5 = "smsOTPCode5";
+ field public static final String AUTOFILL_HINT_SMS_OTP_6 = "smsOTPCode6";
+ field public static final String AUTOFILL_HINT_SMS_OTP_7 = "smsOTPCode7";
+ field public static final String AUTOFILL_HINT_SMS_OTP_8 = "smsOTPCode8";
field public static final String AUTOFILL_HINT_USERNAME = "username";
}
diff --git a/autofill/api/restricted_current.txt b/autofill/api/restricted_current.txt
index b193fb7..a56ee53 100644
--- a/autofill/api/restricted_current.txt
+++ b/autofill/api/restricted_current.txt
@@ -38,6 +38,15 @@
field public static final String AUTOFILL_HINT_POSTAL_ADDRESS_REGION = "addressRegion";
field public static final String AUTOFILL_HINT_POSTAL_ADDRESS_STREET_ADDRESS = "streetAddress";
field public static final String AUTOFILL_HINT_POSTAL_CODE = "postalCode";
+ field public static final String AUTOFILL_HINT_SMS_OTP = "smsOTPCode";
+ field public static final String AUTOFILL_HINT_SMS_OTP_1 = "smsOTPCode1";
+ field public static final String AUTOFILL_HINT_SMS_OTP_2 = "smsOTPCode2";
+ field public static final String AUTOFILL_HINT_SMS_OTP_3 = "smsOTPCode3";
+ field public static final String AUTOFILL_HINT_SMS_OTP_4 = "smsOTPCode4";
+ field public static final String AUTOFILL_HINT_SMS_OTP_5 = "smsOTPCode5";
+ field public static final String AUTOFILL_HINT_SMS_OTP_6 = "smsOTPCode6";
+ field public static final String AUTOFILL_HINT_SMS_OTP_7 = "smsOTPCode7";
+ field public static final String AUTOFILL_HINT_SMS_OTP_8 = "smsOTPCode8";
field public static final String AUTOFILL_HINT_USERNAME = "username";
}
diff --git a/autofill/src/main/java/androidx/autofill/HintConstants.java b/autofill/src/main/java/androidx/autofill/HintConstants.java
index adc319e..4c4c5fe 100644
--- a/autofill/src/main/java/androidx/autofill/HintConstants.java
+++ b/autofill/src/main/java/androidx/autofill/HintConstants.java
@@ -542,4 +542,124 @@
* hints.
*/
public static final String AUTOFILL_HINT_BIRTH_DATE_YEAR = "birthDateYear";
+
+ /**
+ * Hint indicating that this view can be autofilled with a SMS One Time Password (OTP).
+ *
+ * <p>Can be used with either {@link android.view.View#setAutofillHints(String[])} or <a
+ * href="#attr_android:autofillHint">{@code android:autofillHint}</a> (in which case the value
+ * should be <code>{@value #AUTOFILL_HINT_SMS_OTP}</code>).
+ *
+ * <p>When annotating OTP code fields which map to a single digit of the code consider using
+ * <code>{@value #AUTOFILL_HINT_SMS_OTP_1}</code> through <code>
+ * {@value #AUTOFILL_HINT_SMS_OTP_8}</code>
+ *
+ * <p>See {@link android.view.View#setAutofillHints(String...)} for more info about autofill
+ * hints.
+ */
+ public static final String AUTOFILL_HINT_SMS_OTP = "smsOTPCode";
+
+ /**
+ * Hint indicating that this view can be autofilled with the first character/digit of a SMS One
+ * Time Password (OTP).
+ *
+ * <p>Can be used with either {@link android.view.View#setAutofillHints(String[])} or <a
+ * href="#attr_android:autofillHint">{@code android:autofillHint}</a> (in which case the value
+ * should be <code>{@value #AUTOFILL_HINT_SMS_OTP_1}</code>).
+ *
+ * <p>See {@link android.view.View#setAutofillHints(String...)} for more info about autofill
+ * hints.
+ */
+ public static final String AUTOFILL_HINT_SMS_OTP_1 = "smsOTPCode1";
+
+ /**
+ * Hint indicating that this view can be autofilled with the second character/digit of a SMS One
+ * Time Password (OTP).
+ *
+ * <p>Can be used with either {@link android.view.View#setAutofillHints(String[])} or <a
+ * href="#attr_android:autofillHint">{@code android:autofillHint}</a> (in which case the value
+ * should be <code>{@value #AUTOFILL_HINT_SMS_OTP_2}</code>).
+ *
+ * <p>See {@link android.view.View#setAutofillHints(String...)} for more info about autofill
+ * hints.
+ */
+ public static final String AUTOFILL_HINT_SMS_OTP_2 = "smsOTPCode2";
+
+ /**
+ * Hint indicating that this view can be autofilled with the third character/digit of a SMS One
+ * Time Password (OTP).
+ *
+ * <p>Can be used with either {@link android.view.View#setAutofillHints(String[])} or <a
+ * href="#attr_android:autofillHint">{@code android:autofillHint}</a> (in which case the value
+ * should be <code>{@value #AUTOFILL_HINT_SMS_OTP_3}</code>).
+ *
+ * <p>See {@link android.view.View#setAutofillHints(String...)} for more info about autofill
+ * hints.
+ */
+ public static final String AUTOFILL_HINT_SMS_OTP_3 = "smsOTPCode3";
+
+ /**
+ * Hint indicating that this view can be autofilled with the fourth character/digit of a SMS One
+ * Time Password (OTP).
+ *
+ * <p>Can be used with either {@link android.view.View#setAutofillHints(String[])} or <a
+ * href="#attr_android:autofillHint">{@code android:autofillHint}</a> (in which case the value
+ * should be <code>{@value #AUTOFILL_HINT_SMS_OTP_4}</code>).
+ *
+ * <p>See {@link android.view.View#setAutofillHints(String...)} for more info about autofill
+ * hints.
+ */
+ public static final String AUTOFILL_HINT_SMS_OTP_4 = "smsOTPCode4";
+
+ /**
+ * Hint indicating that this view can be autofilled with the fifth character/digit of a SMS One
+ * Time Password (OTP).
+ *
+ * <p>Can be used with either {@link android.view.View#setAutofillHints(String[])} or <a
+ * href="#attr_android:autofillHint">{@code android:autofillHint}</a> (in which case the value
+ * should be <code>{@value #AUTOFILL_HINT_SMS_OTP_5}</code>).
+ *
+ * <p>See {@link android.view.View#setAutofillHints(String...)} for more info about autofill
+ * hints.
+ */
+ public static final String AUTOFILL_HINT_SMS_OTP_5 = "smsOTPCode5";
+
+ /**
+ * Hint indicating that this view can be autofilled with the sixth character/digit of a SMS One
+ * Time Password (OTP).
+ *
+ * <p>Can be used with either {@link android.view.View#setAutofillHints(String[])} or <a
+ * href="#attr_android:autofillHint">{@code android:autofillHint}</a> (in which case the value
+ * should be <code>{@value #AUTOFILL_HINT_SMS_OTP_6}</code>).
+ *
+ * <p>See {@link android.view.View#setAutofillHints(String...)} for more info about autofill
+ * hints.
+ */
+ public static final String AUTOFILL_HINT_SMS_OTP_6 = "smsOTPCode6";
+
+ /**
+ * Hint indicating that this view can be autofilled with the seventh character/digit of a SMS
+ * One Time Password (OTP).
+ *
+ * <p>Can be used with either {@link android.view.View#setAutofillHints(String[])} or <a
+ * href="#attr_android:autofillHint">{@code android:autofillHint}</a> (in which case the value
+ * should be <code>{@value #AUTOFILL_HINT_SMS_OTP_7}</code>).
+ *
+ * <p>See {@link android.view.View#setAutofillHints(String...)} for more info about autofill
+ * hints.
+ */
+ public static final String AUTOFILL_HINT_SMS_OTP_7 = "smsOTPCode7";
+
+ /**
+ * Hint indicating that this view can be autofilled with the eighth character/digit of a SMS One
+ * Time Password (OTP).
+ *
+ * <p>Can be used with either {@link android.view.View#setAutofillHints(String[])} or <a
+ * href="#attr_android:autofillHint">{@code android:autofillHint}</a> (in which case the value
+ * should be <code>{@value #AUTOFILL_HINT_SMS_OTP_8}</code>).
+ *
+ * <p>See {@link android.view.View#setAutofillHints(String...)} for more info about autofill
+ * hints.
+ */
+ public static final String AUTOFILL_HINT_SMS_OTP_8 = "smsOTPCode8";
}
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..050c32d 100644
--- a/benchmark/benchmark/src/androidTest/java/androidx/benchmark/benchmark/ParameterizedBenchmark.kt
+++ b/benchmark/benchmark/src/androidTest/java/androidx/benchmark/benchmark/ParameterizedBenchmark.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/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 85%
rename from benchmark/build.gradle
rename to benchmark/common/build.gradle
index 386f71d..488d327 100644
--- a/benchmark/build.gradle
+++ b/benchmark/common/build.gradle
@@ -26,20 +26,19 @@
}
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)
}
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 97%
rename from benchmark/src/androidTest/java/androidx/benchmark/ResultWriterTest.kt
rename to benchmark/common/src/androidTest/java/androidx/benchmark/ResultWriterTest.kt
index 20ed927..d48f976 100644
--- a/benchmark/src/androidTest/java/androidx/benchmark/ResultWriterTest.kt
+++ b/benchmark/common/src/androidTest/java/androidx/benchmark/ResultWriterTest.kt
@@ -66,7 +66,7 @@
fun validateJson() {
val tempFile = tempFolder.newFile()
- val sustainedPerformanceModeInUse = AndroidBenchmarkRunner.sustainedPerformanceModeInUse
+ val sustainedPerformanceModeInUse = IsolationActivity.sustainedPerformanceModeInUse
ResultWriter.writeReport(tempFile, listOf(reportA, reportB))
assertEquals(
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 93%
rename from benchmark/src/main/java/androidx/benchmark/Arguments.kt
rename to benchmark/common/src/main/java/androidx/benchmark/Arguments.kt
index 75d15ab..e6163bf 100644
--- a/benchmark/src/main/java/androidx/benchmark/Arguments.kt
+++ b/benchmark/common/src/main/java/androidx/benchmark/Arguments.kt
@@ -29,8 +29,9 @@
var argumentSource: Bundle? = null
internal object Arguments {
- val startupMode: Boolean
+ val additionalTestOutputDir: String?
val outputEnable: Boolean
+ val startupMode: Boolean
val suppressedErrors: Set<String>
init {
@@ -48,5 +49,7 @@
.map { it.trim() }
.filter { it.isNotEmpty() }
.toSet()
+
+ additionalTestOutputDir = arguments.getString("additionalTestOutputDir")
}
}
\ No newline at end of file
diff --git a/benchmark/src/main/java/androidx/benchmark/BenchmarkState.kt b/benchmark/common/src/main/java/androidx/benchmark/BenchmarkState.kt
similarity index 90%
rename from benchmark/src/main/java/androidx/benchmark/BenchmarkState.kt
rename to benchmark/common/src/main/java/androidx/benchmark/BenchmarkState.kt
index 4e3b050..7d55169 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
@@ -258,10 +277,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 +313,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,7 +328,7 @@
}
if (performThrottleChecks &&
!CpuInfo.locked &&
- !AndroidBenchmarkRunner.sustainedPerformanceModeInUse &&
+ !IsolationActivity.sustainedPerformanceModeInUse &&
!Errors.isEmulator
) {
ThrottleDetector.computeThrottleBaseline()
@@ -393,17 +414,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 +440,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."
@@ -483,7 +523,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..18fe7c7
--- /dev/null
+++ b/benchmark/common/src/main/java/androidx/benchmark/IsolationActivity.kt
@@ -0,0 +1,175 @@
+/*
+ * 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.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.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(sustainedPerfCallbacks)
+
+ // trigger the one missed lifecycle event, from registering the callbacks late
+ sustainedPerfCallbacks.onActivityCreated(this, savedInstanceState)
+
+ // 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 sustainedPerfCallbacks = object : Application.ActivityLifecycleCallbacks {
+ @SuppressLint("NewApi") // window API guarded by [isSustainedPerformanceModeSupported]
+ override fun onActivityCreated(activity: Activity, bundle: Bundle?) {
+ activity.window.setSustainedPerformanceMode(true)
+ }
+ 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 92%
rename from benchmark/src/main/java/androidx/benchmark/ResultWriter.kt
rename to benchmark/common/src/main/java/androidx/benchmark/ResultWriter.kt
index 06dfb50..6b6ca2e 100644
--- a/benchmark/src/main/java/androidx/benchmark/ResultWriter.kt
+++ b/benchmark/common/src/main/java/androidx/benchmark/ResultWriter.kt
@@ -36,8 +36,10 @@
// Ideally, append for efficiency
val packageName =
InstrumentationRegistry.getInstrumentation().targetContext!!.packageName
- @Suppress("DEPRECATION") // b/134925431
- val filePath = getExternalStoragePublicDirectory(DIRECTORY_DOWNLOADS)
+
+ @Suppress("DEPRECATION") // Legacy code path for versions of agp older than 3.6
+ val filePath = Arguments.additionalTestOutputDir?.let { File(it) }
+ ?: getExternalStoragePublicDirectory(DIRECTORY_DOWNLOADS)
val file = File(filePath, "$packageName-benchmarkData.json")
writeReport(file, reports)
}
@@ -63,7 +65,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()
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..949c11a 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
@@ -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 af03f51..1a57e4f9 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
@@ -21,7 +21,6 @@
import com.android.build.gradle.LibraryExtension
import org.gradle.api.Plugin
import org.gradle.api.Project
-import org.gradle.api.Task
import org.gradle.api.tasks.StopExecutionException
class BenchmarkPlugin : Plugin<Project> {
@@ -64,33 +63,42 @@
}
private fun configureWithAndroidExtension(project: Project, extension: BaseExtension) {
+ val defaultConfig = extension.defaultConfig
+ val testInstrumentationArgs = defaultConfig.testInstrumentationRunnerArguments
+
// 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
- project.tasks.configureEach {
+ project.configurations.configureEach {
if (!enabledOutput &&
- !project.rootProject.hasProperty("android.injected.invoked.from.ide")
+ !project.rootProject.hasProperty("android.injected.invoked.from.ide") &&
+ !testInstrumentationArgs.containsKey("androidx.benchmark.output.enable")
) {
enabledOutput = true
// NOTE: This argument is checked by ResultWriter to enable CI reports.
- extension.defaultConfig.testInstrumentationRunnerArgument(
+ defaultConfig.testInstrumentationRunnerArgument(
"androidx.benchmark.output.enable",
"true"
)
- extension.defaultConfig.testInstrumentationRunnerArgument(
- "no-isolated-storage",
- "1"
- )
+ if (!testInstrumentationArgs.containsKey("additionalTestOutputDir")) {
+ defaultConfig.testInstrumentationRunnerArgument("no-isolated-storage", "1")
+ }
}
}
- 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) {
@@ -110,9 +118,11 @@
// extension variants have been resolved.
var applied = false
extensionVariants.all {
- if (!applied) {
+ if (!applied && !testInstrumentationArgs.containsKey("additionalTestOutputDir")) {
applied = true
+ // Only enable pulling benchmark data through this plugin on older versions of AGP
+ // that do not yet enable this flag.
project.tasks.register("benchmarkReport", BenchmarkReportTask::class.java)
.configure {
it.adbPath.set(extension.adbExecutable.absolutePath)
@@ -120,38 +130,12 @@
}
project.tasks.named("connectedAndroidTest").configure {
- configureWithConnectedAndroidTest(project, it)
+ // The task benchmarkReport must be registered by this point, and is responsible
+ // for pulling report data from all connected devices onto host machine through
+ // adb.
+ it.finalizedBy("benchmarkReport")
}
}
}
}
-
- private fun configureWithConnectedAndroidTest(project: Project, connectedAndroidTest: Task) {
- // The task benchmarkReport must be registered by this point, and is responsible for
- // pulling report data from all connected devices onto host machine through adb.
- connectedAndroidTest.finalizedBy("benchmarkReport")
-
- var hasJetpackBenchmark = false
-
- project.configurations.matching { it.name.contains("androidTest") }.all {
- it.allDependencies.all { dependency ->
- if (dependency.name == "benchmark" && dependency.group == "androidx.benchmark") {
- hasJetpackBenchmark = true
- }
- }
- }
-
- if (!hasJetpackBenchmark) {
- throw StopExecutionException(
- """Project ${project.name} missing required project dependency,
- androidx.benchmark:benchmark. The androidx.benchmark plugin is meant to be
- used in conjunction with the androix.benchmark library, but it was not found
- within this project's dependencies. You can add the androidx.benchmark library
- to your project by including androidTestImplementation
- 'androidx.benchmark:benchmark:<version>' in the dependencies block of the
- project build.gradle file"""
- .trimIndent()
- )
- }
- }
}
diff --git a/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/BenchmarkReportTask.kt b/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/BenchmarkReportTask.kt
index 8a39554..baae2fc 100644
--- a/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/BenchmarkReportTask.kt
+++ b/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/BenchmarkReportTask.kt
@@ -69,9 +69,7 @@
for (deviceId in deviceIds) {
val dataDir = getReportDirForDevice(adb, deviceId)
if (dataDir.isBlank()) {
- throw StopExecutionException(
- "Failed to find benchmark reports on device: $deviceId"
- )
+ throw StopExecutionException("Failed to find benchmark report on device: $deviceId")
}
val outDir = File(benchmarkReportDir, deviceId)
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 4511e38..73a987d 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
@@ -27,6 +27,7 @@
import java.io.File
import java.util.Properties
import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
import kotlin.test.assertTrue
@RunWith(JUnit4::class)
@@ -109,7 +110,6 @@
val output = gradleRunner.withArguments("tasks").build()
assertTrue { output.output.contains("lockClocks - ") }
assertTrue { output.output.contains("unlockClocks - ") }
- assertTrue { output.output.contains("benchmarkReport - ") }
}
@Test
@@ -144,7 +144,6 @@
val output = gradleRunner.withArguments("tasks").build()
assertTrue { output.output.contains("lockClocks - ") }
assertTrue { output.output.contains("unlockClocks - ") }
- assertTrue { output.output.contains("benchmarkReport - ") }
}
@Test
@@ -205,9 +204,9 @@
""".trimIndent()
)
- assertFailsWith(UnexpectedBuildFailure::class) {
- gradleRunner.withArguments("-m", "connectedAndroidTest").build()
- }
+ val output = gradleRunner.withArguments("tasks").build()
+ assertTrue { output.output.contains("lockClocks - ") }
+ assertTrue { output.output.contains("unlockClocks - ") }
}
@Test
@@ -242,6 +241,89 @@
val output = gradleRunner.withArguments("tasks").build()
assertTrue { output.output.contains("lockClocks - ") }
assertTrue { output.output.contains("unlockClocks - ") }
+ }
+
+ @Test
+ fun applyPluginOnAgp36() {
+ buildFile.writeText(
+ """
+ plugins {
+ id('androidx.benchmark')
+ id('com.android.library')
+ }
+
+ repositories {
+ maven { url "$prebuiltsRepo/androidx/external" }
+ maven { url "$prebuiltsRepo/androidx/internal" }
+ }
+
+ android {
+ compileSdkVersion $compileSdkVersion
+ buildToolsVersion "$buildToolsVersion"
+
+ defaultConfig {
+ minSdkVersion $minSdkVersion
+ testInstrumentationRunnerArguments additionalTestOutputDir: "/fake_path/files"
+ }
+ }
+
+ dependencies {
+ androidTestImplementation "androidx.benchmark:benchmark:1.0.0-alpha01"
+ }
+ """.trimIndent()
+ )
+
+ val output = gradleRunner.withArguments("tasks").build()
+ assertTrue { output.output.contains("lockClocks - ") }
+ assertTrue { output.output.contains("unlockClocks - ") }
+
+ // Should depend on AGP to pull benchmark reports via additionalTestOutputDir.
+ assertFalse { output.output.contains("benchmarkReport - ") }
+ }
+
+ @Test
+ fun applyPluginOnAgp35() {
+ buildFile.writeText(
+ """
+ plugins {
+ id('androidx.benchmark')
+ id('com.android.library')
+ }
+
+ repositories {
+ maven { url "$prebuiltsRepo/androidx/external" }
+ maven { url "$prebuiltsRepo/androidx/internal" }
+ }
+
+ android {
+ compileSdkVersion $compileSdkVersion
+ buildToolsVersion "$buildToolsVersion"
+
+ defaultConfig {
+ minSdkVersion $minSdkVersion
+ testInstrumentationRunnerArguments.remove("additionalTestOutputDir")
+ }
+ }
+
+ dependencies {
+ androidTestImplementation "androidx.benchmark:benchmark:1.0.0-alpha01"
+
+ }
+
+ tasks.register("printInstrumentationArgs") {
+ println android.defaultConfig.testInstrumentationRunnerArguments
+ }
+ """.trimIndent()
+ )
+
+ val output = gradleRunner.withArguments("tasks").build()
+ assertTrue { output.output.contains("lockClocks - ") }
+ assertTrue { output.output.contains("unlockClocks - ") }
+
+ // Should try to pull benchmark reports via legacy BenchmarkPlugin code path.
assertTrue { output.output.contains("benchmarkReport - ") }
+
+ val argsOutput = gradleRunner.withArguments("printInstrumentationArgs").build()
+ assertTrue { argsOutput.output.contains("no-isolated-storage:1") }
}
}
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..6fa16a5 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 80%
rename from benchmark/src/main/java/androidx/benchmark/BenchmarkRule.kt
rename to benchmark/junit4/src/main/java/androidx/benchmark/junit4/BenchmarkRule.kt
index 3b3875a..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
@@ -64,9 +64,9 @@
* ```
*
* Benchmark results will be output:
- * - Summary in AndroidStudio in the test log,
+ * - Summary in AndroidStudio in the test log
+ * - In JSON format, on the host
* - In simple form in Logcat with the tag "Benchmark"
- * - In csv form in Logcat with the tag "BenchmarkCsv"
* - To the instrumentation status result Bundle on the gradle command line
*
* Every test in the Class using this @Rule must contain a single benchmark.
@@ -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/biometric/build.gradle b/biometric/build.gradle
index e423137..e1dbcfe 100644
--- a/biometric/build.gradle
+++ b/biometric/build.gradle
@@ -10,7 +10,7 @@
dependencies {
api("androidx.annotation:annotation:1.1.0")
api("androidx.appcompat:appcompat:1.1.0-rc01")
- api("androidx.core:core:1.1.0-rc02")
+ api("androidx.core:core:1.1.0")
api("androidx.fragment:fragment:1.1.0-rc01")
}
diff --git a/browser/api/1.2.0-alpha06.txt b/browser/api/1.2.0-alpha06.txt
new file mode 100644
index 0000000..a38cd9b
--- /dev/null
+++ b/browser/api/1.2.0-alpha06.txt
@@ -0,0 +1,279 @@
+// 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";
+ }
+
+}
+
+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/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 4e2c30d..798e756 100644
--- a/browser/api/current.txt
+++ b/browser/api/current.txt
@@ -152,6 +152,7 @@
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);
@@ -243,16 +244,58 @@
package androidx.browser.trusted {
- public class TrustedWebActivityBuilder {
- ctor public TrustedWebActivityBuilder(android.content.Context, android.net.Uri);
+ 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 void launchActivity(androidx.browser.customtabs.CustomTabsSession);
- method public androidx.browser.trusted.TrustedWebActivityBuilder setAdditionalTrustedOrigins(java.util.List<java.lang.String!>);
- method public androidx.browser.trusted.TrustedWebActivityBuilder setSplashScreenParams(android.os.Bundle);
+ 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 {
diff --git a/benchmark/api/res-1.0.0-alpha01.txt b/browser/api/res-1.2.0-alpha06.txt
similarity index 100%
copy from benchmark/api/res-1.0.0-alpha01.txt
copy to browser/api/res-1.2.0-alpha06.txt
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-alpha06.txt b/browser/api/restricted_1.2.0-alpha06.txt
new file mode 100644
index 0000000..8f41848
--- /dev/null
+++ b/browser/api/restricted_1.2.0-alpha06.txt
@@ -0,0 +1,300 @@
+// 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";
+ }
+
+
+
+
+
+}
+
+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_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 8a1533f..9cdf40e 100644
--- a/browser/api/restricted_current.txt
+++ b/browser/api/restricted_current.txt
@@ -164,6 +164,7 @@
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);
@@ -260,19 +261,57 @@
package androidx.browser.trusted {
- public class TrustedWebActivityBuilder {
- ctor public TrustedWebActivityBuilder(android.content.Context, android.net.Uri);
+ 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 void launchActivity(androidx.browser.customtabs.CustomTabsSession);
- method public androidx.browser.trusted.TrustedWebActivityBuilder setAdditionalTrustedOrigins(java.util.List<java.lang.String!>);
- method public androidx.browser.trusted.TrustedWebActivityBuilder setSplashScreenParams(android.os.Bundle);
+ 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);
+ }
}
diff --git a/browser/build.gradle b/browser/build.gradle
index 88a21283..5dd3789 100644
--- a/browser/build.gradle
+++ b/browser/build.gradle
@@ -12,15 +12,17 @@
defaultConfig {
minSdkVersion 16
}
+
+ testOptions.unitTests.includeAndroidResources = true
}
dependencies {
- api("androidx.core:core:1.1.0-rc01")
+ api("androidx.core:core:1.1.0")
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/androidTest/java/androidx/browser/trusted/TrustedWebActivityBuilderTest.java b/browser/src/androidTest/java/androidx/browser/trusted/TrustedWebActivityBuilderTest.java
deleted file mode 100644
index d1531a1..0000000
--- a/browser/src/androidTest/java/androidx/browser/trusted/TrustedWebActivityBuilderTest.java
+++ /dev/null
@@ -1,119 +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.browser.trusted;
-
-import static androidx.browser.customtabs.TrustedWebUtils.EXTRA_LAUNCH_AS_TRUSTED_WEB_ACTIVITY;
-import static androidx.browser.customtabs.testutil.TestUtil.getBrowserActivityWhenLaunched;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-
-import androidx.browser.customtabs.CustomTabsIntent;
-import androidx.browser.customtabs.CustomTabsSession;
-import androidx.browser.customtabs.CustomTabsSessionToken;
-import androidx.browser.customtabs.EnableComponentsTestRule;
-import androidx.browser.customtabs.TestActivity;
-import androidx.browser.customtabs.TestCustomTabsServiceSupportsTwas;
-import androidx.browser.customtabs.testutil.CustomTabConnectionRule;
-import androidx.browser.trusted.splashscreens.SplashScreenParamKey;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.MediumTest;
-import androidx.test.rule.ActivityTestRule;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Tests for {@link TrustedWebActivityBuilder}.
- */
-@RunWith(AndroidJUnit4.class)
-@MediumTest
-public class TrustedWebActivityBuilderTest {
-
- @Rule
- public final EnableComponentsTestRule mEnableComponents = new EnableComponentsTestRule(
- TestActivity.class,
- TestBrowser.class,
- TestCustomTabsServiceSupportsTwas.class
- );
-
- @Rule
- public final ActivityTestRule<TestActivity> mActivityTestRule =
- new ActivityTestRule<>(TestActivity.class, false, true);
-
- @Rule
- public final CustomTabConnectionRule mConnectionRule = new CustomTabConnectionRule();
-
- private TestActivity mActivity;
- private CustomTabsSession mSession;
-
- @Before
- public void setUp() {
- mActivity = mActivityTestRule.getActivity();
- mSession = mConnectionRule.establishSessionBlocking(mActivity);
- }
-
- @Test
- public void intentIsConstructedCorrectly() {
- Uri url = Uri.parse("https://test.com/page");
- int statusBarColor = 0xaabbcc;
- List<String> additionalTrustedOrigins =
- Arrays.asList("https://m.test.com", "https://test.org");
-
- Bundle splashScreenParams = new Bundle();
- int splashBgColor = 0x112233;
- splashScreenParams.putInt(
- SplashScreenParamKey.BACKGROUND_COLOR, splashBgColor);
-
- final TrustedWebActivityBuilder builder =
- new TrustedWebActivityBuilder(mActivity, url)
- .setStatusBarColor(statusBarColor)
- .setAdditionalTrustedOrigins(additionalTrustedOrigins)
- .setSplashScreenParams(splashScreenParams);
- Intent intent =
- getBrowserActivityWhenLaunched(new Runnable() {
- @Override
- public void run() {
- builder.launchActivity(mSession);
- }
- }).getIntent();
-
- assertTrue(intent.getBooleanExtra(EXTRA_LAUNCH_AS_TRUSTED_WEB_ACTIVITY, false));
- assertTrue(CustomTabsSessionToken.getSessionTokenFromIntent(intent)
- .isAssociatedWith(mSession));
- assertEquals(url, intent.getData());
- assertEquals(statusBarColor, intent.getIntExtra(CustomTabsIntent.EXTRA_TOOLBAR_COLOR, 0));
- assertEquals(additionalTrustedOrigins, intent.getStringArrayListExtra(
- TrustedWebActivityBuilder.EXTRA_ADDITIONAL_TRUSTED_ORIGINS));
-
- Bundle splashScreenParamsReceived =
- intent.getBundleExtra(TrustedWebActivityBuilder.EXTRA_SPLASH_SCREEN_PARAMS);
-
- // No need to test every splash screen param: they are sent in as-is in provided Bundle.
- assertEquals(splashBgColor, splashScreenParamsReceived.getInt(
- SplashScreenParamKey.BACKGROUND_COLOR));
- }
-}
diff --git a/browser/src/main/java/androidx/browser/customtabs/CustomTabColorSchemeParams.java b/browser/src/main/java/androidx/browser/customtabs/CustomTabColorSchemeParams.java
index d4f0592..cf4fbfa 100644
--- a/browser/src/main/java/androidx/browser/customtabs/CustomTabColorSchemeParams.java
+++ b/browser/src/main/java/androidx/browser/customtabs/CustomTabColorSchemeParams.java
@@ -119,7 +119,7 @@
*/
@NonNull
public Builder setToolbarColor(@ColorInt int color) {
- mToolbarColor = color;
+ mToolbarColor = color | 0xff000000;
return this;
}
@@ -137,7 +137,7 @@
*/
@NonNull
public Builder setNavigationBarColor(@ColorInt int color) {
- mNavigationBarColor = color;
+ mNavigationBarColor = color | 0xff000000;
return this;
}
diff --git a/browser/src/main/java/androidx/browser/customtabs/CustomTabsIntent.java b/browser/src/main/java/androidx/browser/customtabs/CustomTabsIntent.java
index 0cb25e5..b80beb4 100644
--- a/browser/src/main/java/androidx/browser/customtabs/CustomTabsIntent.java
+++ b/browser/src/main/java/androidx/browser/customtabs/CustomTabsIntent.java
@@ -355,21 +355,7 @@
* Creates a {@link CustomTabsIntent.Builder} object associated with no
* {@link CustomTabsSession}.
*/
- public Builder() {
- initialize(null, null);
- }
-
- /**
- * Creates a {@link CustomTabsIntent.Builder} object associated with a given
- * {@link CustomTabsSession.PendingSession}.
- *
- * {@see Builder(CustomTabsSession)}
- * @hide
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY)
- public Builder(@Nullable CustomTabsSession.PendingSession session) {
- initialize(null, session.getId());
- }
+ public Builder() {}
/**
* Creates a {@link CustomTabsIntent.Builder} object associated with a given
@@ -382,16 +368,40 @@
*/
public Builder(@Nullable CustomTabsSession session) {
if (session != null) {
- mIntent.setPackage(session.getComponentName().getPackageName());
- initialize(session.getBinder(), session.getId());
- } else {
- initialize(null, null);
+ setSession(session);
}
}
- private void initialize(@Nullable IBinder session, @Nullable PendingIntent sessionId) {
+ /**
+ * Associates the {@link Intent} with the given {@link CustomTabsSession}.
+ *
+ * Guarantees that the {@link Intent} will be sent to the same component as the one the
+ * session is associated with.
+ */
+ @NonNull
+ public Builder setSession(@NonNull CustomTabsSession session) {
+ mIntent.setPackage(session.getComponentName().getPackageName());
+ setSessionParameters(session.getBinder(), session.getId());
+ return this;
+ }
+
+ /**
+ * Associates the {@link Intent} with the given {@link CustomTabsSession.PendingSession}.
+ * Overrides the effect of {@link #setSession}.
+ *
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @NonNull
+ public Builder setPendingSession(@NonNull CustomTabsSession.PendingSession session) {
+ setSessionParameters(null, session.getId());
+ return this;
+ }
+
+ private void setSessionParameters(@Nullable IBinder binder,
+ @Nullable PendingIntent sessionId) {
Bundle bundle = new Bundle();
- BundleCompat.putBinder(bundle, EXTRA_SESSION, session);
+ BundleCompat.putBinder(bundle, EXTRA_SESSION, binder);
if (sessionId != null) {
bundle.putParcelable(EXTRA_SESSION_ID, sessionId);
}
@@ -718,6 +728,10 @@
*/
@NonNull
public CustomTabsIntent build() {
+ if (!mIntent.hasExtra(EXTRA_SESSION)) {
+ // The intent must have EXTRA_SESSION, even if it is null.
+ setSessionParameters(null, null);
+ }
if (mMenuItems != null) {
mIntent.putParcelableArrayListExtra(CustomTabsIntent.EXTRA_MENU_ITEMS, mMenuItems);
}
diff --git a/browser/src/main/java/androidx/browser/customtabs/TrustedWebUtils.java b/browser/src/main/java/androidx/browser/customtabs/TrustedWebUtils.java
index d287b43..7f50cbc 100644
--- a/browser/src/main/java/androidx/browser/customtabs/TrustedWebUtils.java
+++ b/browser/src/main/java/androidx/browser/customtabs/TrustedWebUtils.java
@@ -29,7 +29,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
import androidx.annotation.WorkerThread;
-import androidx.browser.trusted.TrustedWebActivityBuilder;
+import androidx.browser.trusted.TrustedWebActivityIntentBuilder;
import androidx.core.app.BundleCompat;
import androidx.core.content.FileProvider;
@@ -134,9 +134,9 @@
* Transfers the splash image to a Custom Tabs provider. The reading and decoding of the image
* happens synchronously, so it's recommended to call this method on a worker thread.
*
- * This method should be called prior to {@link TrustedWebActivityBuilder#launchActivity}.
+ * This method should be called prior to launching the Activity.
* Pass additional parameters, such as background color, using
- * {@link TrustedWebActivityBuilder#setSplashScreenParams(Bundle)}.
+ * {@link TrustedWebActivityIntentBuilder#setSplashScreenParams(Bundle)}.
*
* @param context {@link Context} to use.
* @param file {@link File} with the image.
@@ -169,8 +169,7 @@
* associated with browser toolbar controls will be ignored.
* @param uri The web page to launch as Trusted Web Activity.
*
- * @deprecated Use {@link TrustedWebActivityBuilder} and
- * {@link TrustedWebActivityBuilder#launchActivity} instead.
+ * @deprecated Use {@link TrustedWebActivityIntentBuilder} instead.
*/
@Deprecated
public static void launchAsTrustedWebActivity(@NonNull Context context,
diff --git a/browser/src/main/java/androidx/browser/trusted/TrustedWebActivityBuilder.java b/browser/src/main/java/androidx/browser/trusted/TrustedWebActivityBuilder.java
deleted file mode 100644
index 9c456cd..0000000
--- a/browser/src/main/java/androidx/browser/trusted/TrustedWebActivityBuilder.java
+++ /dev/null
@@ -1,184 +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.browser.trusted;
-
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-
-import androidx.annotation.ColorInt;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
-import androidx.browser.customtabs.CustomTabsIntent;
-import androidx.browser.customtabs.CustomTabsSession;
-import androidx.browser.customtabs.TrustedWebUtils;
-import androidx.browser.trusted.splashscreens.SplashScreenParamKey;
-import androidx.core.content.ContextCompat;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Constructs and launches an intent to start a Trusted Web Activity (see {@link TrustedWebUtils}
- * for more details).
- */
-public class TrustedWebActivityBuilder {
- /**
- * Extra for the Trusted Web Activity launch Intent to specify a {@link Bundle} of parameters
- * for the browser to use in constructing a splash screen.
- *
- * It is recommended to use {@link TrustedWebActivityBuilder} instead of manually piecing the
- * Intent together.
- */
- @SuppressLint("ActionValue")
- public static final String EXTRA_SPLASH_SCREEN_PARAMS =
- "androidx.browser.trusted.EXTRA_SPLASH_SCREEN_PARAMS";
-
- /**
- * Extra for the Trusted Web Activity launch Intent to specify a list of origins for the
- * browser to treat as trusted, in addition to the origin of the launching URL.
- *
- * It is recommended to use {@link TrustedWebActivityBuilder} instead of manually piecing the
- * Intent together.
- */
- @SuppressLint("ActionValue")
- public static final String EXTRA_ADDITIONAL_TRUSTED_ORIGINS =
- "android.support.customtabs.extra.ADDITIONAL_TRUSTED_ORIGINS";
-
- private final Context mContext;
- private final Uri mUri;
-
- @Nullable private Integer mStatusBarColor;
- @Nullable private List<String> mAdditionalTrustedOrigins;
- @Nullable private Bundle mSplashScreenParams;
-
- /**
- * Creates a Builder given the required parameters.
- * @param context {@link Context} to use.
- * @param uri The web page to launch as Trusted Web Activity.
- */
- public TrustedWebActivityBuilder(@NonNull Context context, @NonNull Uri uri) {
- mContext = context;
- mUri = uri;
- }
-
- /**
- * Sets the status bar color to be seen while the Trusted Web Activity is running.
- *
- * @hide
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY)
- @NonNull
- public TrustedWebActivityBuilder setStatusBarColor(@ColorInt int color) {
- mStatusBarColor = color;
- return this;
- }
-
- /**
- * Sets a list of additional trusted origins that the user may navigate or be redirected to
- * from the starting uri.
- *
- * For example, if the user starts at https://www.example.com/page1 and is redirected to
- * https://m.example.com/page2, and both origins are associated with the calling application
- * via the Digital Asset Links, then pass "https://www.example.com/page1" as uri and
- * Arrays.asList("https://m.example.com") as additionalTrustedOrigins.
- *
- * Alternatively, use {@link CustomTabsSession#validateRelationship} to validate additional
- * origins asynchronously, but that would delay launching the Trusted Web Activity.
- */
- @NonNull
- public TrustedWebActivityBuilder setAdditionalTrustedOrigins(
- @NonNull List<String> origins) {
- mAdditionalTrustedOrigins = origins;
- return this;
- }
-
- /**
- * Sets the parameters of a splash screen shown while the web page is loading, such as
- * background color. See {@link SplashScreenParamKey} for a list of supported parameters.
- *
- * To provide the image for the splash screen, use {@link TrustedWebUtils#transferSplashImage},
- * prior to calling {@link #launchActivity} on the builder.
- *
- * It is recommended to also show the same splash screen in the app as soon as possible,
- * prior to establishing a CustomTabConnection. The Trusted Web Activity provider should
- * ensure seamless transition of the splash screen from the app onto the top of webpage
- * being loaded.
- *
- * The splash screen will be removed on the first paint of the page, or when the page load
- * fails.
- */
- @NonNull
- public TrustedWebActivityBuilder setSplashScreenParams(@NonNull Bundle splashScreenParams) {
- mSplashScreenParams = splashScreenParams;
- return this;
- }
-
- /**
- * Launches a Trusted Web Activity. Once it is launched, browser side implementations may
- * have their own fallback behavior (e.g. showing the page in a custom tab UI with toolbar).
- *
- * @param session The {@link CustomTabsSession} to use for launching a Trusted Web Activity.
- */
- public void launchActivity(@NonNull CustomTabsSession session) {
- if (session == null) {
- throw new NullPointerException("CustomTabsSession is required for launching a TWA");
- }
-
- CustomTabsIntent.Builder intentBuilder = new CustomTabsIntent.Builder(session);
- if (mStatusBarColor != null) {
- // Toolbar color applies also to the status bar.
- intentBuilder.setToolbarColor(mStatusBarColor);
- }
-
- Intent intent = intentBuilder.build().intent;
- intent.setData(mUri);
- intent.putExtra(TrustedWebUtils.EXTRA_LAUNCH_AS_TRUSTED_WEB_ACTIVITY, true);
- if (mAdditionalTrustedOrigins != null) {
- intent.putExtra(EXTRA_ADDITIONAL_TRUSTED_ORIGINS,
- new ArrayList<>(mAdditionalTrustedOrigins));
- }
-
- if (mSplashScreenParams != null) {
- intent.putExtra(EXTRA_SPLASH_SCREEN_PARAMS, mSplashScreenParams);
- }
- ContextCompat.startActivity(mContext, intent, null);
- }
-
- /**
- * Returns the {@link Uri} to be launched with this Builder.
- */
- @NonNull
- public Uri getUrl() {
- return mUri;
- }
-
- /**
- * Returns the color set via {@link #setStatusBarColor(int)} or {@code null} if not set.
- *
- * @hide
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY)
- @Nullable
- @ColorInt
- public Integer getStatusBarColor() {
- return mStatusBarColor;
- }
-}
diff --git a/browser/src/main/java/androidx/browser/trusted/TrustedWebActivityIntentBuilder.java b/browser/src/main/java/androidx/browser/trusted/TrustedWebActivityIntentBuilder.java
new file mode 100644
index 0000000..b87fea5
--- /dev/null
+++ b/browser/src/main/java/androidx/browser/trusted/TrustedWebActivityIntentBuilder.java
@@ -0,0 +1,210 @@
+/*
+ * 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.browser.trusted;
+
+import android.annotation.SuppressLint;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.browser.customtabs.CustomTabColorSchemeParams;
+import androidx.browser.customtabs.CustomTabsIntent;
+import androidx.browser.customtabs.CustomTabsSession;
+import androidx.browser.customtabs.TrustedWebUtils;
+import androidx.browser.trusted.splashscreens.SplashScreenParamKey;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Constructs an intent to start a Trusted Web Activity (see {@link TrustedWebUtils} for more
+ * details).
+ */
+public class TrustedWebActivityIntentBuilder {
+ /**
+ * Extra for the Trusted Web Activity launch Intent to specify a {@link Bundle} of parameters
+ * for the browser to use in constructing a splash screen.
+ *
+ * It is recommended to use {@link TrustedWebActivityIntentBuilder} instead of manually piecing
+ * the Intent together.
+ */
+ @SuppressLint("ActionValue")
+ public static final String EXTRA_SPLASH_SCREEN_PARAMS =
+ "androidx.browser.trusted.EXTRA_SPLASH_SCREEN_PARAMS";
+
+ /**
+ * Extra for the Trusted Web Activity launch Intent to specify a list of origins for the
+ * browser to treat as trusted, in addition to the origin of the launching URL.
+ *
+ * It is recommended to use {@link TrustedWebActivityIntentBuilder} instead of manually piecing
+ * the Intent together.
+ */
+ @SuppressLint("ActionValue")
+ public static final String EXTRA_ADDITIONAL_TRUSTED_ORIGINS =
+ "android.support.customtabs.extra.ADDITIONAL_TRUSTED_ORIGINS";
+
+ @NonNull private final Uri mUri;
+ @NonNull private final CustomTabsIntent.Builder mIntentBuilder = new CustomTabsIntent.Builder();
+
+ @Nullable private List<String> mAdditionalTrustedOrigins;
+ @Nullable private Bundle mSplashScreenParams;
+
+ /**
+ * Creates a Builder given the required parameters.
+ * @param uri The web page to launch as Trusted Web Activity.
+ */
+ public TrustedWebActivityIntentBuilder(@NonNull Uri uri) {
+ mUri = uri;
+ }
+
+ /**
+ * Sets the color applied to the toolbar and the status bar, see
+ * {@link CustomTabsIntent.Builder#setToolbarColor}.
+ *
+ * When a Trusted Web Activity is on the verified origin, the toolbar is hidden, so the color
+ * applies only to the status bar. When it's on an unverified origin, the toolbar is shown, and
+ * the color applies to both toolbar and status bar.
+ */
+ @NonNull
+ public TrustedWebActivityIntentBuilder setToolbarColor(@ColorInt int color) {
+ mIntentBuilder.setToolbarColor(color);
+ return this;
+ }
+
+ /**
+ * Sets the navigation bar color, see {@link CustomTabsIntent.Builder#setNavigationBarColor}.
+ */
+ @NonNull
+ public TrustedWebActivityIntentBuilder setNavigationBarColor(@ColorInt int color) {
+ mIntentBuilder.setNavigationBarColor(color);
+ return this;
+ }
+
+ /**
+ * Sets the color scheme, see {@link CustomTabsIntent.Builder#setColorScheme}.
+ * In Trusted Web Activities color scheme may effect such UI elements as info bars and context
+ * menus.
+ *
+ * @param colorScheme Must be one of {@link CustomTabsIntent#COLOR_SCHEME_SYSTEM},
+ * {@link CustomTabsIntent#COLOR_SCHEME_LIGHT}, and {@link CustomTabsIntent#COLOR_SCHEME_DARK}.
+ */
+ @NonNull
+ public TrustedWebActivityIntentBuilder setColorScheme(int colorScheme) {
+ mIntentBuilder.setColorScheme(colorScheme);
+ return this;
+ }
+
+ /**
+ * Sets {@link CustomTabColorSchemeParams} for the given color scheme.
+ * This allows, for example, to set two navigation bar colors - for light and dark scheme.
+ * Trusted Web Activity will automatically apply the correct color according to current system
+ * settings. For more details see {@link CustomTabsIntent.Builder#setColorSchemeParams}.
+ */
+ @NonNull
+ public TrustedWebActivityIntentBuilder setColorSchemeParams(int colorScheme,
+ @NonNull CustomTabColorSchemeParams params) {
+ mIntentBuilder.setColorSchemeParams(colorScheme, params);
+ return this;
+ }
+ /**
+ * Sets a list of additional trusted origins that the user may navigate or be redirected to
+ * from the starting uri.
+ *
+ * For example, if the user starts at https://www.example.com/page1 and is redirected to
+ * https://m.example.com/page2, and both origins are associated with the calling application
+ * via the Digital Asset Links, then pass "https://www.example.com/page1" as uri and
+ * Arrays.asList("https://m.example.com") as additionalTrustedOrigins.
+ *
+ * Alternatively, use {@link CustomTabsSession#validateRelationship} to validate additional
+ * origins asynchronously, but that would delay launching the Trusted Web Activity.
+ */
+ @NonNull
+ public TrustedWebActivityIntentBuilder setAdditionalTrustedOrigins(
+ @NonNull List<String> origins) {
+ mAdditionalTrustedOrigins = origins;
+ return this;
+ }
+
+ /**
+ * Sets the parameters of a splash screen shown while the web page is loading, such as
+ * background color. See {@link SplashScreenParamKey} for a list of supported parameters.
+ *
+ * To provide the image for the splash screen, use {@link TrustedWebUtils#transferSplashImage},
+ * prior to launching the intent.
+ *
+ * It is recommended to also show the same splash screen in the app as soon as possible,
+ * prior to establishing a CustomTabConnection. The Trusted Web Activity provider should
+ * ensure seamless transition of the splash screen from the app onto the top of webpage
+ * being loaded.
+ *
+ * The splash screen will be removed on the first paint of the page, or when the page load
+ * fails.
+ */
+ @NonNull
+ public TrustedWebActivityIntentBuilder setSplashScreenParams(
+ @NonNull Bundle splashScreenParams) {
+ mSplashScreenParams = splashScreenParams;
+ return this;
+ }
+
+ /**
+ * Builds the Intent.
+ *
+ * @param session The {@link CustomTabsSession} to use for launching a Trusted Web Activity.
+ */
+ @NonNull
+ public Intent build(@NonNull CustomTabsSession session) {
+ if (session == null) {
+ throw new NullPointerException("CustomTabsSession is required for launching a TWA");
+ }
+
+ mIntentBuilder.setSession(session);
+ Intent intent = mIntentBuilder.build().intent;
+ intent.setData(mUri);
+ intent.putExtra(TrustedWebUtils.EXTRA_LAUNCH_AS_TRUSTED_WEB_ACTIVITY, true);
+ if (mAdditionalTrustedOrigins != null) {
+ intent.putExtra(EXTRA_ADDITIONAL_TRUSTED_ORIGINS,
+ new ArrayList<>(mAdditionalTrustedOrigins));
+ }
+
+ if (mSplashScreenParams != null) {
+ intent.putExtra(EXTRA_SPLASH_SCREEN_PARAMS, mSplashScreenParams);
+ }
+ return intent;
+ }
+
+ /**
+ * Builds a {@link CustomTabsIntent} based on provided parameters.
+ * Can be useful for falling back to Custom Tabs when Trusted Web Activity providers are
+ * unavailable.
+ */
+ @NonNull
+ public CustomTabsIntent buildCustomTabsIntent() {
+ return mIntentBuilder.build();
+ }
+
+ /**
+ * Returns the {@link Uri} to be launched with this Builder.
+ */
+ @NonNull
+ public Uri getUrl() {
+ return mUri;
+ }
+}
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/browser/src/main/java/androidx/browser/trusted/splashscreens/SplashScreenParamKey.java b/browser/src/main/java/androidx/browser/trusted/splashscreens/SplashScreenParamKey.java
index 543d966..b91f786 100644
--- a/browser/src/main/java/androidx/browser/trusted/splashscreens/SplashScreenParamKey.java
+++ b/browser/src/main/java/androidx/browser/trusted/splashscreens/SplashScreenParamKey.java
@@ -18,13 +18,13 @@
import android.os.Bundle;
-import androidx.browser.trusted.TrustedWebActivityBuilder;
+import androidx.browser.trusted.TrustedWebActivityIntentBuilder;
/**
* The keys of the entries in the {@link Bundle} passed to
- * {@link TrustedWebActivityBuilder#setSplashScreenParams}. This Bundle can also be assembled
+ * {@link TrustedWebActivityIntentBuilder#setSplashScreenParams}. This Bundle can also be assembled
* manually and added to the launch Intent as an extra with the key
- * {@link TrustedWebActivityBuilder#EXTRA_SPLASH_SCREEN_PARAMS}.
+ * {@link TrustedWebActivityIntentBuilder#EXTRA_SPLASH_SCREEN_PARAMS}.
*/
public final class SplashScreenParamKey {
/**
diff --git a/browser/src/androidTest/java/androidx/browser/customtabs/CustomTabColorSchemeParamsTest.java b/browser/src/test/java/androidx/browser/customtabs/CustomTabColorSchemeParamsTest.java
similarity index 90%
rename from browser/src/androidTest/java/androidx/browser/customtabs/CustomTabColorSchemeParamsTest.java
rename to browser/src/test/java/androidx/browser/customtabs/CustomTabColorSchemeParamsTest.java
index 9d9bd11..d7cd6fa 100644
--- a/browser/src/androidTest/java/androidx/browser/customtabs/CustomTabColorSchemeParamsTest.java
+++ b/browser/src/test/java/androidx/browser/customtabs/CustomTabColorSchemeParamsTest.java
@@ -20,14 +20,17 @@
import static androidx.browser.customtabs.CustomTabsIntent.COLOR_SCHEME_LIGHT;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
import android.content.Intent;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
/**
@@ -35,7 +38,8 @@
* In particular, for {@link CustomTabsIntent.Builder#setColorSchemeParams} and
* {@link CustomTabsIntent#getColorSchemeParams}
*/
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
@SmallTest
public class CustomTabColorSchemeParamsTest {
@Test
@@ -211,17 +215,26 @@
.build()
.intent;
- assertEquals(defaultToolbarColor,
+ assertColorEquals(defaultToolbarColor,
intent.getIntExtra(CustomTabsIntent.EXTRA_TOOLBAR_COLOR, 0));
- assertEquals(defaultSecondaryToolbarColor,
+ assertColorEquals(defaultSecondaryToolbarColor,
intent.getIntExtra(CustomTabsIntent.EXTRA_SECONDARY_TOOLBAR_COLOR, 0));
}
private void assertSchemeParamsEqual(CustomTabColorSchemeParams params1,
CustomTabColorSchemeParams params2) {
- assertEquals(params1.toolbarColor, params2.toolbarColor);
- assertEquals(params1.secondaryToolbarColor, params2.secondaryToolbarColor);
- assertEquals(params1.navigationBarColor, params2.navigationBarColor);
+ assertColorEquals(params1.toolbarColor, params2.toolbarColor);
+ assertColorEquals(params1.secondaryToolbarColor, params2.secondaryToolbarColor);
+ assertColorEquals(params1.navigationBarColor, params2.navigationBarColor);
+ }
+
+ // Compares colors ignoring alpha
+ private void assertColorEquals(@Nullable Integer color1, @Nullable Integer color2) {
+ if (color1 == null) {
+ assertNull(color2);
+ } else {
+ assertEquals(color1 & 0xffffff, color2 & 0xffffff);
+ }
}
}
diff --git a/browser/src/androidTest/java/androidx/browser/customtabs/CustomTabsIntentTest.java b/browser/src/test/java/androidx/browser/customtabs/CustomTabsIntentTest.java
similarity index 64%
rename from browser/src/androidTest/java/androidx/browser/customtabs/CustomTabsIntentTest.java
rename to browser/src/test/java/androidx/browser/customtabs/CustomTabsIntentTest.java
index 3d65e75..e8a9654 100644
--- a/browser/src/androidTest/java/androidx/browser/customtabs/CustomTabsIntentTest.java
+++ b/browser/src/test/java/androidx/browser/customtabs/CustomTabsIntentTest.java
@@ -29,16 +29,22 @@
import androidx.annotation.ColorRes;
import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
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;
+import org.robolectric.shadows.ShadowPendingIntent;
/**
* Tests for CustomTabsIntent.
*/
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
+// minSdk For Bundle#getBinder
+@Config(minSdk = Build.VERSION_CODES.JELLY_BEAN_MR2, shadows = ShadowPendingIntent.class)
@SmallTest
public class CustomTabsIntentTest {
@@ -50,10 +56,6 @@
assertNull(customTabsIntent.startAnimationBundle);
assertEquals(Intent.ACTION_VIEW, intent.getAction());
- assertTrue(intent.hasExtra(CustomTabsIntent.EXTRA_SESSION));
- if (Build.VERSION.SDK_INT >= 18) {
- assertNull(intent.getExtras().getBinder(CustomTabsIntent.EXTRA_SESSION));
- }
assertNull(intent.getComponent());
}
@@ -113,4 +115,49 @@
assertEquals(value, intent.getIntExtra(CustomTabsIntent.EXTRA_COLOR_SCHEME, -1));
}
}
+
+ @Test
+ public void hasNullSessionExtra_WhenBuiltWithDefaultConstructor() {
+ Intent intent = new CustomTabsIntent.Builder().build().intent;
+ assertNullSessionInExtras(intent);
+ }
+
+ @Test
+ public void hasNullSessionExtra_WhenBuiltWithNullSession() {
+ CustomTabsSession session = null;
+ Intent intent = new CustomTabsIntent.Builder(session).build().intent;
+ assertNullSessionInExtras(intent);
+ }
+
+ @Test
+ public void putsSessionBinderAndId_IfSuppliedInConstructor() {
+ CustomTabsSession session = TestUtil.makeMockSession();
+ Intent intent = new CustomTabsIntent.Builder(session).build().intent;
+ assertEquals(session.getBinder(),
+ intent.getExtras().getBinder(CustomTabsIntent.EXTRA_SESSION));
+ assertEquals(session.getId(), intent.getParcelableExtra(CustomTabsIntent.EXTRA_SESSION_ID));
+ }
+
+ @Test
+ public void putsSessionBinderAndId_IfSuppliedInSetter() {
+ CustomTabsSession session = TestUtil.makeMockSession();
+ Intent intent = new CustomTabsIntent.Builder().setSession(session).build().intent;
+ assertEquals(session.getBinder(),
+ intent.getExtras().getBinder(CustomTabsIntent.EXTRA_SESSION));
+ assertEquals(session.getId(), intent.getParcelableExtra(CustomTabsIntent.EXTRA_SESSION_ID));
+ }
+
+ @Test
+ public void putsPendingSessionId() {
+ CustomTabsSession.PendingSession pendingSession = TestUtil.makeMockPendingSession();
+ Intent intent = new CustomTabsIntent.Builder().setPendingSession(pendingSession).build()
+ .intent;
+ assertEquals(pendingSession.getId(),
+ intent.getParcelableExtra(CustomTabsIntent.EXTRA_SESSION_ID));
+ }
+
+ private void assertNullSessionInExtras(Intent intent) {
+ assertTrue(intent.hasExtra(CustomTabsIntent.EXTRA_SESSION));
+ assertNull(intent.getExtras().getBinder(CustomTabsIntent.EXTRA_SESSION));
+ }
}
diff --git a/browser/src/test/java/androidx/browser/customtabs/TestUtil.java b/browser/src/test/java/androidx/browser/customtabs/TestUtil.java
new file mode 100644
index 0000000..d5316fd
--- /dev/null
+++ b/browser/src/test/java/androidx/browser/customtabs/TestUtil.java
@@ -0,0 +1,60 @@
+/*
+ * 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.browser.customtabs;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.support.customtabs.ICustomTabsCallback;
+import android.support.customtabs.ICustomTabsService;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Utilities for unit testing Custom Tabs.
+ */
+public class TestUtil {
+
+ @NonNull
+ public static CustomTabsSession makeMockSession() {
+ return new CustomTabsSession(mock(ICustomTabsService.class),
+ mock(ICustomTabsCallback.class), new ComponentName("", ""),
+ makeMockPendingIntent());
+ }
+
+ @NonNull
+ public static CustomTabsSession.PendingSession makeMockPendingSession() {
+ return new CustomTabsSession.PendingSession(
+ mock(CustomTabsCallback.class), makeMockPendingIntent());
+ }
+
+ @NonNull
+ private static PendingIntent makeMockPendingIntent() {
+ return PendingIntent.getBroadcast(mock(Context.class), 0, new Intent(), 0);
+ }
+
+ public static void assertIntentHasSession(@NonNull Intent intent,
+ @NonNull CustomTabsSession session) {
+ assertEquals(session.getBinder(), intent.getExtras().getBinder(
+ CustomTabsIntent.EXTRA_SESSION));
+ assertEquals(session.getId(), intent.getParcelableExtra(CustomTabsIntent.EXTRA_SESSION_ID));
+ }
+}
diff --git a/browser/src/test/java/androidx/browser/trusted/TrustedWebActivityIntentBuilderTest.java b/browser/src/test/java/androidx/browser/trusted/TrustedWebActivityIntentBuilderTest.java
new file mode 100644
index 0000000..c86da1d9
--- /dev/null
+++ b/browser/src/test/java/androidx/browser/trusted/TrustedWebActivityIntentBuilderTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.browser.trusted;
+
+import static androidx.browser.customtabs.CustomTabsIntent.COLOR_SCHEME_DARK;
+import static androidx.browser.customtabs.TrustedWebUtils.EXTRA_LAUNCH_AS_TRUSTED_WEB_ACTIVITY;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+
+import androidx.browser.customtabs.CustomTabColorSchemeParams;
+import androidx.browser.customtabs.CustomTabsIntent;
+import androidx.browser.customtabs.CustomTabsSession;
+import androidx.browser.customtabs.TestUtil;
+import androidx.browser.trusted.splashscreens.SplashScreenParamKey;
+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;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Tests for {@link TrustedWebActivityIntentBuilder}.
+ */
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
+// minSdk For Bundle#getBinder
+@Config(minSdk = Build.VERSION_CODES.JELLY_BEAN_MR2)
+@SmallTest
+public class TrustedWebActivityIntentBuilderTest {
+
+ @Test
+ public void intentIsConstructedCorrectly() {
+ Uri url = Uri.parse("https://test.com/page");
+ int toolbarColor = 0xffaabbcc;
+ int navigationBarColor = 0xffccaabb;
+ List<String> additionalTrustedOrigins =
+ Arrays.asList("https://m.test.com", "https://test.org");
+
+ Bundle splashScreenParams = new Bundle();
+ int splashBgColor = 0x112233;
+ splashScreenParams.putInt(
+ SplashScreenParamKey.BACKGROUND_COLOR, splashBgColor);
+
+ CustomTabColorSchemeParams colorSchemeParams = new CustomTabColorSchemeParams.Builder()
+ .setToolbarColor(0xff112233).build();
+
+ CustomTabsSession session = TestUtil.makeMockSession();
+
+ Intent intent = new TrustedWebActivityIntentBuilder(url)
+ .setToolbarColor(toolbarColor)
+ .setNavigationBarColor(navigationBarColor)
+ .setColorScheme(COLOR_SCHEME_DARK)
+ .setColorSchemeParams(COLOR_SCHEME_DARK, colorSchemeParams)
+ .setAdditionalTrustedOrigins(additionalTrustedOrigins)
+ .setSplashScreenParams(splashScreenParams)
+ .build(session);
+
+ assertTrue(intent.getBooleanExtra(EXTRA_LAUNCH_AS_TRUSTED_WEB_ACTIVITY, false));
+ TestUtil.assertIntentHasSession(intent, session);
+ assertEquals(url, intent.getData());
+ assertEquals(toolbarColor, intent.getIntExtra(CustomTabsIntent.EXTRA_TOOLBAR_COLOR, 0));
+ assertEquals(navigationBarColor, intent.getIntExtra(
+ CustomTabsIntent.EXTRA_NAVIGATION_BAR_COLOR, 0));
+ assertEquals(additionalTrustedOrigins, intent.getStringArrayListExtra(
+ TrustedWebActivityIntentBuilder.EXTRA_ADDITIONAL_TRUSTED_ORIGINS));
+ assertEquals(COLOR_SCHEME_DARK, intent.getIntExtra(CustomTabsIntent.EXTRA_COLOR_SCHEME,
+ -1));
+ assertEquals(colorSchemeParams.toolbarColor,
+ CustomTabsIntent.getColorSchemeParams(intent, COLOR_SCHEME_DARK).toolbarColor);
+
+ Bundle splashScreenParamsReceived =
+ intent.getBundleExtra(TrustedWebActivityIntentBuilder.EXTRA_SPLASH_SCREEN_PARAMS);
+
+ // No need to test every splash screen param: they are sent in as-is in provided Bundle.
+ assertEquals(splashBgColor, splashScreenParamsReceived.getInt(
+ SplashScreenParamKey.BACKGROUND_COLOR));
+ }
+}
diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle
index 59996ce..c51fd30 100644
--- a/buildSrc/build.gradle
+++ b/buildSrc/build.gradle
@@ -58,11 +58,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
@@ -73,3 +68,18 @@
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/src/main/kotlin/androidx/build/AndroidXPlugin.kt b/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt
index 665df4f..35fedcb 100644
--- a/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt
@@ -16,8 +16,6 @@
package androidx.build
-import androidx.benchmark.gradle.LockClocksTask
-import androidx.benchmark.gradle.UnlockClocksTask
import androidx.build.SupportConfig.BENCHMARK_INSTRUMENTATION_RUNNER
import androidx.build.SupportConfig.BUILD_TOOLS_VERSION
import androidx.build.SupportConfig.COMPILE_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
@@ -370,6 +361,14 @@
defaultConfig.targetSdkVersion(TARGET_SDK_VERSION)
defaultConfig.testInstrumentationRunner =
if (project.isBenchmark()) BENCHMARK_INSTRUMENTATION_RUNNER else INSTRUMENTATION_RUNNER
+
+ // 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
+ // instrumentation runner argument is passed raw to the `am instrument` command.
+ // NOTE - instrumentation args aren't respected by CI - window animations are
+ // disabled there separately
+ defaultConfig.testInstrumentationRunnerArgument("thisisignored",
+ "thisisignored --no-window-animation")
testOptions.unitTests.isReturnDefaultValues = true
defaultConfig.minSdkVersion(DEFAULT_MIN_SDK_VERSION)
@@ -632,8 +631,7 @@
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"
+ return name.endsWith("-benchmark")
}
fun Project.hideJavadocTask() {
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 bfd5d87..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-alpha05")
+ 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")
@@ -109,7 +110,7 @@
val VECTORDRAWABLE_ANIMATED = Version("1.2.0-alpha01")
val VERSIONED_PARCELABLE = Version("1.2.0-alpha01")
val VIEWPAGER = Version("1.1.0-alpha01")
- val VIEWPAGER2 = Version("1.0.0-beta02")
+ val VIEWPAGER2 = Version("1.0.0-beta03")
val WEAR = Version("1.1.0-alpha01")
val WEBKIT = Version("1.1.0-alpha02")
val WORK = Version("2.3.0-alpha01")
diff --git a/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt b/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
index 4046931..6c5bd2c 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")
@@ -118,15 +119,15 @@
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-beta01")
+ prebuilts(LibraryGroups.VIEWPAGER2, "1.0.0-beta02")
prebuilts(LibraryGroups.WEAR, "1.0.0")
.addStubs("wear/wear_stubs/com.google.android.wearable-stubs.jar")
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..f9b32b8d 100644
--- a/buildSrc/src/main/kotlin/androidx/build/SupportConfig.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/SupportConfig.kt
@@ -23,7 +23,7 @@
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 BENCHMARK_INSTRUMENTATION_RUNNER = "androidx.benchmark.junit4.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..7c83e43 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
@@ -19,6 +19,7 @@
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"
@@ -68,6 +69,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"
diff --git a/buildSrc/src/main/kotlin/androidx/build/metalava/CheckApiCompatibilityTask.kt b/buildSrc/src/main/kotlin/androidx/build/metalava/CheckApiCompatibilityTask.kt
index 3939416..7c8a6528 100644
--- a/buildSrc/src/main/kotlin/androidx/build/metalava/CheckApiCompatibilityTask.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/metalava/CheckApiCompatibilityTask.kt
@@ -25,11 +25,14 @@
import org.gradle.api.tasks.TaskAction
import java.io.File
-// Validate an API signature text file against a set of source files.
+// Validate that the API described in one signature txt file is compatible with the API in another
abstract class CheckApiCompatibilityTask : MetalavaTask() {
// Text file from which the API signatures will be obtained.
@get:Input
abstract val referenceApi: Property<ApiLocation>
+
+ @get:Input
+ abstract val api: Property<ApiLocation>
// Text file listing violations that should be ignored
@get:Input
abstract val baselines: Property<ApiViolationBaselines>
@@ -56,25 +59,25 @@
fun exec() {
check(bootClasspath.isNotEmpty()) { "Android boot classpath not set." }
- checkApiFile(referenceApi.get().publicApiFile, baselines.get().publicApiFile, false)
+ checkApiFile(api.get().publicApiFile, referenceApi.get().publicApiFile,
+ baselines.get().publicApiFile, false)
if (checkRestrictedAPIs) {
- checkApiFile(referenceApi.get().restrictedApiFile,
+ checkApiFile(api.get().restrictedApiFile, referenceApi.get().restrictedApiFile,
baselines.get().restrictedApiFile,
true)
}
}
- // Confirms that the public API of this library (or the restricted API, if <checkRestrictedAPIs> is set
- // is compatible with <apiFile> except for any baselines listed in <baselineFile>
- fun checkApiFile(apiFile: File, baselineFile: File, checkRestrictedAPIs: Boolean) {
+ // Confirms that <api> is compatible with <oldApi> except for any baselines listed in <baselineFile>
+ fun checkApiFile(api: File, oldApi: File, baselineFile: File, checkRestrictedAPIs: Boolean) {
var args = listOf("--classpath",
(bootClasspath + dependencyClasspath.files).joinToString(File.pathSeparator),
- "--source-path",
- sourcePaths.filter { it.exists() }.joinToString(File.pathSeparator),
+ "--source-files",
+ api.toString(),
"--check-compatibility:api:released",
- apiFile.toString(),
+ oldApi.toString(),
"--warnings-as-errors",
"--format=v3"
diff --git a/buildSrc/src/main/kotlin/androidx/build/metalava/MetalavaRunner.kt b/buildSrc/src/main/kotlin/androidx/build/metalava/MetalavaRunner.kt
index f5c5aed..ab64ddd 100644
--- a/buildSrc/src/main/kotlin/androidx/build/metalava/MetalavaRunner.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/metalava/MetalavaRunner.kt
@@ -38,7 +38,9 @@
it.classpath = checkNotNull(configuration) { "Configuration not set." }
it.main = "com.android.tools.metalava.Driver"
it.args = listOf(
- "--no-banner"
+ "--no-banner",
+ "--hide",
+ "HiddenSuperclass" // We allow having a hidden parent class
) + args
}
}
@@ -50,10 +52,10 @@
// The list of checks that are hidden as they are not useful in androidx
"Enum", // Enums are allowed to be use in androidx
"CallbackInterface", // With target Java 8, we have default methods
- "HiddenSuperclass", // We allow having a hidden parent class
"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
@@ -181,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/buildSrc/src/main/kotlin/androidx/build/metalava/MetalavaTasks.kt b/buildSrc/src/main/kotlin/androidx/build/metalava/MetalavaTasks.kt
index f435065..51d97a4 100644
--- a/buildSrc/src/main/kotlin/androidx/build/metalava/MetalavaTasks.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/metalava/MetalavaTasks.kt
@@ -141,7 +141,10 @@
task.baselines.set(baselines)
task.dependsOn(metalavaConfiguration)
task.checkRestrictedAPIs = extension.trackRestrictedAPIs
- applyInputs(javaCompileInputs, task)
+ task.api.set(builtApiLocation)
+ task.dependencyClasspath = javaCompileInputs.dependencyClasspath
+ task.bootClasspath = javaCompileInputs.bootClasspath
+ task.dependsOn(generateApi)
}
project.tasks.register("ignoreApiChanges", IgnoreApiChangesTask::class.java) { task ->
@@ -149,7 +152,10 @@
task.referenceApi.set(checkApiRelease!!.flatMap { it.referenceApi })
task.baselines.set(checkApiRelease!!.flatMap { it.baselines })
task.processRestrictedApis = extension.trackRestrictedAPIs
- applyInputs(javaCompileInputs, task)
+ task.api.set(builtApiLocation)
+ task.dependencyClasspath = javaCompileInputs.dependencyClasspath
+ task.bootClasspath = javaCompileInputs.bootClasspath
+ task.dependsOn(generateApi)
}
}
@@ -183,6 +189,14 @@
task.outputApiLocations.set(checkApi.flatMap { it.checkedInApis })
task.updateRestrictedAPIs = extension.trackRestrictedAPIs
task.dependsOn(generateApi)
+ if (checkApiRelease != null) {
+ // If a developer (accidentally) makes a non-backwards compatible change to an
+ // api, the developer will want to be informed of it as soon as possible.
+ // So, whenever a developer updates an api, if backwards compatibility checks are
+ // enabled in the library, then we want to check that the changes are backwards
+ // compatible
+ task.dependsOn(checkApiRelease)
+ }
}
project.tasks.register("regenerateOldApis", RegenerateOldApisTask::class.java) { task ->
diff --git a/buildSrc/src/main/kotlin/androidx/build/metalava/RegenerateOldApisTask.kt b/buildSrc/src/main/kotlin/androidx/build/metalava/RegenerateOldApisTask.kt
index 559f3fb..733d8fd 100644
--- a/buildSrc/src/main/kotlin/androidx/build/metalava/RegenerateOldApisTask.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/metalava/RegenerateOldApisTask.kt
@@ -72,7 +72,6 @@
runnerProject.logger.info("Skipping illegal version $version from $mavenId")
return
}
- project.logger.lifecycle("Regenerating $mavenId")
val inputs: JavaCompileInputs?
try {
inputs = getFiles(runnerProject, mavenId)
@@ -84,6 +83,7 @@
val outputApiLocation = project.getApiLocation(version)
val tempDir = File(project.buildDir, "api")
if (outputApiLocation.publicApiFile.exists()) {
+ project.logger.lifecycle("Regenerating $mavenId")
val generateRestrictedAPIs = outputApiLocation.restrictedApiFile.exists()
project.generateApi(
inputs, outputApiLocation, tempDir, ApiLintMode.Skip, generateRestrictedAPIs)
diff --git a/buildSrc/src/main/kotlin/androidx/build/metalava/UpdateBaselineTasks.kt b/buildSrc/src/main/kotlin/androidx/build/metalava/UpdateBaselineTasks.kt
index 5cab0d5..9d20a50 100644
--- a/buildSrc/src/main/kotlin/androidx/build/metalava/UpdateBaselineTasks.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/metalava/UpdateBaselineTasks.kt
@@ -45,10 +45,13 @@
val args = getCommonBaselineUpdateArgs(
bootClasspath,
dependencyClasspath,
- sourcePaths,
baselines.get().apiLintFile
)
- args += API_LINT_ARGS
+ args.addAll(API_LINT_ARGS + listOf<String>(
+ "--source-path",
+ sourcePaths.filter { it.exists() }.joinToString(File.pathSeparator)
+ ))
+
runWithArgs(args)
}
}
@@ -63,6 +66,9 @@
@get:Input
abstract val referenceApi: Property<ApiLocation>
+ @get:Input
+ abstract val api: Property<ApiLocation>
+
// The baseline files (api/*.*.*.ignore) to update
@get:Input
abstract val baselines: Property<ApiViolationBaselines>
@@ -93,12 +99,14 @@
check(bootClasspath.isNotEmpty()) { "Android boot classpath not set." }
updateBaseline(
+ api.get().publicApiFile,
referenceApi.get().publicApiFile,
baselines.get().publicApiFile,
false
)
if (processRestrictedApis && referenceApi.get().restrictedApiFile.exists()) {
updateBaseline(
+ api.get().restrictedApiFile,
referenceApi.get().restrictedApiFile,
baselines.get().restrictedApiFile,
true
@@ -109,19 +117,21 @@
// Updates the contents of baselineFile to specify an exception for every API present in apiFile but not
// present in the current source path
private fun updateBaseline(
- apiFile: File,
+ api: File,
+ prevApi: File,
baselineFile: File,
processRestrictedApis: Boolean
) {
val args = getCommonBaselineUpdateArgs(
bootClasspath,
dependencyClasspath,
- sourcePaths,
baselineFile
)
args += listOf(
"--check-compatibility:api:released",
- apiFile.toString()
+ prevApi.toString(),
+ "--source-files",
+ api.toString()
)
if (processRestrictedApis) {
args += listOf(
@@ -137,7 +147,6 @@
private fun getCommonBaselineUpdateArgs(
bootClasspath: Collection<File>,
dependencyClasspath: FileCollection,
- sourcePaths: Collection<File>,
baselineFile: File
): MutableList<String> {
// Create the baseline file if it does exist, as Metalava cannot handle non-existent files.
@@ -146,9 +155,6 @@
"--classpath",
(bootClasspath + dependencyClasspath.files).joinToString(File.pathSeparator),
- "--source-path",
- sourcePaths.filter { it.exists() }.joinToString(File.pathSeparator),
-
"--update-baseline",
baselineFile.toString(),
"--baseline", baselineFile.toString(),
diff --git a/camera/camera-camera2/build.gradle b/camera/camera-camera2/build.gradle
index 2a15464..b13de44 100644
--- a/camera/camera-camera2/build.gradle
+++ b/camera/camera-camera2/build.gradle
@@ -27,7 +27,7 @@
dependencies {
api(project(":camera:camera-core"))
- implementation("androidx.core:core:1.1.0-rc01")
+ implementation("androidx.core:core:1.1.0")
implementation("androidx.annotation:annotation:1.0.0")
implementation("androidx.concurrent:concurrent-futures:1.0.0-alpha03")
implementation(GUAVA_LISTENABLE_FUTURE)
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..f11a503 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,6 +34,7 @@
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;
@@ -60,6 +61,7 @@
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 +78,7 @@
}
@Before
- public void setUp() {
+ public void setUp() {
assumeTrue(CameraUtil.deviceHasCamera());
Context context = ApplicationProvider.getApplicationContext();
AppConfig appConfig = Camera2AppConfig.create(context);
@@ -86,11 +88,15 @@
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();
}
@@ -99,21 +105,21 @@
@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.
@@ -125,21 +131,20 @@
@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/impl/CaptureSessionTest.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/impl/CaptureSessionTest.java
index cd153b5..507177c 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/impl/CaptureSessionTest.java
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/impl/CaptureSessionTest.java
@@ -205,7 +205,8 @@
CaptureSession captureSession = new CaptureSession(mTestParameters0.mHandler);
captureSession.setSessionConfig(mTestParameters0.mSessionConfig);
- ListenableFuture<Void> releaseFuture = captureSession.release();
+ ListenableFuture<Void> releaseFuture = captureSession.release(
+ /*abortInFlightCaptures=*/false);
// Wait for release
releaseFuture.get();
@@ -227,7 +228,8 @@
assertThat(captureSession.getState()).isEqualTo(State.CLOSED);
// Release the session to clean up for next test
- ListenableFuture<Void> releaseFuture = captureSession.release();
+ ListenableFuture<Void> releaseFuture = captureSession.release(
+ /*abortInFlightCaptures=*/false);
// Wait for release to finish
releaseFuture.get();
@@ -239,7 +241,8 @@
CaptureSession captureSession = new CaptureSession(mTestParameters0.mHandler);
captureSession.setSessionConfig(mTestParameters0.mSessionConfig);
captureSession.open(mTestParameters0.mSessionConfig, mCameraDevice);
- ListenableFuture<Void> releaseFuture = captureSession.release();
+ ListenableFuture<Void> releaseFuture = captureSession.release(
+ /*abortInFlightCaptures=*/false);
// Wait for release
releaseFuture.get();
@@ -418,7 +421,8 @@
};
mTestParameters0.mDeferrableSurface.setOnSurfaceDetachedListener(executor, listener);
- ListenableFuture<Void> releaseFuture = captureSession.release();
+ ListenableFuture<Void> releaseFuture = captureSession.release(
+ /*abortInFlightCaptures=*/false);
// Wait for release
releaseFuture.get();
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/Camera.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/Camera.java
index 9a61ef0..8896381 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/Camera.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/Camera.java
@@ -264,7 +264,7 @@
void closeCameraResource() {
mCaptureSession.close();
- mCaptureSession.release();
+ mCaptureSession.release(/*abortInFlightCaptures=*/true);
mCameraDevice.close();
notifyCameraDeviceCloseToCaptureSessions();
mCameraDevice = null;
@@ -695,7 +695,7 @@
SessionConfig previousSessionConfig = mCaptureSession.getSessionConfig();
mCaptureSession.close();
- mCaptureSession.release();
+ mCaptureSession.release(/*abortInFlightCaptures=*/false);
// Saves the closed CaptureSessions if device is not closed yet.
// We need to notify camera device closed event to these CaptureSessions.
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/Camera2DeviceSurfaceManager.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/Camera2DeviceSurfaceManager.java
index 7b1b45b..71303cd 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/Camera2DeviceSurfaceManager.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/Camera2DeviceSurfaceManager.java
@@ -30,7 +30,6 @@
import androidx.annotation.VisibleForTesting;
import androidx.camera.core.CameraDeviceConfig;
import androidx.camera.core.CameraDeviceSurfaceManager;
-import androidx.camera.core.CameraInfoUnavailableException;
import androidx.camera.core.CameraX;
import androidx.camera.core.SurfaceConfig;
import androidx.camera.core.UseCase;
@@ -50,6 +49,7 @@
* of surface configuration type and size pairs can be supported for different hardware level camera
* devices. This structure is used to store the guaranteed supported stream capabilities related
* info.
+ *
* @hide
*/
@RestrictTo(Scope.LIBRARY)
@@ -302,11 +302,16 @@
}
private String getCameraIdFromConfig(UseCaseConfig<?> useCaseConfig) {
+ CameraDeviceConfig config = (CameraDeviceConfig) useCaseConfig;
String cameraId;
try {
- cameraId =
- CameraX.getCameraWithCameraDeviceConfig((CameraDeviceConfig) useCaseConfig);
- } catch (CameraInfoUnavailableException e) {
+ CameraX.LensFacing lensFacing = config.getLensFacing(null);
+ // Adds default lensFacing if the user doesn't specify the lens facing.
+ if (lensFacing == null) {
+ lensFacing = CameraX.getDefaultLensFacing();
+ }
+ cameraId = CameraX.getCameraWithLensFacing(lensFacing);
+ } catch (Exception e) {
throw new IllegalArgumentException(
"Unable to get camera ID for use case " + useCaseConfig.getTargetName(), e);
}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/CaptureSession.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/CaptureSession.java
index 31ed13e..2f69596 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/CaptureSession.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/CaptureSession.java
@@ -345,7 +345,7 @@
* <p>Once a session is released it can no longer be opened again. After the session is released
* all method calls on it do nothing.
*/
- ListenableFuture<Void> release() {
+ ListenableFuture<Void> release(boolean abortInFlightCaptures) {
synchronized (mStateLock) {
switch (mState) {
case UNINITIALIZED:
@@ -354,6 +354,15 @@
case OPENED:
case CLOSED:
if (mCameraCaptureSession != null) {
+ if (abortInFlightCaptures) {
+ try {
+ mCameraCaptureSession.abortCaptures();
+ } catch (CameraAccessException e) {
+ // We couldn't abort the captures, but we should continue on to
+ // release the session.
+ Log.e(TAG, "Unable to abort captures.", e);
+ }
+ }
mCameraCaptureSession.close();
}
// Fall through
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/ImageAnalysisConfigProvider.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/ImageAnalysisConfigProvider.java
index 7ec4ccd..3719aab 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/ImageAnalysisConfigProvider.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/ImageAnalysisConfigProvider.java
@@ -33,9 +33,6 @@
import androidx.camera.core.ImageAnalysisConfig;
import androidx.camera.core.SessionConfig;
-import java.util.Arrays;
-import java.util.List;
-
/**
* Provides defaults for {@link ImageAnalysisConfig} in the Camera2 implementation.
* @hide
@@ -74,24 +71,13 @@
builder.setDefaultCaptureConfig(captureBuilder.build());
builder.setCaptureOptionUnpacker(Camera2CaptureOptionUnpacker.INSTANCE);
- List<LensFacing> lensFacingList;
-
- // Add default lensFacing if we can
- if (lensFacing == LensFacing.FRONT) {
- lensFacingList = Arrays.asList(LensFacing.FRONT, LensFacing.BACK);
- } else {
- lensFacingList = Arrays.asList(LensFacing.BACK, LensFacing.FRONT);
- }
-
try {
- String defaultId = null;
-
- for (LensFacing lensFacingCandidate : lensFacingList) {
- defaultId = mCameraFactory.cameraIdForLensFacing(lensFacingCandidate);
- if (defaultId != null) {
- builder.setLensFacing(lensFacingCandidate);
- break;
- }
+ // Add default lensFacing if we can
+ LensFacing checkedLensFacing =
+ (lensFacing != null) ? lensFacing : CameraX.getDefaultLensFacing();
+ String defaultId = mCameraFactory.cameraIdForLensFacing(checkedLensFacing);
+ if (defaultId != null) {
+ builder.setLensFacing(checkedLensFacing);
}
int targetRotation = mWindowManager.getDefaultDisplay().getRotation();
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/ImageCaptureConfigProvider.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/ImageCaptureConfigProvider.java
index 158fa34..b8b2951 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/ImageCaptureConfigProvider.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/ImageCaptureConfigProvider.java
@@ -33,9 +33,6 @@
import androidx.camera.core.ImageCaptureConfig;
import androidx.camera.core.SessionConfig;
-import java.util.Arrays;
-import java.util.List;
-
/**
* Provides defaults for {@link ImageCaptureConfig} in the Camera2 implementation.
* @hide
@@ -74,24 +71,13 @@
builder.setDefaultCaptureConfig(captureConfig.build());
builder.setCaptureOptionUnpacker(ImageCaptureOptionUnpacker.INSTANCE);
- List<LensFacing> lensFacingList;
-
- // Add default lensFacing if we can
- if (lensFacing == LensFacing.FRONT) {
- lensFacingList = Arrays.asList(LensFacing.FRONT, LensFacing.BACK);
- } else {
- lensFacingList = Arrays.asList(LensFacing.BACK, LensFacing.FRONT);
- }
-
try {
- String defaultId = null;
-
- for (LensFacing lensFacingCandidate : lensFacingList) {
- defaultId = mCameraFactory.cameraIdForLensFacing(lensFacingCandidate);
- if (defaultId != null) {
- builder.setLensFacing(lensFacingCandidate);
- break;
- }
+ // Add default lensFacing if we can
+ LensFacing checkedLensFacing =
+ (lensFacing != null) ? lensFacing : CameraX.getDefaultLensFacing();
+ String defaultId = mCameraFactory.cameraIdForLensFacing(checkedLensFacing);
+ if (defaultId != null) {
+ builder.setLensFacing(checkedLensFacing);
}
int targetRotation = mWindowManager.getDefaultDisplay().getRotation();
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/PreviewConfigProvider.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/PreviewConfigProvider.java
index 290cd6f..e58ca95 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/PreviewConfigProvider.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/PreviewConfigProvider.java
@@ -33,9 +33,6 @@
import androidx.camera.core.PreviewConfig;
import androidx.camera.core.SessionConfig;
-import java.util.Arrays;
-import java.util.List;
-
/**
* Provides defaults for {@link PreviewConfig} in the Camera2 implementation.
* @hide
@@ -72,24 +69,13 @@
builder.setDefaultCaptureConfig(captureBuilder.build());
builder.setCaptureOptionUnpacker(Camera2CaptureOptionUnpacker.INSTANCE);
- List<LensFacing> lensFacingList;
-
- // Add default lensFacing if we can
- if (lensFacing == LensFacing.FRONT) {
- lensFacingList = Arrays.asList(LensFacing.FRONT, LensFacing.BACK);
- } else {
- lensFacingList = Arrays.asList(LensFacing.BACK, LensFacing.FRONT);
- }
-
try {
- String defaultId = null;
-
- for (LensFacing lensFacingCandidate : lensFacingList) {
- defaultId = mCameraFactory.cameraIdForLensFacing(lensFacingCandidate);
- if (defaultId != null) {
- builder.setLensFacing(lensFacingCandidate);
- break;
- }
+ // Add default lensFacing if we can
+ LensFacing checkedLensFacing =
+ (lensFacing != null) ? lensFacing : CameraX.getDefaultLensFacing();
+ String defaultId = mCameraFactory.cameraIdForLensFacing(checkedLensFacing);
+ if (defaultId != null) {
+ builder.setLensFacing(checkedLensFacing);
}
int targetRotation = mWindowManager.getDefaultDisplay().getRotation();
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/VideoCaptureConfigProvider.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/VideoCaptureConfigProvider.java
index 1f91fad..807ef4a 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/VideoCaptureConfigProvider.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/VideoCaptureConfigProvider.java
@@ -33,9 +33,6 @@
import androidx.camera.core.VideoCapture;
import androidx.camera.core.VideoCaptureConfig;
-import java.util.Arrays;
-import java.util.List;
-
/**
* Provides defaults for {@link VideoCaptureConfig} in the Camera2 implementation.
* @hide
@@ -74,24 +71,13 @@
builder.setDefaultCaptureConfig(captureBuilder.build());
builder.setCaptureOptionUnpacker(Camera2CaptureOptionUnpacker.INSTANCE);
- List<LensFacing> lensFacingList;
-
- // Add default lensFacing if we can
- if (lensFacing == LensFacing.FRONT) {
- lensFacingList = Arrays.asList(LensFacing.FRONT, LensFacing.BACK);
- } else {
- lensFacingList = Arrays.asList(LensFacing.BACK, LensFacing.FRONT);
- }
-
try {
- String defaultId = null;
-
- for (LensFacing lensFacingCandidate : lensFacingList) {
- defaultId = mCameraFactory.cameraIdForLensFacing(lensFacingCandidate);
- if (defaultId != null) {
- builder.setLensFacing(lensFacingCandidate);
- break;
- }
+ // Add default lensFacing if we can
+ LensFacing checkedLensFacing =
+ (lensFacing != null) ? lensFacing : CameraX.getDefaultLensFacing();
+ String defaultId = mCameraFactory.cameraIdForLensFacing(checkedLensFacing);
+ if (defaultId != null) {
+ builder.setLensFacing(checkedLensFacing);
}
int targetRotation = mWindowManager.getDefaultDisplay().getRotation();
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/impl/Camera2CaptureRequestBuilderTest.java b/camera/camera-camera2/src/test/java/androidx/camera/camera2/impl/Camera2CaptureRequestBuilderTest.java
index 84e213d..aa325cd 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/impl/Camera2CaptureRequestBuilderTest.java
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/impl/Camera2CaptureRequestBuilderTest.java
@@ -21,6 +21,7 @@
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CaptureRequest;
+import android.os.Build;
import android.view.Surface;
import androidx.camera.core.CaptureConfig;
@@ -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 java.util.HashMap;
@@ -37,7 +39,9 @@
@SmallTest
@RunWith(RobolectricTestRunner.class)
@DoNotInstrument
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
public class Camera2CaptureRequestBuilderTest {
+
@Test
public void buildCaptureRequestWithNullCameraDevice() throws CameraAccessException {
CameraDevice cameraDevice = null;
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 d93503b..2e10371 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
@@ -516,6 +516,34 @@
}
@Test
+ public void checkDefaultLensFacingForMixedUseCase() {
+ setupCamera(/* supportsRaw= */ false);
+
+ Preview preview = new Preview(new PreviewConfig.Builder().build());
+ ImageCapture imageCapture = new ImageCapture(new ImageCaptureConfig.Builder().build());
+ ImageAnalysis imageAnalysis = new ImageAnalysis(new ImageAnalysisConfig.Builder().build());
+ VideoCapture videoCapture = new VideoCapture(new VideoCaptureConfig.Builder().build());
+
+ PreviewConfig previewConfig = (PreviewConfig) preview.getUseCaseConfig();
+ ImageCaptureConfig imageCaptureConfig =
+ (ImageCaptureConfig) imageCapture.getUseCaseConfig();
+ ImageAnalysisConfig imageAnalysisConfig =
+ (ImageAnalysisConfig) imageAnalysis.getUseCaseConfig();
+ VideoCaptureConfig videoCaptureConfig =
+ (VideoCaptureConfig) videoCapture.getUseCaseConfig();
+
+ LensFacing previewLensFacing = previewConfig.getLensFacing(null);
+ LensFacing imageCaptureLensFacing = imageCaptureConfig.getLensFacing(null);
+ LensFacing imageAnalysisLensFacing = imageAnalysisConfig.getLensFacing(null);
+ LensFacing videoCaptureLensFacing = videoCaptureConfig.getLensFacing(null);
+
+ assertThat(previewLensFacing).isNotNull();
+ assertThat(imageCaptureLensFacing).isNotNull();
+ assertThat(imageAnalysisLensFacing).isNotNull();
+ assertThat(videoCaptureLensFacing).isNotNull();
+ }
+
+ @Test
public void suggestedResolutionsForMixedUseCaseNotSupportedInLegacyDevice() {
setupCamera(/* supportsRaw= */ false);
SupportedSurfaceCombination supportedSurfaceCombination =
diff --git a/camera/camera-core/build.gradle b/camera/camera-core/build.gradle
index d792fc0..b111e0d 100644
--- a/camera/camera-core/build.gradle
+++ b/camera/camera-core/build.gradle
@@ -29,7 +29,7 @@
api("androidx.lifecycle:lifecycle-common:2.0.0")
implementation("androidx.exifinterface:exifinterface:1.0.0")
implementation("androidx.annotation:annotation:1.0.0")
- implementation("androidx.core:core:1.1.0-rc01")
+ implementation("androidx.core:core:1.1.0")
implementation("androidx.concurrent:concurrent-futures:1.0.0-alpha03")
implementation(ARCH_LIFECYCLE_LIVEDATA)
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/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 e5d2998a..0679266 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
@@ -29,6 +29,7 @@
import androidx.lifecycle.LifecycleOwner;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
@@ -352,6 +353,30 @@
}
/**
+ * Gets the default lens facing or {@code null} if there is no available camera.
+ *
+ * @return The default lens facing or {@code null}.
+ * @throws CameraInfoUnavailableException if unable to access cameras, perhaps due to
+ * insufficient permissions.
+ * @hide
+ */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ @Nullable
+ public static LensFacing getDefaultLensFacing()
+ throws CameraInfoUnavailableException {
+ LensFacing lensFacingCandidate = null;
+ List<LensFacing> lensFacingList = Arrays.asList(LensFacing.BACK, LensFacing.FRONT);
+ for (LensFacing lensFacing : lensFacingList) {
+ String cameraId = INSTANCE.getCameraFactory().cameraIdForLensFacing(lensFacing);
+ if (cameraId != null) {
+ lensFacingCandidate = lensFacing;
+ break;
+ }
+ }
+ return lensFacingCandidate;
+ }
+
+ /**
* Returns the camera info for the camera with the given camera id.
*
* @param cameraId the internal id of the camera
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..e94c9d60 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,26 @@
* @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();
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..5733c02 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 {
/**
@@ -58,7 +58,7 @@
private static final String TAG = "ImageAnalysis";
final AtomicReference<Analyzer> mSubscribedAnalyzer;
final AtomicInteger mRelativeRotation = new AtomicInteger();
- private final Handler mHandler;
+ final Handler mHandler;
private final ImageAnalysisConfig.Builder mUseCaseConfigBuilder;
@Nullable
ImageReaderProxy mImageReader;
@@ -257,6 +257,9 @@
mImageReader.close();
}
+ Executor backgroundExecutor = config.getBackgroundExecutor(
+ CameraXExecutors.highPriorityExecutor());
+
mImageReader =
ImageReaderProxys.createCompatibleReader(
cameraId,
@@ -264,7 +267,7 @@
resolution.getHeight(),
getImageFormat(),
config.getImageQueueDepth(),
- mHandler);
+ backgroundExecutor);
tryUpdateRelativeRotation(cameraId);
mImageReader.setOnImageAvailableListener(
@@ -272,24 +275,30 @@
@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;
- }
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ 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());
+ if (analyzer != null) {
+ analyzer.analyze(image, mRelativeRotation.get());
+ }
+ }
}
- }
+ });
}
},
- mHandler);
+ backgroundExecutor);
SessionConfig.Builder sessionConfigBuilder = SessionConfig.Builder.createFrom(config);
@@ -329,12 +338,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 +368,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);
}
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/CaptureBundlesTest.java b/camera/camera-core/src/test/java/androidx/camera/core/CaptureBundlesTest.java
index b6bcc13..f6a205f 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/CaptureBundlesTest.java
+++ b/camera/camera-core/src/test/java/androidx/camera/core/CaptureBundlesTest.java
@@ -18,16 +18,20 @@
import static com.google.common.truth.Truth.assertThat;
+import android.os.Build;
+
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 CaptureBundlesTest {
@Test
public void singleDefaultBundleSizeCheck() {
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/CaptureStagesTest.java b/camera/camera-core/src/test/java/androidx/camera/core/CaptureStagesTest.java
index 7f7ab60..fdc808e 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/CaptureStagesTest.java
+++ b/camera/camera-core/src/test/java/androidx/camera/core/CaptureStagesTest.java
@@ -18,21 +18,26 @@
import static com.google.common.truth.Truth.assertThat;
+import android.os.Build;
+
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 CaptureStagesTest {
@Test
public void defaultCaptureStageHasNoOptions() {
CaptureStage captureStage = new CaptureStage.DefaultCaptureStage();
- Config options = captureStage.getCaptureConfig().getImplementationOptions();
+ androidx.camera.core.Config options =
+ captureStage.getCaptureConfig().getImplementationOptions();
assertThat(options.listOptions().size()).isEqualTo(0);
}
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/ImageUtilTest.java b/camera/camera-core/src/test/java/androidx/camera/core/ImageUtilTest.java
index e89270b..e0e88d1 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/ImageUtilTest.java
+++ b/camera/camera-core/src/test/java/androidx/camera/core/ImageUtilTest.java
@@ -28,6 +28,7 @@
import android.graphics.ImageFormat;
import android.graphics.Point;
import android.graphics.Rect;
+import android.os.Build;
import android.util.Base64;
import android.util.Rational;
import android.util.Size;
@@ -40,6 +41,7 @@
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
import org.robolectric.annotation.internal.DoNotInstrument;
import java.nio.ByteBuffer;
@@ -47,6 +49,7 @@
@SmallTest
@RunWith(RobolectricTestRunner.class)
@DoNotInstrument
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
public class ImageUtilTest {
private static final int WIDTH = 160;
private static final int HEIGHT = 120;
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/ProcessingImageReaderTest.java b/camera/camera-core/src/test/java/androidx/camera/core/ProcessingImageReaderTest.java
index dfa5d8b..552efbf 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/ProcessingImageReaderTest.java
+++ b/camera/camera-core/src/test/java/androidx/camera/core/ProcessingImageReaderTest.java
@@ -21,6 +21,7 @@
import static org.mockito.Mockito.mock;
import android.graphics.ImageFormat;
+import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.util.Size;
@@ -34,6 +35,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.shadows.ShadowLooper;
@@ -47,6 +49,7 @@
@SmallTest
@RunWith(RobolectricTestRunner.class)
@DoNotInstrument
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
public final class ProcessingImageReaderTest {
private static final int CAPTURE_ID_0 = 0;
private static final int CAPTURE_ID_1 = 1;
@@ -88,7 +91,7 @@
}
@Test
- public void canSetFuturesInSettableImageProxyBundle()
+ public void canSetFuturesInSettableImawgeProxyBundle()
throws InterruptedException, TimeoutException, ExecutionException {
final AtomicReference<ImageProxyBundle> bundleRef = new AtomicReference<>();
// Sets the callback from ProcessingImageReader to start processing
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/SingleImageProxyBundleTest.java b/camera/camera-core/src/test/java/androidx/camera/core/SingleImageProxyBundleTest.java
index 61ac5ef..a8845df 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/SingleImageProxyBundleTest.java
+++ b/camera/camera-core/src/test/java/androidx/camera/core/SingleImageProxyBundleTest.java
@@ -18,6 +18,8 @@
import static com.google.common.truth.Truth.assertThat;
+import android.os.Build;
+
import androidx.camera.testing.fakes.FakeImageInfo;
import androidx.camera.testing.fakes.FakeImageProxy;
import androidx.test.filters.SmallTest;
@@ -25,11 +27,13 @@
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 final class SingleImageProxyBundleTest {
@Test
public void successfulCreationFromImageProxy() {
diff --git a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/ExtensionAvailability.java b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/ExtensionAvailability.java
deleted file mode 100644
index 252d8ec..0000000
--- a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/ExtensionAvailability.java
+++ /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.camera.extensions.impl;
-
-import android.hardware.camera2.CameraCharacteristics;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-/**
- * Provides abstract methods that the OEM needs to implement to check extension status.
- */
-public interface ExtensionAvailability {
- /**
- * Indicates whether the extension is supported on the camera device with specified camera id.
- *
- * @param cameraId The camera2 id string of the camera.
- * @param cameraCharacteristics The {@link CameraCharacteristics} of the camera.
- * @return true if the extension is supported, otherwise false
- */
- boolean isExtensionAvailable(@NonNull String cameraId,
- @Nullable CameraCharacteristics cameraCharacteristics);
-}
diff --git a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/ImageCaptureExtenderImpl.java b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/ImageCaptureExtenderImpl.java
index 8e2773f..6c5c124 100644
--- a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/ImageCaptureExtenderImpl.java
+++ b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/ImageCaptureExtenderImpl.java
@@ -23,7 +23,16 @@
/**
* Provides abstract methods that the OEM needs to implement to enable extensions for image capture.
*/
-public interface ImageCaptureExtenderImpl extends ExtenderStateListener, ExtensionAvailability {
+public interface ImageCaptureExtenderImpl extends ExtenderStateListener {
+ /**
+ * Indicates whether the extension is supported on the device.
+ *
+ * @param cameraId The camera2 id string of the camera.
+ * @param cameraCharacteristics The {@link CameraCharacteristics} of the camera.
+ * @return true if the extension is supported, otherwise false
+ */
+ boolean isExtensionAvailable(String cameraId, CameraCharacteristics cameraCharacteristics);
+
/**
* Initializes the extender to be used with the specified camera.
*
diff --git a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/PreviewExtenderImpl.java b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/PreviewExtenderImpl.java
index 215ddc4..cfe2995 100644
--- a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/PreviewExtenderImpl.java
+++ b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/PreviewExtenderImpl.java
@@ -22,7 +22,7 @@
/**
* Provides abstract methods that the OEM needs to implement to enable extensions in the preview.
*/
-public interface PreviewExtenderImpl extends ExtenderStateListener, ExtensionAvailability {
+public interface PreviewExtenderImpl extends ExtenderStateListener {
/** The different types of the preview processing. */
enum ProcessorType {
/** Processor which only updates the {@link CaptureStageImpl}. */
@@ -34,6 +34,15 @@
}
/**
+ * Indicates whether the extension is supported on the device.
+ *
+ * @param cameraId The camera2 id string of the camera.
+ * @param cameraCharacteristics The {@link CameraCharacteristics} of the camera.
+ * @return true if the extension is supported, otherwise false
+ */
+ boolean isExtensionAvailable(String cameraId, CameraCharacteristics cameraCharacteristics);
+
+ /**
* Initializes the extender to be used with the specified camera.
*
* <p>This should be called before any other method on the extender. The exception is {@link
diff --git a/camera/camera-extensions/build.gradle b/camera/camera-extensions/build.gradle
index 5e20eb3..0e3202c 100644
--- a/camera/camera-extensions/build.gradle
+++ b/camera/camera-extensions/build.gradle
@@ -57,8 +57,7 @@
defaultConfig {
minSdkVersion 21
- // TODO: To use the separate version for extensions after new library item is created.
- buildConfigField "String", "CAMERA_VERSION", "\"${LibraryVersions.CAMERA}\""
+ buildConfigField "String", "CAMERA_VERSION", "\"${LibraryVersions.CAMERA_EXTENSIONS}\""
}
}
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionTest.java b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionTest.java
new file mode 100644
index 0000000..471b902
--- /dev/null
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionTest.java
@@ -0,0 +1,315 @@
+/*
+ * 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.extensions;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertNotNull;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import android.Manifest;
+import android.content.Context;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CaptureRequest;
+
+import androidx.annotation.NonNull;
+import androidx.camera.camera2.Camera2AppConfig;
+import androidx.camera.camera2.Camera2Config;
+import androidx.camera.camera2.impl.CameraEventCallback;
+import androidx.camera.camera2.impl.CameraEventCallbacks;
+import androidx.camera.core.AppConfig;
+import androidx.camera.core.CameraInfoUnavailableException;
+import androidx.camera.core.CameraX;
+import androidx.camera.core.ImageCapture;
+import androidx.camera.core.ImageCaptureConfig;
+import androidx.camera.core.ImageProxy;
+import androidx.camera.core.Preview;
+import androidx.camera.core.PreviewConfig;
+import androidx.camera.extensions.impl.AutoPreviewExtenderImpl;
+import androidx.camera.extensions.impl.BeautyPreviewExtenderImpl;
+import androidx.camera.extensions.impl.BokehPreviewExtenderImpl;
+import androidx.camera.extensions.impl.HdrPreviewExtenderImpl;
+import androidx.camera.extensions.impl.NightPreviewExtenderImpl;
+import androidx.camera.testing.CameraUtil;
+import androidx.camera.testing.fakes.FakeLifecycleOwner;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.LargeTest;
+import androidx.test.rule.GrantPermissionRule;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.mockito.ArgumentCaptor;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(Parameterized.class)
+@LargeTest
+public class ExtensionTest {
+
+ @Rule
+ public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(
+ Manifest.permission.CAMERA);
+
+ private FakeLifecycleOwner mLifecycleOwner;
+ private CameraDevice.StateCallback mCameraStatusCallback;
+ private ExtensionsManager.EffectMode mEffectMode;
+ private CameraX.LensFacing mLensFacing;
+ private CountDownLatch mLatch;
+
+ private ImageCaptureConfig.Builder mImageCaptureConfigBuilder;
+ private PreviewConfig.Builder mPreviewConfigBuilder;
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> getParameters() {
+ return Arrays.asList(new Object[][] {
+ { ExtensionsManager.EffectMode.BOKEH, CameraX.LensFacing.FRONT },
+ { ExtensionsManager.EffectMode.BOKEH, CameraX.LensFacing.BACK },
+ { ExtensionsManager.EffectMode.HDR, CameraX.LensFacing.FRONT },
+ { ExtensionsManager.EffectMode.HDR, CameraX.LensFacing.BACK },
+ { ExtensionsManager.EffectMode.BEAUTY, CameraX.LensFacing.FRONT },
+ { ExtensionsManager.EffectMode.BEAUTY, CameraX.LensFacing.BACK },
+ { ExtensionsManager.EffectMode.NIGHT, CameraX.LensFacing.FRONT },
+ { ExtensionsManager.EffectMode.NIGHT, CameraX.LensFacing.BACK },
+ { ExtensionsManager.EffectMode.AUTO, CameraX.LensFacing.FRONT },
+ { ExtensionsManager.EffectMode.AUTO, CameraX.LensFacing.BACK }
+ });
+ }
+
+ public ExtensionTest(ExtensionsManager.EffectMode effectMode, CameraX.LensFacing lensFacing) {
+ mEffectMode = effectMode;
+ mLensFacing = lensFacing;
+ }
+
+ @Before
+ public void setUp() {
+ assumeTrue(CameraUtil.deviceHasCamera());
+
+ Context context = ApplicationProvider.getApplicationContext();
+ AppConfig appConfig = Camera2AppConfig.create(context);
+ CameraX.init(context, appConfig);
+
+ mLifecycleOwner = new FakeLifecycleOwner();
+ mLifecycleOwner.startAndResume();
+
+ mImageCaptureConfigBuilder = new ImageCaptureConfig.Builder();
+ mPreviewConfigBuilder = new PreviewConfig.Builder();
+ mCameraStatusCallback = new CameraDevice.StateCallback() {
+ @Override
+ public void onOpened(@NonNull CameraDevice camera) {
+ mLatch = new CountDownLatch(1);
+ }
+
+ @Override
+ public void onDisconnected(@NonNull CameraDevice camera) {
+
+ }
+
+ @Override
+ public void onError(@NonNull CameraDevice camera, int error) {
+
+ }
+
+ @Override
+ public void onClosed(@NonNull CameraDevice camera) {
+ mLatch.countDown();
+ }
+ };
+
+ new Camera2Config.Extender(mImageCaptureConfigBuilder).setDeviceStateCallback(
+ mCameraStatusCallback);
+ }
+
+ @After
+ public void cleanUp() throws InterruptedException {
+ if (mLatch != null) {
+ CameraX.unbindAll();
+
+ // Make sure camera was closed.
+ mLatch.await(3000, TimeUnit.MILLISECONDS);
+ }
+ }
+
+ @Test
+ public void testCanBindToLifeCycleAndTakePicture() {
+ assumeTrue(CameraUtil.hasCameraWithLensFacing(mLensFacing));
+ assumeTrue(ExtensionsManager.isExtensionAvailable(mEffectMode, mLensFacing));
+ assumeTrue(supportAFMode(mLensFacing));
+
+ enableExtension(mEffectMode, mLensFacing);
+
+ Preview.OnPreviewOutputUpdateListener mockOnPreviewOutputUpdateListener = mock(
+ Preview.OnPreviewOutputUpdateListener.class);
+ ImageCapture.OnImageCapturedListener mockOnImageCapturedListener = mock(
+ ImageCapture.OnImageCapturedListener.class);
+
+ // To test bind/unbind and take picture.
+ ImageCapture imageCapture = new ImageCapture(mImageCaptureConfigBuilder.build());
+ Preview preview = new Preview(mPreviewConfigBuilder.build());
+
+ CameraX.bindToLifecycle(mLifecycleOwner, preview, imageCapture);
+
+ // To set the update listener and Preview will change to active state.
+ preview.setOnPreviewOutputUpdateListener(mockOnPreviewOutputUpdateListener);
+
+ imageCapture.takePicture(mockOnImageCapturedListener);
+
+ // Verify the image captured.
+ ArgumentCaptor<ImageProxy> imageProxy = ArgumentCaptor.forClass(ImageProxy.class);
+ verify(mockOnImageCapturedListener, timeout(3000)).onCaptureSuccess(
+ imageProxy.capture(), anyInt());
+ assertNotNull(imageProxy.getValue());
+ imageProxy.getValue().close(); // Close the image after verification.
+
+ // Verify the take picture should not have any error happen.
+ verify(mockOnImageCapturedListener, never()).onError(
+ any(ImageCapture.UseCaseError.class), anyString(), any(Throwable.class));
+ }
+
+ @Test
+ public void testEventCallbackInConfig() {
+ assumeTrue(CameraUtil.hasCameraWithLensFacing(mLensFacing));
+ assumeTrue(ExtensionsManager.isExtensionAvailable(mEffectMode, mLensFacing));
+
+ enableExtension(mEffectMode, mLensFacing);
+
+ // Verify Preview config should have related callback.
+ PreviewConfig previewConfig = mPreviewConfigBuilder.build();
+ assertNotNull(previewConfig.getUseCaseEventListener());
+ CameraEventCallbacks callback1 = new Camera2Config(previewConfig).getCameraEventCallback(
+ null);
+ assertNotNull(callback1);
+ assertEquals(callback1.getAllItems().size(), 1);
+ assertThat(callback1.getAllItems().get(0)).isInstanceOf(CameraEventCallback.class);
+
+ // Verify ImageCapture config should have related callback.
+ ImageCaptureConfig imageCaptureConfig = mImageCaptureConfigBuilder.build();
+ assertNotNull(imageCaptureConfig.getUseCaseEventListener());
+ assertNotNull(imageCaptureConfig.getCaptureBundle());
+ CameraEventCallbacks callback2 = new Camera2Config(
+ imageCaptureConfig).getCameraEventCallback(null);
+ assertNotNull(callback2);
+ assertEquals(callback2.getAllItems().size(), 1);
+ assertThat(callback2.getAllItems().get(0)).isInstanceOf(CameraEventCallback.class);
+ }
+
+ /**
+ * To invoke the enableExtension() method for different effect.
+ */
+ private void enableExtension(ExtensionsManager.EffectMode effectMode,
+ CameraX.LensFacing lensFacing) {
+
+ mImageCaptureConfigBuilder.setLensFacing(lensFacing);
+ mPreviewConfigBuilder.setLensFacing(lensFacing);
+
+ ImageCaptureExtender imageCaptureExtender = null;
+ PreviewExtender previewExtender = null;
+
+ switch (effectMode) {
+ case HDR:
+ imageCaptureExtender = HdrImageCaptureExtender.create(mImageCaptureConfigBuilder);
+ previewExtender = HdrPreviewExtender.create(mPreviewConfigBuilder);
+
+ // Make sure we are testing on the testlib/Vendor implementation.
+ assertThat(previewExtender.mImpl).isInstanceOf(HdrPreviewExtenderImpl.class);
+ break;
+ case BOKEH:
+ imageCaptureExtender = BokehImageCaptureExtender.create(
+ mImageCaptureConfigBuilder);
+ previewExtender = BokehPreviewExtender.create(mPreviewConfigBuilder);
+
+ // Make sure we are testing on the testlib/Vendor implementation.
+ assertThat(previewExtender.mImpl).isInstanceOf(BokehPreviewExtenderImpl.class);
+ break;
+ case BEAUTY:
+ imageCaptureExtender = BeautyImageCaptureExtender.create(
+ mImageCaptureConfigBuilder);
+ previewExtender = BeautyPreviewExtender.create(mPreviewConfigBuilder);
+
+ // Make sure we are testing on the testlib/Vendor implementation.
+ assertThat(previewExtender.mImpl).isInstanceOf(BeautyPreviewExtenderImpl.class);
+ break;
+ case NIGHT:
+ imageCaptureExtender = NightImageCaptureExtender.create(mImageCaptureConfigBuilder);
+ previewExtender = NightPreviewExtender.create(mPreviewConfigBuilder);
+
+ // Make sure we are testing on the testlib/Vendor implementation.
+ assertThat(previewExtender.mImpl).isInstanceOf(NightPreviewExtenderImpl.class);
+ break;
+ case AUTO:
+ imageCaptureExtender = AutoImageCaptureExtender.create(mImageCaptureConfigBuilder);
+ previewExtender = AutoPreviewExtender.create(mPreviewConfigBuilder);
+
+ // Make sure we are testing on the testlib/Vendor implementation.
+ assertThat(previewExtender.mImpl).isInstanceOf(AutoPreviewExtenderImpl.class);
+ break;
+ }
+
+ assertNotNull(imageCaptureExtender);
+ assertNotNull(previewExtender);
+
+ assertTrue(previewExtender.isExtensionAvailable());
+ previewExtender.enableExtension();
+ assertTrue(imageCaptureExtender.isExtensionAvailable());
+ imageCaptureExtender.enableExtension();
+ }
+
+ private static boolean supportAFMode(CameraX.LensFacing lensFacing) {
+ String cameraId = null;
+ try {
+ cameraId = CameraX.getCameraWithLensFacing(lensFacing);
+ } catch (CameraInfoUnavailableException e) {
+ return false;
+ }
+
+ CameraCharacteristics characteristics = null;
+ try {
+ characteristics = CameraUtil.getCameraManager().getCameraCharacteristics(cameraId);
+ } catch (CameraAccessException e) {
+ return false;
+ }
+
+ int[] afModes = characteristics.get(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES);
+
+ if (afModes == null) {
+ return false;
+ }
+
+ // CameraX will use CONTROL_AF_MODE_AUTO or CONTROL_AF_MODE_CONTINUOUS_PICTURE to take
+ // picture. To check if the camera can support the these AF modes.
+ return Arrays.asList(afModes).contains(CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)
+ && Arrays.asList(afModes).contains(CaptureRequest.CONTROL_AF_MODE_AUTO);
+ }
+}
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/PreviewExtenderTest.java b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/PreviewExtenderTest.java
index 7ae0547..d9551c3 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/PreviewExtenderTest.java
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/PreviewExtenderTest.java
@@ -33,6 +33,7 @@
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.TotalCaptureResult;
+import android.media.Image;
import android.util.Pair;
import androidx.camera.core.CameraX;
@@ -48,6 +49,7 @@
import androidx.test.filters.MediumTest;
import androidx.test.rule.GrantPermissionRule;
+import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -61,6 +63,8 @@
@RunWith(AndroidJUnit4.class)
public class PreviewExtenderTest {
+ private FakeLifecycleOwner mFakeLifecycle;
+
@Rule
public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(
Manifest.permission.CAMERA);
@@ -68,13 +72,19 @@
@Before
public void setUp() {
assumeTrue(CameraUtil.deviceHasCamera());
+ assumeTrue(CameraUtil.hasCameraWithLensFacing(CameraX.LensFacing.BACK));
+ mFakeLifecycle = new FakeLifecycleOwner();
+ mFakeLifecycle.startAndResume();
+ }
+
+ @After
+ public void cleanUp() {
+ CameraX.unbindAll();
}
@Test
@MediumTest
public void extenderLifeCycleTest_noMoreInvokeBeforeAndAfterInitDeInit() {
- FakeLifecycleOwner lifecycle = new FakeLifecycleOwner();
-
PreviewExtenderImpl mockPreviewExtenderImpl = mock(PreviewExtenderImpl.class);
when(mockPreviewExtenderImpl.getProcessorType()).thenReturn(
PreviewExtenderImpl.ProcessorType.PROCESSOR_TYPE_IMAGE_PROCESSOR);
@@ -92,8 +102,7 @@
Preview useCase = new Preview(configBuilder.build());
- CameraX.bindToLifecycle(lifecycle, useCase);
- lifecycle.startAndResume();
+ CameraX.bindToLifecycle(mFakeLifecycle, useCase);
// To set the update listener and Preview will change to active state.
useCase.setOnPreviewOutputUpdateListener(mock(Preview.OnPreviewOutputUpdateListener.class));
@@ -129,8 +138,6 @@
@Test
@MediumTest
public void getCaptureStagesTest_shouldSetToRepeatingRequest() throws InterruptedException {
- FakeLifecycleOwner lifecycle = new FakeLifecycleOwner();
-
// Set up a result for getCaptureStages() testing.
CaptureStageImpl fakeCaptureStageImpl = new FakeCaptureStageImpl();
@@ -154,13 +161,13 @@
mockPreviewExtenderImpl);
fakePreviewExtender.enableExtension();
- Preview useCase = new Preview(configBuilder.build());
+ Preview preview = new Preview(configBuilder.build());
- CameraX.bindToLifecycle(lifecycle, useCase);
- lifecycle.startAndResume();
+ CameraX.bindToLifecycle(mFakeLifecycle, preview);
// To set the update listener and Preview will change to active state.
- useCase.setOnPreviewOutputUpdateListener(mock(Preview.OnPreviewOutputUpdateListener.class));
+ preview.setOnPreviewOutputUpdateListener(
+ mock(Preview.OnPreviewOutputUpdateListener.class));
ArgumentCaptor<TotalCaptureResult> captureResultArgumentCaptor = ArgumentCaptor.forClass(
TotalCaptureResult.class);
@@ -181,6 +188,36 @@
}
}
+ @Test
+ @MediumTest
+ public void processShouldBeInvoked_typeImageProcessor() {
+ // The type image processor will invoke PreviewImageProcessor.process()
+ PreviewImageProcessorImpl mockPreviewImageProcessorImpl = mock(
+ PreviewImageProcessorImpl.class);
+
+ PreviewExtenderImpl mockPreviewExtenderImpl = mock(PreviewExtenderImpl.class);
+ when(mockPreviewExtenderImpl.getProcessor()).thenReturn(mockPreviewImageProcessorImpl);
+ when(mockPreviewExtenderImpl.getProcessorType()).thenReturn(
+ PreviewExtenderImpl.ProcessorType.PROCESSOR_TYPE_IMAGE_PROCESSOR);
+ when(mockPreviewExtenderImpl.isExtensionAvailable(any(String.class),
+ any(CameraCharacteristics.class))).thenReturn(true);
+
+ PreviewConfig.Builder configBuilder = new PreviewConfig.Builder().setLensFacing(
+ CameraX.LensFacing.BACK);
+ FakePreviewExtender fakePreviewExtender = new FakePreviewExtender(configBuilder,
+ mockPreviewExtenderImpl);
+ fakePreviewExtender.enableExtension();
+ Preview preview = new Preview(configBuilder.build());
+ CameraX.bindToLifecycle(mFakeLifecycle, preview);
+ // To set the update listener and Preview will change to active state.
+ preview.setOnPreviewOutputUpdateListener(
+ mock(Preview.OnPreviewOutputUpdateListener.class));
+
+ // To verify the process() method was invoked with non-null TotalCaptureResult input.
+ verify(mockPreviewImageProcessorImpl, timeout(3000).atLeastOnce()).process(any(Image.class),
+ any(TotalCaptureResult.class));
+ }
+
private class FakePreviewExtender extends PreviewExtender {
FakePreviewExtender(PreviewConfig.Builder builder, PreviewExtenderImpl impl) {
init(builder, impl, ExtensionsManager.EffectMode.NORMAL);
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/ExtensionCameraIdFilter.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/ExtensionCameraIdFilter.java
index 6dacde0..ce50789 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/ExtensionCameraIdFilter.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/ExtensionCameraIdFilter.java
@@ -17,20 +17,36 @@
package androidx.camera.extensions;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.camera.core.CameraIdFilter;
-import androidx.camera.extensions.impl.ExtensionAvailability;
+import androidx.camera.extensions.impl.ImageCaptureExtenderImpl;
+import androidx.camera.extensions.impl.PreviewExtenderImpl;
import java.util.LinkedHashSet;
import java.util.Set;
/**
- * Filter camera id by extension availability.
+ * Filter camera id by extender implementation. If the implementation is null, all the camera ids
+ * will be considered available.
*/
public final class ExtensionCameraIdFilter implements CameraIdFilter {
- private ExtensionAvailability mExtensionAvailability;
+ private PreviewExtenderImpl mPreviewExtenderImpl;
+ private ImageCaptureExtenderImpl mImageCaptureExtenderImpl;
- ExtensionCameraIdFilter(ExtensionAvailability extensionAvailability) {
- mExtensionAvailability = extensionAvailability;
+ ExtensionCameraIdFilter(@Nullable PreviewExtenderImpl previewExtenderImpl) {
+ mPreviewExtenderImpl = previewExtenderImpl;
+ mImageCaptureExtenderImpl = null;
+ }
+
+ ExtensionCameraIdFilter(@Nullable ImageCaptureExtenderImpl imageCaptureExtenderImpl) {
+ mPreviewExtenderImpl = null;
+ mImageCaptureExtenderImpl = imageCaptureExtenderImpl;
+ }
+
+ ExtensionCameraIdFilter(@Nullable PreviewExtenderImpl previewExtenderImpl,
+ @Nullable ImageCaptureExtenderImpl imageCaptureExtenderImpl) {
+ mPreviewExtenderImpl = previewExtenderImpl;
+ mImageCaptureExtenderImpl = imageCaptureExtenderImpl;
}
@Override
@@ -38,8 +54,22 @@
public Set<String> filter(@NonNull Set<String> cameraIdSet) {
Set<String> resultCameraIdSet = new LinkedHashSet<>();
for (String cameraId : cameraIdSet) {
- if (mExtensionAvailability.isExtensionAvailable(cameraId,
- CameraUtil.getCameraCharacteristics(cameraId))) {
+ boolean available = true;
+
+ // If preview extender impl isn't null, check if the camera id is supported.
+ if (mPreviewExtenderImpl != null) {
+ available = mPreviewExtenderImpl.isExtensionAvailable(cameraId,
+ CameraUtil.getCameraCharacteristics(cameraId));
+ }
+
+ // If image capture extender impl isn't null, check if the camera id is supported.
+ if (mImageCaptureExtenderImpl != null) {
+ available = mImageCaptureExtenderImpl.isExtensionAvailable(cameraId,
+ CameraUtil.getCameraCharacteristics(cameraId));
+ }
+
+ // If the camera id is supported, add it to the result set.
+ if (available) {
resultCameraIdSet.add(cameraId);
}
}
diff --git a/camera/camera-testing/build.gradle b/camera/camera-testing/build.gradle
index 396ab3c..2be49b7 100644
--- a/camera/camera-testing/build.gradle
+++ b/camera/camera-testing/build.gradle
@@ -30,7 +30,7 @@
implementation("androidx.annotation:annotation:1.0.0")
implementation(GUAVA_LISTENABLE_FUTURE)
implementation(project(":camera:camera-core"))
- implementation("androidx.core:core:1.1.0-rc01")
+ implementation("androidx.core:core:1.1.0")
implementation("androidx.concurrent:concurrent-futures:1.0.0-alpha03")
implementation(JUNIT)
}
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/CameraUtil.java b/camera/camera-testing/src/main/java/androidx/camera/testing/CameraUtil.java
index 09c3e56..ef6e389 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/CameraUtil.java
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/CameraUtil.java
@@ -20,20 +20,25 @@
import android.content.Context;
import android.hardware.Camera;
import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraDevice.StateCallback;
import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CameraMetadata;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.annotation.RequiresPermission;
import androidx.camera.core.BaseCamera;
+import androidx.camera.core.CameraX;
import androidx.camera.core.UseCase;
import androidx.test.core.app.ApplicationProvider;
import java.util.Arrays;
+import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
@@ -191,4 +196,54 @@
return numberOfCamera > 0;
}
+
+ /**
+ * Check if the specified lensFacing is supported by the device.
+ *
+ * @param lensFacing The desired camera lensFacing.
+ * @return True if the device supports the lensFacing.
+ * @throws IllegalStateException if the CAMERA permission is not currently granted.
+ */
+ public static boolean hasCameraWithLensFacing(@NonNull CameraX.LensFacing lensFacing) {
+
+ CameraManager cameraManager = getCameraManager();
+
+ List<String> camerasList = null;
+ try {
+ camerasList = Arrays.asList(cameraManager.getCameraIdList());
+ } catch (CameraAccessException e) {
+ throw new IllegalStateException(
+ "Unable to retrieve list of cameras on device.", e);
+ }
+
+ // Convert to from CameraX enum to Camera2 CameraMetadata
+ Integer lensFacingInteger = -1;
+ switch (lensFacing) {
+ case BACK:
+ lensFacingInteger = CameraMetadata.LENS_FACING_BACK;
+ break;
+ case FRONT:
+ lensFacingInteger = CameraMetadata.LENS_FACING_FRONT;
+ break;
+ }
+
+ for (String cameraId : camerasList) {
+ CameraCharacteristics characteristics = null;
+ try {
+ characteristics = cameraManager.getCameraCharacteristics(cameraId);
+ } catch (CameraAccessException e) {
+ throw new IllegalStateException(
+ "Unable to retrieve info for camera with id " + cameraId + ".", e);
+ }
+ Integer cameraLensFacing = characteristics.get(CameraCharacteristics.LENS_FACING);
+ if (cameraLensFacing == null) {
+ continue;
+ }
+ if (cameraLensFacing.equals(lensFacingInteger)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
}
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 d47c9f4..1e65e192 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
@@ -24,15 +24,20 @@
import static org.junit.Assume.assumeTrue;
+import android.content.Intent;
+
import androidx.camera.integration.extensions.idlingresource.WaitForViewToShow;
import androidx.camera.testing.CameraUtil;
+import androidx.test.core.app.ApplicationProvider;
import androidx.test.espresso.Espresso;
import androidx.test.espresso.IdlingRegistry;
import androidx.test.espresso.IdlingResource;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
+import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
import androidx.test.rule.GrantPermissionRule;
+import androidx.test.uiautomator.UiDevice;
import org.junit.After;
import org.junit.Before;
@@ -47,6 +52,8 @@
@LargeTest
public final class ToggleButtonTest {
+ private static final int DISMISS_LOCK_SCREEN_CODE = 82;
+
@Rule
public ActivityTestRule<CameraExtensionsActivity> mActivityRule =
new ActivityTestRule<>(CameraExtensionsActivity.class);
@@ -70,6 +77,14 @@
@Before
public void setUp() {
assumeTrue(CameraUtil.deviceHasCamera());
+
+ // In case the lock screen on top, the action to dismiss it.
+ UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()).pressKeyCode(
+ DISMISS_LOCK_SCREEN_CODE);
+
+ // Close system dialogs first to avoid interrupt.
+ ApplicationProvider.getApplicationContext().sendBroadcast(
+ new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
}
@After
diff --git a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/ExtensionAvailability.java b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/ExtensionAvailability.java
deleted file mode 100644
index 252d8ec..0000000
--- a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/ExtensionAvailability.java
+++ /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.camera.extensions.impl;
-
-import android.hardware.camera2.CameraCharacteristics;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-/**
- * Provides abstract methods that the OEM needs to implement to check extension status.
- */
-public interface ExtensionAvailability {
- /**
- * Indicates whether the extension is supported on the camera device with specified camera id.
- *
- * @param cameraId The camera2 id string of the camera.
- * @param cameraCharacteristics The {@link CameraCharacteristics} of the camera.
- * @return true if the extension is supported, otherwise false
- */
- boolean isExtensionAvailable(@NonNull String cameraId,
- @Nullable CameraCharacteristics cameraCharacteristics);
-}
diff --git a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/ImageCaptureExtenderImpl.java b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/ImageCaptureExtenderImpl.java
index 43f65c0..8bf39ae 100644
--- a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/ImageCaptureExtenderImpl.java
+++ b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/ImageCaptureExtenderImpl.java
@@ -23,7 +23,16 @@
/**
* Provides abstract methods that the OEM needs to implement to enable extensions for image capture.
*/
-public interface ImageCaptureExtenderImpl extends ExtenderStateListener, ExtensionAvailability {
+public interface ImageCaptureExtenderImpl extends ExtenderStateListener {
+ /**
+ * Indicates whether the extension is supported on the device.
+ *
+ * @param cameraId The camera2 id string of the camera.
+ * @param cameraCharacteristics The {@link CameraCharacteristics} of the camera.
+ * @return true if the extension is supported, otherwise false
+ */
+ boolean isExtensionAvailable(String cameraId, CameraCharacteristics cameraCharacteristics);
+
/**
* Initializes the extender to be used with the specified camera.
*
diff --git a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/PreviewExtenderImpl.java b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/PreviewExtenderImpl.java
index 215ddc4..cfe2995 100644
--- a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/PreviewExtenderImpl.java
+++ b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/PreviewExtenderImpl.java
@@ -22,7 +22,7 @@
/**
* Provides abstract methods that the OEM needs to implement to enable extensions in the preview.
*/
-public interface PreviewExtenderImpl extends ExtenderStateListener, ExtensionAvailability {
+public interface PreviewExtenderImpl extends ExtenderStateListener {
/** The different types of the preview processing. */
enum ProcessorType {
/** Processor which only updates the {@link CaptureStageImpl}. */
@@ -34,6 +34,15 @@
}
/**
+ * Indicates whether the extension is supported on the device.
+ *
+ * @param cameraId The camera2 id string of the camera.
+ * @param cameraCharacteristics The {@link CameraCharacteristics} of the camera.
+ * @return true if the extension is supported, otherwise false
+ */
+ boolean isExtensionAvailable(String cameraId, CameraCharacteristics cameraCharacteristics);
+
+ /**
* Initializes the extender to be used with the specified camera.
*
* <p>This should be called before any other method on the extender. The exception is {@link
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/res/drawable/car_card_ripple_background.xml b/car/core/res/drawable/car_card_ripple_background.xml
index ca20e0f..51e3ef9 100644
--- a/car/core/res/drawable/car_card_ripple_background.xml
+++ b/car/core/res/drawable/car_card_ripple_background.xml
@@ -17,7 +17,9 @@
<ripple
xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/car_card_ripple_background">
- <item
- android:id="@android:id/mask"
- android:drawable="@android:color/white" />
+ <item android:id="@android:id/mask">
+ <shape android:shape="rectangle">
+ <solid android:color="@android:color/white" />
+ </shape>
+ </item>
</ripple>
diff --git a/car/core/res/drawable/car_card_ripple_background_dark.xml b/car/core/res/drawable/car_card_ripple_background_dark.xml
index 880ff7a..031d495 100644
--- a/car/core/res/drawable/car_card_ripple_background_dark.xml
+++ b/car/core/res/drawable/car_card_ripple_background_dark.xml
@@ -17,7 +17,9 @@
<ripple
xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/car_card_ripple_background_dark">
- <item
- android:id="@android:id/mask"
- android:drawable="@android:color/white" />
+ <item android:id="@android:id/mask">
+ <shape android:shape="rectangle">
+ <solid android:color="@android:color/white" />
+ </shape>
+ </item>
</ripple>
diff --git a/car/core/res/drawable/car_card_ripple_background_light.xml b/car/core/res/drawable/car_card_ripple_background_light.xml
index 5d4f2c6..a0fb41f 100644
--- a/car/core/res/drawable/car_card_ripple_background_light.xml
+++ b/car/core/res/drawable/car_card_ripple_background_light.xml
@@ -17,7 +17,9 @@
<ripple
xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/car_card_ripple_background_light">
- <item
- android:id="@android:id/mask"
- android:drawable="@android:color/white" />
+ <item android:id="@android:id/mask">
+ <shape android:shape="rectangle">
+ <solid android:color="@android:color/white" />
+ </shape>
+ </item>
</ripple>
diff --git a/car/core/res/layout/car_alert_dialog.xml b/car/core/res/layout/car_alert_dialog.xml
index e0ae46d..bb32134 100644
--- a/car/core/res/layout/car_alert_dialog.xml
+++ b/car/core/res/layout/car_alert_dialog.xml
@@ -83,13 +83,13 @@
<!-- Margins for buttons set dynamically. -->
<Button
- android:id="@+id/positive_button"
+ android:id="@+id/negative_button"
android:layout_width="wrap_content"
android:visibility="gone"
style="?attr/dialogButtonStyle" />
<Button
- android:id="@+id/negative_button"
+ android:id="@+id/positive_button"
android:layout_width="wrap_content"
android:visibility="gone"
style="?attr/dialogButtonStyle" />
diff --git a/car/core/res/values/styles.xml b/car/core/res/values/styles.xml
index b9d089f..ea9f5de 100644
--- a/car/core/res/values/styles.xml
+++ b/car/core/res/values/styles.xml
@@ -393,7 +393,7 @@
<!-- The style for borderless buttons. -->
<style name="Widget.Car.Button.Borderless.Colored" parent="Widget.MaterialComponents.Button.TextButton">
- <item name="android:background">?android:attr/selectableItemBackgroundBorderless</item>
+ <item name="android:background">?android:attr/selectableItemBackground</item>
<item name="android:fadingEdgeLength">@dimen/car_button_fading_edge_length</item>
<item name="android:textAppearance">@style/TextAppearance.Car.Body3.Medium</item>
<item name="android:ellipsize">none</item>
diff --git a/car/core/src/main/java/androidx/car/app/CarAlertDialog.java b/car/core/src/main/java/androidx/car/app/CarAlertDialog.java
index 9b7d908..b3110c0 100644
--- a/car/core/src/main/java/androidx/car/app/CarAlertDialog.java
+++ b/car/core/src/main/java/androidx/car/app/CarAlertDialog.java
@@ -234,13 +234,13 @@
// If both buttons are visible, then there needs to be spacing between them.
if ((mPositiveButton.getVisibility() == View.VISIBLE
&& mNegativeButton.getVisibility() == View.VISIBLE)) {
- int extraSpacingOffset = CarDialogUtil.calculateExtraButtonSpace(mPositiveButton) / 2;
- positiveButtonLayoutParams.setMarginStart(buttonOffset - extraSpacingOffset);
- positiveButtonLayoutParams.setMarginEnd(mButtonSpacing);
- mPositiveButton.requestLayout();
-
- negativeButtonLayoutParams.setMarginStart(mButtonSpacing);
+ int extraSpacingOffset = CarDialogUtil.calculateExtraButtonSpace(mNegativeButton) / 2;
+ negativeButtonLayoutParams.setMarginStart(buttonOffset - extraSpacingOffset);
+ negativeButtonLayoutParams.setMarginEnd(mButtonSpacing);
mNegativeButton.requestLayout();
+
+ positiveButtonLayoutParams.setMarginStart(mButtonSpacing);
+ mPositiveButton.requestLayout();
} else if (mPositiveButton.getVisibility() == View.VISIBLE) {
int extraSpacingOffset = CarDialogUtil.calculateExtraButtonSpace(mPositiveButton) / 2;
positiveButtonLayoutParams.setMarginStart(buttonOffset - extraSpacingOffset);
diff --git a/car/moderator/build.gradle b/car/moderator/build.gradle
index 4d19dbd..bd7370d 100644
--- a/car/moderator/build.gradle
+++ b/car/moderator/build.gradle
@@ -26,7 +26,7 @@
dependencies {
api("androidx.annotation:annotation:1.1.0")
- api("androidx.core:core:1.1.0-rc01")
+ api("androidx.core:core:1.1.0")
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
androidTestImplementation(ANDROIDX_TEST_CORE)
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/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/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..d5e819f 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
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/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/java/androidx/compose/benchmark/ComposeBenchmark.kt b/compose/compose-runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/benchmark/ComposeBenchmark.kt
index 61625db..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 android.widget.FrameLayout
-import androidx.benchmark.BenchmarkRule
-import androidx.benchmark.measureRepeated
import androidx.compose.Composable
-import androidx.compose.Compose
-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.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.Rule
+import org.junit.Ignore
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()
@@ -152,6 +137,7 @@
@UiThreadTest
@Test
+ @Ignore("Disabled as it appears to not do anything")
fun benchmark_realworld4_mid_recompose() {
val model = androidx.compose.benchmark.realworld4.createSampleData()
measureRecompose {
@@ -164,53 +150,6 @@
}
}
- private fun measureCompose(block: @Composable() () -> Unit) {
- benchmarkRule.measureRepeated {
- val root = runWithTimingDisabled {
- val root = FrameLayout(activityRule.activity)
- activityRule.activity.setContentView(root)
-
- root
- }
-
- root.setContent {
- block()
- }
-
- runWithTimingDisabled {
- Compose.disposeComposition(root)
- }
- }
- }
-
- private fun measureRecompose(block: RecomposeReceiver.() -> Unit) {
- val receiver = RecomposeReceiver()
- receiver.block()
- var activeComposer: Composer<*>? = null
-
- val root = FrameLayout(activityRule.activity)
- activityRule.activity.setContentView(root)
- root.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)
- }
-
- Compose.disposeComposition(root)
- }
}
private val color = Color.Yellow
@@ -242,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) {
@@ -256,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..8d72e02
--- /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) {
+ benchmarkRule.measureRepeated {
+ val activity = activityRule.activity
+
+ activity.setContent {
+ block()
+ }
+
+ runWithTimingDisabled {
+ 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..6525704
--- /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 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..7712cee
--- /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 children: @Composable() () -> Unit) {
+ if (vertical) {
+ Column { children() }
+ } else {
+ Row { children() }
+ }
+}
+
+@Composable
+fun Container(@Children 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/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..6b1d4b7
--- /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 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/compose/compose-runtime/src/main/java/androidx/compose/Applier.kt b/compose/compose-runtime/src/main/java/androidx/compose/Applier.kt
index a2090a3..f94b0a5 100644
--- a/compose/compose-runtime/src/main/java/androidx/compose/Applier.kt
+++ b/compose/compose-runtime/src/main/java/androidx/compose/Applier.kt
@@ -16,8 +16,6 @@
package androidx.compose
-import java.util.Stack
-
/**
* An adapter that performs tree based operations on some tree startNode N without requiring a specific base type for N
*/
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/Composer.kt b/compose/compose-runtime/src/main/java/androidx/compose/Composer.kt
index b5b4175..e5fb5ec 100644
--- a/compose/compose-runtime/src/main/java/androidx/compose/Composer.kt
+++ b/compose/compose-runtime/src/main/java/androidx/compose/Composer.kt
@@ -16,8 +16,6 @@
package androidx.compose
-import java.util.Stack
-
internal typealias Change<N> = (
applier: Applier<N>,
slots: SlotWriter,
@@ -474,8 +472,8 @@
if (insertedProviders.isNotEmpty()) {
var current = insertedProviders.size - 1
while (current >= 0) {
- val element = insertedProviders[current]
- if (element is Ambient.Holder<*> && element.ambient === key) {
+ val element = insertedProviders.peek(current)
+ if (element.ambient === key) {
@Suppress("UNCHECKED_CAST")
return element.value as? T ?: key.defaultValue
}
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/main/java/androidx/compose/Immutable.kt b/compose/compose-runtime/src/main/java/androidx/compose/Immutable.kt
new file mode 100644
index 0000000..a490564
--- /dev/null
+++ b/compose/compose-runtime/src/main/java/androidx/compose/Immutable.kt
@@ -0,0 +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
+
+@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/Key.kt b/compose/compose-runtime/src/main/java/androidx/compose/Key.kt
index 5557f00..00c0414 100644
--- a/compose/compose-runtime/src/main/java/androidx/compose/Key.kt
+++ b/compose/compose-runtime/src/main/java/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 children: @Composable() () -> Unit) {
children()
}
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/Model.kt b/compose/compose-runtime/src/main/java/androidx/compose/Model.kt
index 6a53af3..9682b69 100644
--- a/compose/compose-runtime/src/main/java/androidx/compose/Model.kt
+++ b/compose/compose-runtime/src/main/java/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/StableMarker.kt b/compose/compose-runtime/src/main/java/androidx/compose/StableMarker.kt
new file mode 100644
index 0000000..1f8111d
--- /dev/null
+++ b/compose/compose-runtime/src/main/java/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/IntStack.kt b/compose/compose-runtime/src/main/java/androidx/compose/Stack.kt
similarity index 73%
rename from compose/compose-runtime/src/main/java/androidx/compose/IntStack.kt
rename to compose/compose-runtime/src/main/java/androidx/compose/Stack.kt
index 340c424..b586297 100644
--- a/compose/compose-runtime/src/main/java/androidx/compose/IntStack.kt
+++ b/compose/compose-runtime/src/main/java/androidx/compose/Stack.kt
@@ -16,6 +16,20 @@
package androidx.compose
+internal class Stack<T> {
+ private val backing = ArrayList<T>()
+
+ val size: Int get() = backing.size
+
+ fun push(value: T) = backing.add(value)
+ fun pop(): T = backing.removeAt(size - 1)
+ fun peek(): T = backing.get(size - 1)
+ fun peek(index: Int): T = backing.get(index)
+ fun isEmpty() = backing.isEmpty()
+ fun isNotEmpty() = !isEmpty()
+ fun clear() = backing.clear()
+}
+
internal class IntStack {
private var slots = IntArray(10)
private var tos = 0
@@ -35,4 +49,4 @@
fun isEmpty() = tos == 0
fun isNotEmpty() = tos != 0
fun clear() { tos = 0 }
-}
\ No newline at end of file
+}
diff --git a/compose/compose-runtime/src/main/java/androidx/compose/ViewComposer.kt b/compose/compose-runtime/src/main/java/androidx/compose/ViewComposer.kt
index 8b6d1f1..90ba367 100644
--- a/compose/compose-runtime/src/main/java/androidx/compose/ViewComposer.kt
+++ b/compose/compose-runtime/src/main/java/androidx/compose/ViewComposer.kt
@@ -20,7 +20,6 @@
import android.view.View
import android.view.ViewGroup
import androidx.compose.adapters.getViewAdapterIfExists
-import java.util.Stack
class ViewAdapters {
private val adapters = mutableListOf<(parent: Any, child: Any) -> Any?>()
@@ -269,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
@@ -279,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 {
@@ -302,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 {
@@ -326,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
}
diff --git a/compose/compose-runtime/src/test/java/androidx/compose/CompositionTests.kt b/compose/compose-runtime/src/test/java/androidx/compose/CompositionTests.kt
index d19d59a..6a58b69 100644
--- a/compose/compose-runtime/src/test/java/androidx/compose/CompositionTests.kt
+++ b/compose/compose-runtime/src/test/java/androidx/compose/CompositionTests.kt
@@ -706,6 +706,94 @@
}
}
+ fun testInvalidationAfterRemoval() {
+ val slReportReports = object {}
+ var recomposeLois: (() -> Unit)? = null
+ val key = 0
+
+ class Reporter : ViewComponent() {
+ var report: Report? = null
+
+ override fun compose() {
+ val r = report
+ if (r != null) {
+ if (r.from == "Lois" || r.to == "Lois") recomposeLois = { recompose() }
+ cc.startGroup(key)
+ text(r.from)
+ text("reports to")
+ text(r.to)
+ cc.endGroup()
+ } else {
+ text("no report to report")
+ }
+ }
+ }
+
+ fun MockViewComposition.reportsReport(
+ reports: Iterable<Report>,
+ include: (report: Report) -> Boolean
+ ) {
+ linear {
+ repeat(of = reports) { report ->
+ if (include(report)) {
+ call(
+ slReportReports,
+ { Reporter() },
+ { set(report) { this.report = it } },
+ { it() }
+ )
+ }
+ }
+ }
+ }
+
+ val r = Report("Lois", "Perry")
+ val reports = listOf(
+ jim_reports_to_sally,
+ rob_reports_to_alice,
+ clark_reports_to_lois,
+ r
+ )
+ val all: (report: Report) -> Boolean = { true }
+ val notLois: (report: Report) -> Boolean = { it.from != "Lois" && it.to != "Lois" }
+ val composer = compose {
+ reportsReport(reports, all)
+ }.apply { applyChanges() }
+
+ validate(composer.root) {
+ linear {
+ reportsTo(jim_reports_to_sally)
+ reportsTo(rob_reports_to_alice)
+ reportsTo(clark_reports_to_lois)
+ reportsTo(r)
+ }
+ }
+
+ compose(composer, expectChanges = true) {
+ reportsReport(reports, notLois)
+ }
+
+ validate(composer.root) {
+ linear {
+ reportsTo(jim_reports_to_sally)
+ reportsTo(rob_reports_to_alice)
+ }
+ }
+
+ // Invalidate Lois which is now removed.
+ recomposeLois?.let { it() }
+
+ composer.recompose()
+ composer.applyChanges()
+
+ validate(composer.root) {
+ linear {
+ reportsTo(jim_reports_to_sally)
+ reportsTo(rob_reports_to_alice)
+ }
+ }
+ }
+
// remember()
fun testSimpleRemember() {
diff --git a/compose/compose-runtime/src/test/java/androidx/compose/ViewComposerTests.kt b/compose/compose-runtime/src/test/java/androidx/compose/ViewComposerTests.kt
index 8f3ebb8b..9acdbcc 100644
--- a/compose/compose-runtime/src/test/java/androidx/compose/ViewComposerTests.kt
+++ b/compose/compose-runtime/src/test/java/androidx/compose/ViewComposerTests.kt
@@ -234,7 +234,7 @@
// @Composable
// fun PhoneView(phone: Phone) {
// phoneCalled++
- // <TextView text="..." />
+ // TextView(text="...")
// }
fun PhoneView(phone: Phone) {
phoneCalled++
@@ -253,11 +253,11 @@
}.then { _ ->
assertEquals(1, phoneCalled)
}.then { _ ->
- assertEquals(2, phoneCalled)
+ assertEquals(1, phoneCalled)
phone = Phone("124", "456", "7890")
}.then { _ ->
- assertEquals(3, phoneCalled)
+ assertEquals(2, phoneCalled)
}
}
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/content/build.gradle b/content/build.gradle
index cafb452..4a27186 100644
--- a/content/build.gradle
+++ b/content/build.gradle
@@ -26,7 +26,7 @@
dependencies {
api("androidx.annotation:annotation:1.1.0")
- api("androidx.core:core:1.1.0-rc01")
+ api("androidx.core:core:1.1.0")
implementation("androidx.collection:collection:1.1.0")
androidTestImplementation(JUNIT)
diff --git a/coordinatorlayout/build.gradle b/coordinatorlayout/build.gradle
index ae2a733..6050e84 100644
--- a/coordinatorlayout/build.gradle
+++ b/coordinatorlayout/build.gradle
@@ -11,7 +11,7 @@
dependencies {
api("androidx.annotation:annotation:1.1.0")
// TODO: change to 1.1.0-alpha04 after release
- api("androidx.core:core:1.1.0-rc01")
+ api("androidx.core:core:1.1.0")
implementation("androidx.collection:collection:1.0.0")
api("androidx.customview:customview:1.0.0")
diff --git a/core/core/api/restricted_1.2.0-alpha03.txt b/core/core/api/restricted_1.2.0-alpha03.txt
index 0693bb2..138fb9f 100644
--- a/core/core/api/restricted_1.2.0-alpha03.txt
+++ b/core/core/api/restricted_1.2.0-alpha03.txt
@@ -140,13 +140,13 @@
@RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class ComponentActivity extends android.app.Activity implements androidx.core.view.KeyEventDispatcher.Component {
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 @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 {
diff --git a/core/core/api/restricted_current.txt b/core/core/api/restricted_current.txt
index 0693bb2..138fb9f 100644
--- a/core/core/api/restricted_current.txt
+++ b/core/core/api/restricted_current.txt
@@ -140,13 +140,13 @@
@RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class ComponentActivity extends android.app.Activity implements androidx.core.view.KeyEventDispatcher.Component {
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 @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 {
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/androidTest/java/androidx/core/widget/NestedScrollViewNestedScrollingA11yScrollTest.java b/core/core/src/androidTest/java/androidx/core/widget/NestedScrollViewNestedScrollingA11yScrollTest.java
new file mode 100644
index 0000000..af6e3bc
--- /dev/null
+++ b/core/core/src/androidTest/java/androidx/core/widget/NestedScrollViewNestedScrollingA11yScrollTest.java
@@ -0,0 +1,336 @@
+/*
+ * 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.core.widget;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.graphics.drawable.GradientDrawable;
+import android.support.v4.BaseInstrumentationTestCase;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.test.R;
+import androidx.core.view.NestedScrollingParent3;
+import androidx.core.view.ViewCompat;
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+
+import org.jetbrains.annotations.NotNull;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Large integration test that verifies that NestedScrollView participates in nested scrolling when
+ * scrolling occurs due to a11y actions.
+ */
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class NestedScrollViewNestedScrollingA11yScrollTest extends
+ BaseInstrumentationTestCase<TestContentViewActivity> {
+
+ private static final int CHILD_HEIGHT = 300;
+ private static final int NSV_HEIGHT = 100;
+ private static final int PARENT_HEIGHT = 300;
+ private static final int WIDTH = 400;
+ // A11Y scroll only scrolls the height of the NestedScrollView at max.
+ private static final int TOTAL_SCROLL_OFFSET = 100;
+
+ private NestedScrollView mNestedScrollView;
+ private NestedScrollingSpyView mParent;
+
+ public NestedScrollViewNestedScrollingA11yScrollTest() {
+ super(TestContentViewActivity.class);
+ }
+
+ @Before
+ public void setup() throws Throwable {
+ Context context = mActivityTestRule.getActivity();
+
+ View child = new View(context);
+ child.setMinimumWidth(WIDTH);
+ child.setMinimumHeight(CHILD_HEIGHT);
+ child.setLayoutParams(new ViewGroup.LayoutParams(WIDTH, CHILD_HEIGHT));
+ child.setBackgroundDrawable(
+ new GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM,
+ new int[]{0xFFFF0000, 0xFF00FF00}));
+
+ mNestedScrollView = new NestedScrollView(context);
+ mNestedScrollView.setLayoutParams(new ViewGroup.LayoutParams(WIDTH, NSV_HEIGHT));
+ mNestedScrollView.setBackgroundColor(0xFF0000FF);
+ mNestedScrollView.addView(child);
+
+ mParent = spy(new NestedScrollingSpyView(context));
+ mParent.setLayoutParams(new ViewGroup.LayoutParams(WIDTH, PARENT_HEIGHT));
+ mParent.setBackgroundColor(0xFF0000FF);
+ mParent.addView(mNestedScrollView);
+
+ // Attach to activity and wait for layouts.
+ final TestContentView testContentView =
+ mActivityTestRule.getActivity().findViewById(R.id.testContentView);
+ testContentView.expectLayouts(1);
+ mActivityTestRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ testContentView.addView(mParent);
+ }
+ });
+ testContentView.awaitLayouts(2);
+ }
+
+ @Test
+ public void a11yActionScrollForward_fullyParticipatesInNestedScrolling() throws Throwable {
+ a11yScroll_fullyParticipatesInNestedScrolling(true);
+ }
+
+ @Test
+ public void a11yActionScrollBackward_fullyParticipatesInNestedScrolling() throws Throwable {
+ a11yScroll_fullyParticipatesInNestedScrolling(false);
+ }
+
+ private void a11yScroll_fullyParticipatesInNestedScrolling(final boolean forward)
+ throws Throwable {
+
+ final CountDownLatch countDownLatch = new CountDownLatch(2);
+ mActivityTestRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ doReturn(true).when(mParent).onStartNestedScroll(any(View.class), any(View.class),
+ anyInt(), anyInt());
+
+ int action;
+ if (forward) {
+ action = AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD;
+ } else {
+ mNestedScrollView.scrollTo(0, 200);
+ action = AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD;
+ }
+
+ mParent.mOnStopNestedScrollListener =
+ new NestedScrollingSpyView.OnStopNestedScrollListener() {
+ @Override
+ public void onStopNestedScroll(int type) {
+ if (type == ViewCompat.TYPE_NON_TOUCH) {
+ countDownLatch.countDown();
+ }
+ }
+ };
+
+ mNestedScrollView.setOnScrollChangeListener(
+ new NestedScrollView.OnScrollChangeListener() {
+ @Override
+ public void onScrollChange(NestedScrollView v, int scrollX, int scrollY,
+ int oldScrollX, int oldScrollY) {
+ if (scrollY == TOTAL_SCROLL_OFFSET) {
+ countDownLatch.countDown();
+ }
+ }
+ });
+
+ ViewCompat.performAccessibilityAction(mNestedScrollView, action, null);
+ }
+ });
+ assertThat(countDownLatch.await(2, TimeUnit.SECONDS), is(true));
+
+ // Verify that none of the following TYPE_TOUCH nested scrolling methods are called.
+ verify(mParent, never()).onStartNestedScroll(mNestedScrollView, mNestedScrollView,
+ ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_TOUCH);
+ verify(mParent, never()).onNestedScrollAccepted(mNestedScrollView, mNestedScrollView,
+ ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_TOUCH);
+ verify(mParent, never()).onNestedPreScroll(eq(mNestedScrollView), anyInt(), anyInt(),
+ any(int[].class), eq(ViewCompat.TYPE_TOUCH));
+ verify(mParent, never()).onNestedScroll(eq(mNestedScrollView), anyInt(), anyInt(),
+ anyInt(), anyInt(), eq(ViewCompat.TYPE_TOUCH), any(int[].class));
+ verify(mParent, never()).onNestedPreFling(eq(mNestedScrollView), anyFloat(),
+ anyFloat());
+ verify(mParent, never()).onNestedFling(eq(mNestedScrollView), anyFloat(), anyFloat(),
+ eq(true));
+ verify(mParent, never()).onStopNestedScroll(mNestedScrollView, ViewCompat.TYPE_TOUCH);
+
+ // Verify all of the following TYPE_NON_TOUCH nested scrolling methods are called
+ verify(mParent, atLeastOnce()).onStartNestedScroll(mNestedScrollView, mNestedScrollView,
+ ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_NON_TOUCH);
+ verify(mParent, atLeastOnce()).onNestedScrollAccepted(mNestedScrollView, mNestedScrollView,
+ ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_NON_TOUCH);
+ verify(mParent, atLeastOnce()).onNestedPreScroll(eq(mNestedScrollView), anyInt(), anyInt(),
+ any(int[].class), eq(ViewCompat.TYPE_NON_TOUCH));
+ verify(mParent, atLeastOnce()).onNestedScroll(eq(mNestedScrollView), anyInt(), anyInt(),
+ anyInt(),
+ anyInt(), eq(ViewCompat.TYPE_NON_TOUCH), any(int[].class));
+ verify(mParent, atLeastOnce()).onStopNestedScroll(mNestedScrollView,
+ ViewCompat.TYPE_NON_TOUCH);
+ }
+
+ public static class NestedScrollingSpyView extends FrameLayout implements
+ NestedScrollingParent3 {
+
+ public OnStopNestedScrollListener mOnStopNestedScrollListener;
+
+ public NestedScrollingSpyView(Context context) {
+ super(context);
+ }
+
+ @Override
+ public boolean onStartNestedScroll(@NonNull View child, @NonNull View target, int axes,
+ int type) {
+ return false;
+ }
+
+ @Override
+ public void onNestedScrollAccepted(@NonNull View child, @NonNull View target, int axes,
+ int type) {
+
+ }
+
+ @Override
+ public void onStopNestedScroll(@NonNull View target, int type) {
+ if (mOnStopNestedScrollListener != null) {
+ mOnStopNestedScrollListener.onStopNestedScroll(type);
+ }
+ }
+
+ @Override
+ public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed,
+ int dxUnconsumed, int dyUnconsumed, int type) {
+
+ }
+
+ @Override
+ public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed,
+ int type) {
+
+ }
+
+ @Override
+ public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed,
+ int dxUnconsumed, int dyUnconsumed, int type, @Nullable int[] consumed) {
+ }
+
+ @Override
+ public void setNestedScrollingEnabled(boolean enabled) {
+
+ }
+
+ @Override
+ public boolean isNestedScrollingEnabled() {
+ return false;
+ }
+
+ @Override
+ public boolean startNestedScroll(int axes) {
+ return false;
+ }
+
+ @Override
+ public void stopNestedScroll() {
+
+ }
+
+ @Override
+ public boolean hasNestedScrollingParent() {
+ return false;
+ }
+
+ @Override
+ public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,
+ int dyUnconsumed, int[] offsetInWindow) {
+ return false;
+ }
+
+ @Override
+ public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed,
+ int[] offsetInWindow) {
+ return false;
+ }
+
+ @Override
+ public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
+ return false;
+ }
+
+ @Override
+ public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
+ return false;
+ }
+
+ @Override
+ public boolean onStartNestedScroll(@NotNull View child, @NotNull View target, int axes) {
+ return false;
+ }
+
+ @Override
+ public void onNestedScrollAccepted(@NotNull View child, @NotNull View target, int axes) {
+
+ }
+
+ @Override
+ public void onStopNestedScroll(@NotNull View target) {
+
+ }
+
+ @Override
+ public void onNestedScroll(@NotNull View target, int dxConsumed, int dyConsumed,
+ int dxUnconsumed,
+ int dyUnconsumed) {
+
+ }
+
+ @Override
+ public void onNestedPreScroll(@NotNull View target, int dx, int dy,
+ @NotNull int[] consumed) {
+
+ }
+
+ @Override
+ public boolean onNestedFling(@NotNull View target, float velocityX, float velocityY,
+ boolean consumed) {
+ return false;
+ }
+
+ @Override
+ public boolean onNestedPreFling(@NotNull View target, float velocityX, float velocityY) {
+ return false;
+ }
+
+ @Override
+ public int getNestedScrollAxes() {
+ return 0;
+ }
+
+ interface OnStopNestedScrollListener {
+ void onStopNestedScroll(int type);
+ }
+ }
+}
diff --git a/core/core/src/androidTest/java/androidx/core/widget/NestedScrollViewScrollingTest.java b/core/core/src/androidTest/java/androidx/core/widget/NestedScrollViewNestedScrollingFlingTest.java
similarity index 78%
rename from core/core/src/androidTest/java/androidx/core/widget/NestedScrollViewScrollingTest.java
rename to core/core/src/androidTest/java/androidx/core/widget/NestedScrollViewNestedScrollingFlingTest.java
index e9aa6bf..443f2ed 100644
--- a/core/core/src/androidTest/java/androidx/core/widget/NestedScrollViewScrollingTest.java
+++ b/core/core/src/androidTest/java/androidx/core/widget/NestedScrollViewNestedScrollingFlingTest.java
@@ -34,13 +34,13 @@
import android.os.SystemClock;
import android.support.v4.BaseInstrumentationTestCase;
import android.view.View;
+import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.test.R;
-import androidx.core.view.NestedScrollingChild3;
import androidx.core.view.NestedScrollingParent3;
import androidx.core.view.ViewCompat;
import androidx.test.core.app.ApplicationProvider;
@@ -49,6 +49,8 @@
import androidx.testutils.Direction;
import androidx.testutils.SimpleGestureGeneratorKt;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -56,12 +58,12 @@
import java.util.concurrent.TimeUnit;
/**
- * Large integration tests that verify correct {@link NestedScrollView} scrolling behavior,
- * including interaction with nested scrolling.
-*/
+ * Large integration tests that verify correct NestedScrollView flinging behavior related to
+ * nested scrolling.
+ */
@RunWith(AndroidJUnit4.class)
@LargeTest
-public class NestedScrollViewScrollingTest extends
+public class NestedScrollViewNestedScrollingFlingTest extends
BaseInstrumentationTestCase<TestContentViewActivity> {
private static final int CHILD_HEIGHT = 800;
@@ -76,10 +78,45 @@
private View mChild;
private NestedScrollingSpyView mParent;
- public NestedScrollViewScrollingTest() {
+ public NestedScrollViewNestedScrollingFlingTest() {
super(TestContentViewActivity.class);
}
+ @Before
+ public void setup() throws Throwable {
+ Context context = mActivityTestRule.getActivity();
+
+ mChild = new View(context);
+ mChild.setMinimumWidth(WIDTH);
+ mChild.setMinimumHeight(CHILD_HEIGHT);
+ mChild.setLayoutParams(new ViewGroup.LayoutParams(WIDTH, CHILD_HEIGHT));
+ mChild.setBackgroundDrawable(
+ new GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM,
+ new int[]{0xFFFF0000, 0xFF00FF00}));
+
+ mNestedScrollView = new NestedScrollView(context);
+ mNestedScrollView.setLayoutParams(new ViewGroup.LayoutParams(WIDTH, NSV_HEIGHT));
+ mNestedScrollView.setBackgroundColor(0xFF0000FF);
+ mNestedScrollView.addView(mChild);
+
+ mParent = spy(new NestedScrollingSpyView(context));
+ mParent.setLayoutParams(new ViewGroup.LayoutParams(WIDTH, PARENT_HEIGHT));
+ mParent.setBackgroundColor(0xFF0000FF);
+ mParent.addView(mNestedScrollView);
+
+ // Attach to activity and wait for layouts.
+ final TestContentView testContentView =
+ mActivityTestRule.getActivity().findViewById(R.id.testContentView);
+ testContentView.expectLayouts(1);
+ mActivityTestRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ testContentView.addView(mParent);
+ }
+ });
+ testContentView.awaitLayouts(2);
+ }
+
@Test
public void onNestedFling_consumedIsFalse_animatesScroll() throws Throwable {
onNestedFling_consumeParamDeterminesScroll(false, true);
@@ -92,9 +129,6 @@
private void onNestedFling_consumeParamDeterminesScroll(final boolean consumeParamValue,
final boolean scrolls) throws Throwable {
- setup();
- attachToActivity();
-
final Context context = ApplicationProvider.getApplicationContext();
final int targetVelocity = (int)
Math.ceil(SimpleGestureGeneratorKt.generateFlingData(context).getVelocity() * 1000);
@@ -131,9 +165,6 @@
private void uiFlings_parentPreFlingReturnDeterminesNestedScrollViewsFling(
final boolean returnValue, final boolean scrolls) throws Throwable {
- setup();
- attachToActivity();
-
final CountDownLatch countDownLatch = new CountDownLatch(1);
mActivityTestRule.runOnUiThread(new Runnable() {
@Override
@@ -171,11 +202,18 @@
}
@Test
- public void uiFling_fullyParticipatesInNestedScrolling() throws Throwable {
- setup();
- attachToActivity();
+ public void uiFling_flingDoesNotReachEnd_fullyParticipatesInNestedScrolling() throws Throwable {
+ uiFling_fullyParticipatesInNestedScrolling(false);
+ }
- final CountDownLatch countDownLatch = new CountDownLatch(2);
+ @Test
+ public void uiFling_flingReachesEnd_fullyParticipatesInNestedScrolling() throws Throwable {
+ uiFling_fullyParticipatesInNestedScrolling(true);
+ }
+
+ private void uiFling_fullyParticipatesInNestedScrolling(final boolean hardFling)
+ throws Throwable {
+ final CountDownLatch countDownLatch = new CountDownLatch(hardFling ? 2 : 1);
mActivityTestRule.runOnUiThread(new Runnable() {
@Override
public void run() {
@@ -192,23 +230,37 @@
}
};
- mNestedScrollView.setOnScrollChangeListener(
- new NestedScrollView.OnScrollChangeListener() {
- @Override
- public void onScrollChange(NestedScrollView v, int scrollX, int scrollY,
- int oldScrollX, int oldScrollY) {
- if (scrollY == TOTAL_SCROLL_DISTANCE) {
- countDownLatch.countDown();
+ if (hardFling) {
+ mNestedScrollView.setOnScrollChangeListener(
+ new NestedScrollView.OnScrollChangeListener() {
+ @Override
+ public void onScrollChange(NestedScrollView v, int scrollX,
+ int scrollY,
+ int oldScrollX, int oldScrollY) {
+ if (scrollY == TOTAL_SCROLL_DISTANCE) {
+ countDownLatch.countDown();
+ }
}
- }
- });
+ });
+ }
+
+ ViewConfiguration configuration =
+ ViewConfiguration.get(mActivityTestRule.getActivity());
+
+ float velocity;
+ if (hardFling) {
+ velocity = configuration.getScaledMaximumFlingVelocity() * .9f;
+ } else {
+ velocity = configuration.getScaledMinimumFlingVelocity() * 1.1f;
+ }
SimpleGestureGeneratorKt.simulateFling(
mNestedScrollView,
SystemClock.uptimeMillis(),
ORIGIN_X_Y,
ORIGIN_X_Y,
- Direction.UP);
+ Direction.UP,
+ velocity);
}
});
assertThat(countDownLatch.await(2, TimeUnit.SECONDS), is(true));
@@ -220,11 +272,11 @@
ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_TOUCH);
verify(mParent, atLeastOnce()).onNestedPreScroll(eq(mNestedScrollView), anyInt(), anyInt(),
any(int[].class), eq(ViewCompat.TYPE_TOUCH));
- verify(mParent, atLeastOnce()).onNestedScroll(eq(mNestedScrollView), anyInt(), anyInt(),
+ verify(mParent, atLeastOnce()).onNestedScroll(eq(mNestedScrollView), anyInt(), anyInt(),
anyInt(), anyInt(), eq(ViewCompat.TYPE_TOUCH), any(int[].class));
- verify(mParent, atLeastOnce()).onNestedPreFling(eq(mNestedScrollView), anyFloat(),
+ verify(mParent, atLeastOnce()).onNestedPreFling(eq(mNestedScrollView), anyFloat(),
anyFloat());
- verify(mParent, atLeastOnce()).onNestedFling(eq(mNestedScrollView), anyFloat(), anyFloat(),
+ verify(mParent, atLeastOnce()).onNestedFling(eq(mNestedScrollView), anyFloat(), anyFloat(),
eq(true));
verify(mParent, atLeastOnce()).onStopNestedScroll(mNestedScrollView, ViewCompat.TYPE_TOUCH);
@@ -235,7 +287,7 @@
ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_NON_TOUCH);
verify(mParent, atLeastOnce()).onNestedPreScroll(eq(mNestedScrollView), anyInt(), anyInt(),
any(int[].class), eq(ViewCompat.TYPE_NON_TOUCH));
- verify(mParent, atLeastOnce()).onNestedScroll(eq(mNestedScrollView), anyInt(), anyInt(),
+ verify(mParent, atLeastOnce()).onNestedScroll(eq(mNestedScrollView), anyInt(), anyInt(),
anyInt(),
anyInt(), eq(ViewCompat.TYPE_NON_TOUCH), any(int[].class));
verify(mParent, atLeastOnce()).onStopNestedScroll(mNestedScrollView,
@@ -244,8 +296,6 @@
@Test
public void fling_fullyParticipatesInNestedScrolling() throws Throwable {
- setup();
- attachToActivity();
final Context context = ApplicationProvider.getApplicationContext();
final int targetVelocity = (int)
Math.ceil(SimpleGestureGeneratorKt.generateFlingData(context).getVelocity() * 1000);
@@ -290,7 +340,7 @@
ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_NON_TOUCH);
verify(mParent, atLeastOnce()).onNestedPreScroll(eq(mNestedScrollView), anyInt(), anyInt(),
any(int[].class), eq(ViewCompat.TYPE_NON_TOUCH));
- verify(mParent, atLeastOnce()).onNestedScroll(eq(mNestedScrollView), anyInt(), anyInt(),
+ verify(mParent, atLeastOnce()).onNestedScroll(eq(mNestedScrollView), anyInt(), anyInt(),
anyInt(),
anyInt(), eq(ViewCompat.TYPE_NON_TOUCH), any(int[].class));
verify(mParent, atLeastOnce()).onStopNestedScroll(mNestedScrollView,
@@ -303,90 +353,15 @@
anyInt(), eq(ViewCompat.TYPE_TOUCH));
verify(mParent, never()).onNestedPreScroll(any(View.class), anyInt(), anyInt(),
any(int[].class), eq(ViewCompat.TYPE_TOUCH));
- verify(mParent, never()).onNestedScroll(any(View.class), anyInt(), anyInt(), anyInt(),
+ verify(mParent, never()).onNestedScroll(any(View.class), anyInt(), anyInt(), anyInt(),
anyInt(), eq(ViewCompat.TYPE_TOUCH), any(int[].class));
- verify(mParent, never()).onNestedPreFling(any(View.class), anyFloat(), anyFloat());
- verify(mParent, never()).onNestedFling(any(View.class), anyFloat(), anyFloat(),
+ verify(mParent, never()).onNestedPreFling(any(View.class), anyFloat(), anyFloat());
+ verify(mParent, never()).onNestedFling(any(View.class), anyFloat(), anyFloat(),
anyBoolean());
verify(mParent, never()).onStopNestedScroll(any(View.class), eq(ViewCompat.TYPE_TOUCH));
}
- @Test
- public void smoothScrollBy_scrollsEntireDistanceIncludingMargins() throws Throwable {
- setup();
- setChildMargins(20, 30);
- attachToActivity();
-
- final CountDownLatch countDownLatch = new CountDownLatch(1);
- final int expectedTarget = TOTAL_SCROLL_DISTANCE + 20 + 30;
- final int scrollDistance = TOTAL_SCROLL_DISTANCE + 20 + 30;
- mActivityTestRule.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- mNestedScrollView.setOnScrollChangeListener(
- new NestedScrollView.OnScrollChangeListener() {
- @Override
- public void onScrollChange(NestedScrollView v, int scrollX, int scrollY,
- int oldScrollX, int oldScrollY) {
- if (scrollY == expectedTarget) {
- countDownLatch.countDown();
- }
- }
- });
- mNestedScrollView.smoothScrollBy(0, scrollDistance);
- }
- });
- assertThat(countDownLatch.await(2, TimeUnit.SECONDS), is(true));
-
- assertThat(mNestedScrollView.getScrollY(), is(expectedTarget));
- }
-
- private void setup() {
- Context context = mActivityTestRule.getActivity();
-
- mChild = new View(context);
- mChild.setMinimumWidth(WIDTH);
- mChild.setMinimumHeight(CHILD_HEIGHT);
- mChild.setLayoutParams(new ViewGroup.LayoutParams(WIDTH, CHILD_HEIGHT));
- mChild.setBackgroundDrawable(
- new GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM,
- new int[]{0xFFFF0000, 0xFF00FF00}));
-
- mNestedScrollView = new NestedScrollView(context);
- mNestedScrollView.setLayoutParams(new ViewGroup.LayoutParams(WIDTH, NSV_HEIGHT));
- mNestedScrollView.setBackgroundColor(0xFF0000FF);
- mNestedScrollView.addView(mChild);
-
- mParent = spy(new NestedScrollingSpyView(context));
- mParent.setLayoutParams(new ViewGroup.LayoutParams(WIDTH, PARENT_HEIGHT));
- mParent.setBackgroundColor(0xFF0000FF);
- mParent.addView(mNestedScrollView);
- }
-
- @SuppressWarnings("SameParameterValue")
- private void setChildMargins(int top, int bottom) {
- ViewGroup.LayoutParams currentLayoutParams = mChild.getLayoutParams();
- NestedScrollView.LayoutParams childLayoutParams = new NestedScrollView.LayoutParams(
- currentLayoutParams.width, currentLayoutParams.height);
- childLayoutParams.topMargin = top;
- childLayoutParams.bottomMargin = bottom;
- mChild.setLayoutParams(childLayoutParams);
- }
-
- private void attachToActivity() throws Throwable {
- final TestContentView testContentView =
- mActivityTestRule.getActivity().findViewById(R.id.testContentView);
- testContentView.expectLayouts(1);
- mActivityTestRule.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- testContentView.addView(mParent);
- }
- });
- testContentView.awaitLayouts(2);
- }
-
- public static class NestedScrollingSpyView extends FrameLayout implements NestedScrollingChild3,
+ public static class NestedScrollingSpyView extends FrameLayout implements
NestedScrollingParent3 {
public OnStopNestedScrollListener mOnStopNestedScrollListener;
@@ -427,44 +402,11 @@
}
@Override
- public boolean startNestedScroll(int axes, int type) {
- return false;
- }
-
- @Override
- public void stopNestedScroll(int type) {
-
- }
-
- @Override
- public boolean hasNestedScrollingParent(int type) {
- return false;
- }
-
- @Override
- public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,
- int dyUnconsumed, @Nullable int[] offsetInWindow, int type) {
- return false;
- }
-
- @Override
- public boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed,
- @Nullable int[] offsetInWindow, int type) {
- return false;
- }
-
- @Override
public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed, int type, @Nullable int[] consumed) {
}
@Override
- public void dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,
- int dyUnconsumed, @Nullable int[] offsetInWindow, int type,
- @NonNull int[] consumed) {
- }
-
- @Override
public void setNestedScrollingEnabled(boolean enabled) {
}
@@ -512,40 +454,40 @@
}
@Override
- public boolean onStartNestedScroll(@NonNull View child, @NonNull View target, int axes) {
+ public boolean onStartNestedScroll(@NotNull View child, @NotNull View target, int axes) {
return false;
}
@Override
- public void onNestedScrollAccepted(@NonNull View child, @NonNull View target, int axes) {
+ public void onNestedScrollAccepted(@NotNull View child, @NotNull View target, int axes) {
}
@Override
- public void onStopNestedScroll(@NonNull View target) {
+ public void onStopNestedScroll(@NotNull View target) {
}
@Override
- public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed,
+ public void onNestedScroll(@NotNull View target, int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed) {
}
@Override
- public void onNestedPreScroll(
- @NonNull View target, int dx, int dy, @NonNull int[] consumed) {
+ public void onNestedPreScroll(@NotNull View target, int dx, int dy,
+ @NotNull int[] consumed) {
}
@Override
- public boolean onNestedFling(@NonNull View target, float velocityX, float velocityY,
+ public boolean onNestedFling(@NotNull View target, float velocityX, float velocityY,
boolean consumed) {
return false;
}
@Override
- public boolean onNestedPreFling(@NonNull View target, float velocityX, float velocityY) {
+ public boolean onNestedPreFling(@NotNull View target, float velocityX, float velocityY) {
return false;
}
diff --git a/core/core/src/androidTest/java/androidx/core/widget/NestedScrollViewNestedScrollingFlingTest.kt b/core/core/src/androidTest/java/androidx/core/widget/NestedScrollViewNestedScrollingFlingVelocityTest.kt
similarity index 88%
rename from core/core/src/androidTest/java/androidx/core/widget/NestedScrollViewNestedScrollingFlingTest.kt
rename to core/core/src/androidTest/java/androidx/core/widget/NestedScrollViewNestedScrollingFlingVelocityTest.kt
index c954b90..8c43046 100644
--- a/core/core/src/androidTest/java/androidx/core/widget/NestedScrollViewNestedScrollingFlingTest.kt
+++ b/core/core/src/androidTest/java/androidx/core/widget/NestedScrollViewNestedScrollingFlingVelocityTest.kt
@@ -22,7 +22,6 @@
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
-import androidx.core.view.NestedScrollingChild3
import androidx.core.view.NestedScrollingParent3
import androidx.test.core.app.ApplicationProvider
import androidx.test.filters.LargeTest
@@ -43,7 +42,7 @@
*/
@RunWith(Parameterized::class)
@LargeTest
-class NestedScrollViewNestedScrollingFlingTest(
+class NestedScrollViewNestedScrollingFlingVelocityTest(
private val fingerDirectionUp: Boolean,
private val parentIntercepts: Boolean,
private val preScrollConsumption: Int,
@@ -127,16 +126,15 @@
var flungVelocity = 0
- constructor(context: Context) : super(context) {}
+ constructor(context: Context) : super(context)
- constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {}
+ constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(
context: Context,
attrs: AttributeSet?,
defStyleAttr: Int
- ) : super(context, attrs, defStyleAttr) {
- }
+ ) : super(context, attrs, defStyleAttr)
override fun fling(velocityY: Int) {
flungVelocity = velocityY
@@ -144,7 +142,7 @@
}
inner class NestedScrollingParent(context: Context) : FrameLayout(context),
- NestedScrollingChild3, NestedScrollingParent3 {
+ NestedScrollingParent3 {
var preScrollY: Int = 0
var postScrollY: Int = 0
@@ -193,37 +191,6 @@
scrollBy(0, toScrollY)
}
- override fun startNestedScroll(axes: Int, type: Int): Boolean {
- return false
- }
-
- override fun stopNestedScroll(type: Int) {}
-
- override fun hasNestedScrollingParent(type: Int): Boolean {
- return false
- }
-
- override fun dispatchNestedScroll(
- dxConsumed: Int,
- dyConsumed: Int,
- dxUnconsumed: Int,
- dyUnconsumed: Int,
- offsetInWindow: IntArray?,
- type: Int
- ): Boolean {
- return false
- }
-
- override fun dispatchNestedPreScroll(
- dx: Int,
- dy: Int,
- consumed: IntArray?,
- offsetInWindow: IntArray?,
- type: Int
- ): Boolean {
- return false
- }
-
override fun onNestedScroll(
target: View,
dxConsumed: Int,
@@ -241,17 +208,6 @@
scrollBy(0, toScrollY)
}
- override fun dispatchNestedScroll(
- dxConsumed: Int,
- dyConsumed: Int,
- dxUnconsumed: Int,
- dyUnconsumed: Int,
- offsetInWindow: IntArray?,
- type: Int,
- consumed: IntArray
- ) {
- }
-
override fun setNestedScrollingEnabled(enabled: Boolean) {}
override fun isNestedScrollingEnabled(): Boolean {
diff --git a/core/core/src/androidTest/java/androidx/core/widget/NestedScrollViewSmoothScrollByTest.java b/core/core/src/androidTest/java/androidx/core/widget/NestedScrollViewSmoothScrollByTest.java
new file mode 100644
index 0000000..af6da42
--- /dev/null
+++ b/core/core/src/androidTest/java/androidx/core/widget/NestedScrollViewSmoothScrollByTest.java
@@ -0,0 +1,129 @@
+/*
+ * 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.core.widget;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.content.Context;
+import android.graphics.drawable.GradientDrawable;
+import android.support.v4.BaseInstrumentationTestCase;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.core.test.R;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Large integration tests that verify correct NestedScrollView scrolling behavior,
+ * including interaction with nested scrolling.
+*/
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class NestedScrollViewSmoothScrollByTest extends
+ BaseInstrumentationTestCase<TestContentViewActivity> {
+
+ private static final int CHILD_HEIGHT = 800;
+ private static final int NSV_HEIGHT = 400;
+ private static final int WIDTH = 400;
+ private static final int TOTAL_SCROLL_DISTANCE = CHILD_HEIGHT - NSV_HEIGHT;
+
+ private NestedScrollView mNestedScrollView;
+ private View mChild;
+
+ public NestedScrollViewSmoothScrollByTest() {
+ super(TestContentViewActivity.class);
+ }
+
+ @Before
+ public void setup() {
+ Context context = mActivityTestRule.getActivity();
+
+ mChild = new View(context);
+ mChild.setMinimumWidth(WIDTH);
+ mChild.setMinimumHeight(CHILD_HEIGHT);
+ mChild.setLayoutParams(new ViewGroup.LayoutParams(WIDTH, CHILD_HEIGHT));
+ mChild.setBackgroundDrawable(
+ new GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM,
+ new int[]{0xFFFF0000, 0xFF00FF00}));
+
+ mNestedScrollView = new NestedScrollView(context);
+ mNestedScrollView.setLayoutParams(new ViewGroup.LayoutParams(WIDTH, NSV_HEIGHT));
+ mNestedScrollView.setBackgroundColor(0xFF0000FF);
+ mNestedScrollView.addView(mChild);
+ }
+
+ @Test
+ public void smoothScrollBy_scrollsEntireDistanceIncludingMargins() throws Throwable {
+ setChildMargins(20, 30);
+ attachToActivity();
+
+ final CountDownLatch countDownLatch = new CountDownLatch(1);
+ final int expectedTarget = TOTAL_SCROLL_DISTANCE + 20 + 30;
+ final int scrollDistance = TOTAL_SCROLL_DISTANCE + 20 + 30;
+ mActivityTestRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mNestedScrollView.setOnScrollChangeListener(
+ new NestedScrollView.OnScrollChangeListener() {
+ @Override
+ public void onScrollChange(NestedScrollView v, int scrollX, int scrollY,
+ int oldScrollX, int oldScrollY) {
+ if (scrollY == expectedTarget) {
+ countDownLatch.countDown();
+ }
+ }
+ });
+ mNestedScrollView.smoothScrollBy(0, scrollDistance);
+ }
+ });
+ assertThat(countDownLatch.await(2, TimeUnit.SECONDS), is(true));
+
+ assertThat(mNestedScrollView.getScrollY(), is(expectedTarget));
+ }
+
+ @SuppressWarnings("SameParameterValue")
+ private void setChildMargins(int top, int bottom) {
+ ViewGroup.LayoutParams currentLayoutParams = mChild.getLayoutParams();
+ NestedScrollView.LayoutParams childLayoutParams = new NestedScrollView.LayoutParams(
+ currentLayoutParams.width, currentLayoutParams.height);
+ childLayoutParams.topMargin = top;
+ childLayoutParams.bottomMargin = bottom;
+ mChild.setLayoutParams(childLayoutParams);
+ }
+
+ private void attachToActivity() throws Throwable {
+ final TestContentView testContentView =
+ mActivityTestRule.getActivity().findViewById(R.id.testContentView);
+ testContentView.expectLayouts(1);
+ mActivityTestRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ testContentView.addView(mNestedScrollView);
+ }
+ });
+ testContentView.awaitLayouts(2);
+ }
+}
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..90d61c4 100644
--- a/core/core/src/main/java/androidx/core/app/ComponentActivity.java
+++ b/core/core/src/main/java/androidx/core/app/ComponentActivity.java
@@ -43,6 +43,7 @@
*
* <p>Note that these objects are not retained across configuration changes</p>
*/
+ @SuppressWarnings("deprecation")
private SimpleArrayMap<Class<? extends ExtraData>, ExtraData> mExtraDataMap =
new SimpleArrayMap<>();
@@ -54,8 +55,11 @@
*
* @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);
}
@@ -65,9 +69,11 @@
*
* @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);
}
@@ -101,8 +107,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/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/core/core/src/main/java/androidx/core/widget/NestedScrollView.java b/core/core/src/main/java/androidx/core/widget/NestedScrollView.java
index c30a780..fff1cd1 100644
--- a/core/core/src/main/java/androidx/core/widget/NestedScrollView.java
+++ b/core/core/src/main/java/androidx/core/widget/NestedScrollView.java
@@ -945,7 +945,7 @@
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
- if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
+ if ((Math.abs(initialVelocity) >= mMinimumVelocity)) {
if (!dispatchNestedPreFling(0, -initialVelocity)) {
dispatchNestedFling(0, -initialVelocity, true);
fling(-initialVelocity);
@@ -1410,6 +1410,17 @@
* @param dy the number of pixels to scroll by on the Y axis
*/
public final void smoothScrollBy(int dx, int dy) {
+ smoothScrollBy(dx, dy, false);
+ }
+
+ /**
+ * Like {@link View#scrollBy}, but scroll smoothly instead of immediately.
+ *
+ * @param dx the number of pixels to scroll by on the X axis
+ * @param dy the number of pixels to scroll by on the Y axis
+ * @param withNestedScrolling whether to include nested scrolling operations.
+ */
+ private void smoothScrollBy(int dx, int dy, boolean withNestedScrolling) {
if (getChildCount() == 0) {
// Nothing to do.
return;
@@ -1424,7 +1435,7 @@
final int maxY = Math.max(0, childSize - parentSpace);
dy = Math.max(0, Math.min(scrollY + dy, maxY)) - scrollY;
mScroller.startScroll(getScrollX(), scrollY, 0, dy);
- runAnimatedScroll(false);
+ runAnimatedScroll(withNestedScrolling);
} else {
if (!mScroller.isFinished()) {
abortAnimatedScroll();
@@ -1441,7 +1452,19 @@
* @param y the position where to scroll on the Y axis
*/
public final void smoothScrollTo(int x, int y) {
- smoothScrollBy(x - getScrollX(), y - getScrollY());
+ smoothScrollTo(x, y, false);
+ }
+
+ /**
+ * Like {@link #scrollTo}, but scroll smoothly instead of immediately.
+ *
+ * @param x the position where to scroll on the X axis
+ * @param y the position where to scroll on the Y axis
+ * @param withNestedScrolling whether to include nested scrolling operations.
+ */
+ // This should be considered private, it is package private to avoid a synthetic ancestor.
+ void smoothScrollTo(int x, int y, boolean withNestedScrolling) {
+ smoothScrollBy(x - getScrollX(), y - getScrollY(), withNestedScrolling);
}
/**
@@ -1592,6 +1615,8 @@
if (!mScroller.isFinished()) {
ViewCompat.postInvalidateOnAnimation(this);
+ } else {
+ stopNestedScroll(ViewCompat.TYPE_NON_TOUCH);
}
}
@@ -2088,7 +2113,7 @@
final int targetScrollY = Math.min(nsvHost.getScrollY() + viewportHeight,
nsvHost.getScrollRange());
if (targetScrollY != nsvHost.getScrollY()) {
- nsvHost.smoothScrollTo(0, targetScrollY);
+ nsvHost.smoothScrollTo(0, targetScrollY, true);
return true;
}
}
@@ -2098,7 +2123,7 @@
- nsvHost.getPaddingTop();
final int targetScrollY = Math.max(nsvHost.getScrollY() - viewportHeight, 0);
if (targetScrollY != nsvHost.getScrollY()) {
- nsvHost.smoothScrollTo(0, targetScrollY);
+ nsvHost.smoothScrollTo(0, targetScrollY, true);
return true;
}
}
diff --git a/dynamic-animation/build.gradle b/dynamic-animation/build.gradle
index fe7bd58..6ad3e31 100644
--- a/dynamic-animation/build.gradle
+++ b/dynamic-animation/build.gradle
@@ -9,7 +9,7 @@
}
dependencies {
- api("androidx.core:core:1.1.0-rc01")
+ api("androidx.core:core:1.1.0")
implementation("androidx.collection:collection:1.1.0")
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
diff --git a/emoji/core/build.gradle b/emoji/core/build.gradle
index 0b811cb..4e13514 100644
--- a/emoji/core/build.gradle
+++ b/emoji/core/build.gradle
@@ -22,7 +22,7 @@
// treats this as local jar and package it inside the aar.
api files(configurations.repackage)
- api("androidx.core:core:1.1.0-rc01")
+ api("androidx.core:core:1.1.0")
implementation(project(':collection:collection'))
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
diff --git a/exifinterface/OWNERS b/exifinterface/OWNERS
index 02f6139..d4d2d29 100644
--- a/exifinterface/OWNERS
+++ b/exifinterface/OWNERS
@@ -1,4 +1,5 @@
gyumin@google.com
hdmoon@google.com
jinpark@google.com
-sungsoo@google.com
\ No newline at end of file
+klhyun@google.com
+sungsoo@google.com
diff --git a/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java b/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java
index 66045dc..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[] {
@@ -4451,7 +4451,10 @@
FileInputStream in = null;
FileOutputStream out = null;
- File originalFile = new File(mFilename);
+ File originalFile = null;
+ if (mFilename != null) {
+ originalFile = new File(mFilename);
+ }
File tempFile = null;
try {
// Move the original file to temporary file.
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/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 865189d..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);
@@ -295,10 +297,10 @@
}
public static interface FragmentManager.BackStackEntry {
- method public CharSequence? getBreadCrumbShortTitle();
- method @StringRes public int getBreadCrumbShortTitleRes();
- method public CharSequence? getBreadCrumbTitle();
- method @StringRes public int getBreadCrumbTitleRes();
+ method @Deprecated public CharSequence? getBreadCrumbShortTitle();
+ method @Deprecated @StringRes public int getBreadCrumbShortTitleRes();
+ method @Deprecated public CharSequence? getBreadCrumbTitle();
+ method @Deprecated @StringRes public int getBreadCrumbTitleRes();
method public int getId();
method public String? getName();
}
@@ -383,10 +385,10 @@
method public androidx.fragment.app.FragmentTransaction replace(@IdRes int, androidx.fragment.app.Fragment, String?);
method public androidx.fragment.app.FragmentTransaction runOnCommit(Runnable);
method @Deprecated public androidx.fragment.app.FragmentTransaction setAllowOptimization(boolean);
- method public androidx.fragment.app.FragmentTransaction setBreadCrumbShortTitle(@StringRes int);
- method public androidx.fragment.app.FragmentTransaction setBreadCrumbShortTitle(CharSequence?);
- method public androidx.fragment.app.FragmentTransaction setBreadCrumbTitle(@StringRes int);
- method public androidx.fragment.app.FragmentTransaction setBreadCrumbTitle(CharSequence?);
+ method @Deprecated public androidx.fragment.app.FragmentTransaction setBreadCrumbShortTitle(@StringRes int);
+ method @Deprecated public androidx.fragment.app.FragmentTransaction setBreadCrumbShortTitle(CharSequence?);
+ method @Deprecated public androidx.fragment.app.FragmentTransaction setBreadCrumbTitle(@StringRes int);
+ method @Deprecated public androidx.fragment.app.FragmentTransaction setBreadCrumbTitle(CharSequence?);
method public androidx.fragment.app.FragmentTransaction setCustomAnimations(@AnimatorRes @AnimRes int, @AnimatorRes @AnimRes int);
method public androidx.fragment.app.FragmentTransaction setCustomAnimations(@AnimatorRes @AnimRes int, @AnimatorRes @AnimRes int, @AnimatorRes @AnimRes int, @AnimatorRes @AnimRes int);
method public androidx.fragment.app.FragmentTransaction setMaxLifecycle(androidx.fragment.app.Fragment, androidx.lifecycle.Lifecycle.State);
diff --git a/fragment/fragment/api/current.txt b/fragment/fragment/api/current.txt
index 865189d..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);
@@ -295,10 +297,10 @@
}
public static interface FragmentManager.BackStackEntry {
- method public CharSequence? getBreadCrumbShortTitle();
- method @StringRes public int getBreadCrumbShortTitleRes();
- method public CharSequence? getBreadCrumbTitle();
- method @StringRes public int getBreadCrumbTitleRes();
+ method @Deprecated public CharSequence? getBreadCrumbShortTitle();
+ method @Deprecated @StringRes public int getBreadCrumbShortTitleRes();
+ method @Deprecated public CharSequence? getBreadCrumbTitle();
+ method @Deprecated @StringRes public int getBreadCrumbTitleRes();
method public int getId();
method public String? getName();
}
@@ -383,10 +385,10 @@
method public androidx.fragment.app.FragmentTransaction replace(@IdRes int, androidx.fragment.app.Fragment, String?);
method public androidx.fragment.app.FragmentTransaction runOnCommit(Runnable);
method @Deprecated public androidx.fragment.app.FragmentTransaction setAllowOptimization(boolean);
- method public androidx.fragment.app.FragmentTransaction setBreadCrumbShortTitle(@StringRes int);
- method public androidx.fragment.app.FragmentTransaction setBreadCrumbShortTitle(CharSequence?);
- method public androidx.fragment.app.FragmentTransaction setBreadCrumbTitle(@StringRes int);
- method public androidx.fragment.app.FragmentTransaction setBreadCrumbTitle(CharSequence?);
+ method @Deprecated public androidx.fragment.app.FragmentTransaction setBreadCrumbShortTitle(@StringRes int);
+ method @Deprecated public androidx.fragment.app.FragmentTransaction setBreadCrumbShortTitle(CharSequence?);
+ method @Deprecated public androidx.fragment.app.FragmentTransaction setBreadCrumbTitle(@StringRes int);
+ method @Deprecated public androidx.fragment.app.FragmentTransaction setBreadCrumbTitle(CharSequence?);
method public androidx.fragment.app.FragmentTransaction setCustomAnimations(@AnimatorRes @AnimRes int, @AnimatorRes @AnimRes int);
method public androidx.fragment.app.FragmentTransaction setCustomAnimations(@AnimatorRes @AnimRes int, @AnimatorRes @AnimRes int, @AnimatorRes @AnimRes int, @AnimatorRes @AnimRes int);
method public androidx.fragment.app.FragmentTransaction setMaxLifecycle(androidx.fragment.app.Fragment, androidx.lifecycle.Lifecycle.State);
diff --git a/fragment/fragment/api/restricted_1.2.0-alpha02.txt b/fragment/fragment/api/restricted_1.2.0-alpha02.txt
index e2cf936..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);
@@ -301,10 +303,10 @@
}
public static interface FragmentManager.BackStackEntry {
- method public CharSequence? getBreadCrumbShortTitle();
- method @StringRes public int getBreadCrumbShortTitleRes();
- method public CharSequence? getBreadCrumbTitle();
- method @StringRes public int getBreadCrumbTitleRes();
+ method @Deprecated public CharSequence? getBreadCrumbShortTitle();
+ method @Deprecated @StringRes public int getBreadCrumbShortTitleRes();
+ method @Deprecated public CharSequence? getBreadCrumbTitle();
+ method @Deprecated @StringRes public int getBreadCrumbTitleRes();
method public int getId();
method public String? getName();
}
@@ -389,10 +391,10 @@
method public androidx.fragment.app.FragmentTransaction replace(@IdRes int, androidx.fragment.app.Fragment, String?);
method public androidx.fragment.app.FragmentTransaction runOnCommit(Runnable);
method @Deprecated public androidx.fragment.app.FragmentTransaction setAllowOptimization(boolean);
- method public androidx.fragment.app.FragmentTransaction setBreadCrumbShortTitle(@StringRes int);
- method public androidx.fragment.app.FragmentTransaction setBreadCrumbShortTitle(CharSequence?);
- method public androidx.fragment.app.FragmentTransaction setBreadCrumbTitle(@StringRes int);
- method public androidx.fragment.app.FragmentTransaction setBreadCrumbTitle(CharSequence?);
+ method @Deprecated public androidx.fragment.app.FragmentTransaction setBreadCrumbShortTitle(@StringRes int);
+ method @Deprecated public androidx.fragment.app.FragmentTransaction setBreadCrumbShortTitle(CharSequence?);
+ method @Deprecated public androidx.fragment.app.FragmentTransaction setBreadCrumbTitle(@StringRes int);
+ method @Deprecated public androidx.fragment.app.FragmentTransaction setBreadCrumbTitle(CharSequence?);
method public androidx.fragment.app.FragmentTransaction setCustomAnimations(@AnimatorRes @AnimRes int, @AnimatorRes @AnimRes int);
method public androidx.fragment.app.FragmentTransaction setCustomAnimations(@AnimatorRes @AnimRes int, @AnimatorRes @AnimRes int, @AnimatorRes @AnimRes int, @AnimatorRes @AnimRes int);
method public androidx.fragment.app.FragmentTransaction setMaxLifecycle(androidx.fragment.app.Fragment, androidx.lifecycle.Lifecycle.State);
diff --git a/fragment/fragment/api/restricted_current.txt b/fragment/fragment/api/restricted_current.txt
index e2cf936..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);
@@ -301,10 +303,10 @@
}
public static interface FragmentManager.BackStackEntry {
- method public CharSequence? getBreadCrumbShortTitle();
- method @StringRes public int getBreadCrumbShortTitleRes();
- method public CharSequence? getBreadCrumbTitle();
- method @StringRes public int getBreadCrumbTitleRes();
+ method @Deprecated public CharSequence? getBreadCrumbShortTitle();
+ method @Deprecated @StringRes public int getBreadCrumbShortTitleRes();
+ method @Deprecated public CharSequence? getBreadCrumbTitle();
+ method @Deprecated @StringRes public int getBreadCrumbTitleRes();
method public int getId();
method public String? getName();
}
@@ -389,10 +391,10 @@
method public androidx.fragment.app.FragmentTransaction replace(@IdRes int, androidx.fragment.app.Fragment, String?);
method public androidx.fragment.app.FragmentTransaction runOnCommit(Runnable);
method @Deprecated public androidx.fragment.app.FragmentTransaction setAllowOptimization(boolean);
- method public androidx.fragment.app.FragmentTransaction setBreadCrumbShortTitle(@StringRes int);
- method public androidx.fragment.app.FragmentTransaction setBreadCrumbShortTitle(CharSequence?);
- method public androidx.fragment.app.FragmentTransaction setBreadCrumbTitle(@StringRes int);
- method public androidx.fragment.app.FragmentTransaction setBreadCrumbTitle(CharSequence?);
+ method @Deprecated public androidx.fragment.app.FragmentTransaction setBreadCrumbShortTitle(@StringRes int);
+ method @Deprecated public androidx.fragment.app.FragmentTransaction setBreadCrumbShortTitle(CharSequence?);
+ method @Deprecated public androidx.fragment.app.FragmentTransaction setBreadCrumbTitle(@StringRes int);
+ method @Deprecated public androidx.fragment.app.FragmentTransaction setBreadCrumbTitle(CharSequence?);
method public androidx.fragment.app.FragmentTransaction setCustomAnimations(@AnimatorRes @AnimRes int, @AnimatorRes @AnimRes int);
method public androidx.fragment.app.FragmentTransaction setCustomAnimations(@AnimatorRes @AnimRes int, @AnimatorRes @AnimRes int, @AnimatorRes @AnimRes int, @AnimatorRes @AnimRes int);
method public androidx.fragment.app.FragmentTransaction setMaxLifecycle(androidx.fragment.app.Fragment, androidx.lifecycle.Lifecycle.State);
diff --git a/fragment/fragment/build.gradle b/fragment/fragment/build.gradle
index c199145..777cb59 100644
--- a/fragment/fragment/build.gradle
+++ b/fragment/fragment/build.gradle
@@ -18,13 +18,14 @@
dependencies {
api("androidx.annotation:annotation:1.1.0")
- api("androidx.core:core:1.1.0-rc01")
+ api("androidx.core:core:1.1.0")
api("androidx.collection:collection:1.1.0")
api("androidx.viewpager:viewpager:1.0.0")
api("androidx.loader:loader:1.0.0")
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/src/androidTest/java/androidx/fragment/app/FragmentContainerViewTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentContainerViewTest.kt
index 670ea6c..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,20 +35,24 @@
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
import org.junit.runner.RunWith
+import java.util.concurrent.CountDownLatch
@MediumTest
@RunWith(AndroidJUnit4::class)
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
@@ -98,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)
@@ -117,6 +119,8 @@
insets
}
+ childView.setTag(R.id.fragment_container_view_tag, Fragment())
+
parentView.addView(childView)
parentView.dispatchApplyWindowInsets(sentInsets)
@@ -124,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)
@@ -145,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)
@@ -165,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)
@@ -184,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)
@@ -203,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)
@@ -222,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()
@@ -242,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.
@@ -275,6 +304,12 @@
activityRule.waitForExecution()
val frag1View = fragment1.mView as ChildView
+ // wait for the first draw to finish
+ drawnFirstCountDownLatch.await()
+
+ // reset the first drawn view for the transaction we care about.
+ drawnFirst = null
+ drawnFirstCountDownLatch = CountDownLatch(1)
fm.beginTransaction()
.setCustomAnimations(android.R.anim.slide_in_left, android.R.anim.slide_out_right)
@@ -282,9 +317,8 @@
.commit()
activityRule.waitForExecution()
- val frag2View = fragment2.mView as ChildView
-
- assertThat(frag1View.drawTime).isLessThan(frag2View.drawTime)
+ drawnFirstCountDownLatch.await()
+ assertThat(drawnFirst!!).isEqualTo(frag1View)
}
class ChildViewFragment : StrictViewFragment() {
@@ -296,11 +330,20 @@
}
class ChildView(context: Context?) : View(context) {
- var drawTime = 0L
-
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
- drawTime = System.nanoTime()
+ setDrawnFirstView(this)
+ }
+ }
+
+ companion object {
+ var drawnFirst: View? = null
+ var drawnFirstCountDownLatch = CountDownLatch(1)
+ fun setDrawnFirstView(v: View) {
+ if (drawnFirst == null) {
+ drawnFirst = v
+ }
+ drawnFirstCountDownLatch.countDown()
}
}
}
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/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..4dc499c 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() {
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 4282de9..d376369 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,28 +137,36 @@
/**
* 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
@StringRes
int getBreadCrumbTitleRes();
/**
* 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
@StringRes
int getBreadCrumbShortTitleRes();
/**
* 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
@Nullable
CharSequence getBreadCrumbTitle();
/**
* 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
@Nullable
CharSequence getBreadCrumbShortTitle();
}
@@ -343,8 +352,6 @@
private final ArrayList<OpGenerator> mPendingActions = new ArrayList<>();
private boolean mExecutingActions;
- private int mNextFragmentIndex = 0;
-
@SuppressWarnings("WeakerAccess") /* synthetic access */
final ArrayList<Fragment> mAdded = new ArrayList<>();
final HashMap<String, Fragment> mActive = new HashMap<>();
@@ -361,9 +368,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>
@@ -749,6 +754,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
@@ -955,26 +1023,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();
@@ -1273,6 +1323,7 @@
if (f.mView != null) {
f.mInnerView = f.mView;
f.mView.setSaveFromParentEnabled(false);
+ setViewTag(f);
if (container != null) {
container.addView(f.mView);
}
@@ -2010,48 +2061,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();
}
/**
@@ -2155,6 +2166,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;
}
@@ -2290,7 +2304,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();
@@ -2824,10 +2837,10 @@
fms.mActive = active;
fms.mAdded = added;
fms.mBackStack = backStack;
+ fms.mBackStackIndex = mBackStackIndex.get();
if (mPrimaryNav != null) {
fms.mPrimaryNavActiveWho = mPrimaryNav.mWho;
}
- fms.mNextFragmentIndex = mNextFragmentIndex;
return fms;
}
@@ -2936,19 +2949,16 @@
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);
dispatchParentPrimaryNavigationFragmentChanged(mPrimaryNav);
}
- this.mNextFragmentIndex = fms.mNextFragmentIndex;
}
/**
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 969c3bc..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,8 +27,8 @@
ArrayList<FragmentState> mActive;
ArrayList<String> mAdded;
BackStackState[] mBackStack;
+ int mBackStackIndex;
String mPrimaryNavActiveWho = null;
- int mNextFragmentIndex;
public FragmentManagerState() {
}
@@ -37,8 +37,8 @@
mActive = in.createTypedArrayList(FragmentState.CREATOR);
mAdded = in.createStringArrayList();
mBackStack = in.createTypedArray(BackStackState.CREATOR);
+ mBackStackIndex = in.readInt();
mPrimaryNavActiveWho = in.readString();
- mNextFragmentIndex = in.readInt();
}
@Override
@@ -51,8 +51,8 @@
dest.writeTypedList(mActive);
dest.writeStringList(mAdded);
dest.writeTypedArray(mBackStack, flags);
+ dest.writeInt(mBackStackIndex);
dest.writeString(mPrimaryNavActiveWho);
- dest.writeInt(mNextFragmentIndex);
}
public static final Parcelable.Creator<FragmentManagerState> CREATOR
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 d161dfb..f92b54d 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,9 @@
* is on the back stack.
*
* @param res A string resource containing the title.
+ * @deprecated Store breadcrumb titles separately from fragment transactions.
*/
+ @Deprecated
@NonNull
public FragmentTransaction setBreadCrumbTitle(@StringRes int res) {
mBreadCrumbTitleRes = res;
@@ -667,7 +669,9 @@
* 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
@NonNull
public FragmentTransaction setBreadCrumbTitle(@Nullable CharSequence text) {
mBreadCrumbTitleRes = 0;
@@ -680,7 +684,9 @@
* is on the back stack.
*
* @param res A string resource containing the title.
+ * @deprecated Store breadcrumb short titles separately from fragment transactions.
*/
+ @Deprecated
@NonNull
public FragmentTransaction setBreadCrumbShortTitle(@StringRes int res) {
mBreadCrumbShortTitleRes = res;
@@ -692,7 +698,9 @@
* 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
@NonNull
public FragmentTransaction setBreadCrumbShortTitle(@Nullable CharSequence text) {
mBreadCrumbShortTitleRes = 0;
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/graphics/drawable/static/build.gradle b/graphics/drawable/static/build.gradle
index a3e453a..f5e1ba4 100644
--- a/graphics/drawable/static/build.gradle
+++ b/graphics/drawable/static/build.gradle
@@ -10,7 +10,7 @@
dependencies {
api("androidx.annotation:annotation:1.1.0")
- api("androidx.core:core:1.1.0-rc01")
+ api("androidx.core:core:1.1.0")
implementation("androidx.collection:collection:1.1.0")
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
diff --git a/gridlayout/build.gradle b/gridlayout/build.gradle
index efb7375..58ab795 100644
--- a/gridlayout/build.gradle
+++ b/gridlayout/build.gradle
@@ -10,7 +10,7 @@
dependencies {
api(ANDROIDX_ANNOTATION)
- implementation("androidx.core:core:1.1.0-rc01")
+ implementation("androidx.core:core:1.1.0")
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
androidTestImplementation(ANDROIDX_TEST_CORE)
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/1.1.0-alpha03.ignore b/leanback/api/1.1.0-alpha03.ignore
index 2a59565..cf7fad9 100644
--- a/leanback/api/1.1.0-alpha03.ignore
+++ b/leanback/api/1.1.0-alpha03.ignore
@@ -1,3 +1,7 @@
// Baseline format: 1.0
ChangedStatic: androidx.leanback.widget.ItemBridgeAdapter.ViewHolder:
Class androidx.leanback.widget.ItemBridgeAdapter.ViewHolder changed 'static' qualifier
+
+
+RemovedDeprecatedMethod: androidx.leanback.app.RowsFragment#onCreate(android.os.Bundle):
+ Removed deprecated method androidx.leanback.app.RowsFragment.onCreate(android.os.Bundle)
diff --git a/leanback/api/restricted_1.1.0-alpha03.ignore b/leanback/api/restricted_1.1.0-alpha03.ignore
index 5dbae58..c94da04 100644
--- a/leanback/api/restricted_1.1.0-alpha03.ignore
+++ b/leanback/api/restricted_1.1.0-alpha03.ignore
@@ -3,5 +3,9 @@
Class androidx.leanback.widget.ItemBridgeAdapter.ViewHolder changed 'static' qualifier
+RemovedDeprecatedMethod: androidx.leanback.app.RowsFragment#onCreate(android.os.Bundle):
+ Removed deprecated method androidx.leanback.app.RowsFragment.onCreate(android.os.Bundle)
+
+
RemovedMethod: androidx.leanback.widget.picker.DatePicker#updateDate(int, int, int, boolean):
Removed method androidx.leanback.widget.picker.DatePicker.updateDate(int,int,int,boolean)
diff --git a/leanback/build.gradle b/leanback/build.gradle
index 7588656..df32fcf 100644
--- a/leanback/build.gradle
+++ b/leanback/build.gradle
@@ -11,7 +11,7 @@
dependencies {
api("androidx.annotation:annotation:1.1.0")
api("androidx.interpolator:interpolator:1.0.0")
- api("androidx.core:core:1.1.0-rc01")
+ api("androidx.core:core:1.1.0")
implementation("androidx.collection:collection:1.0.0")
api("androidx.media:media:1.0.0")
api("androidx.fragment:fragment:1.0.0")
diff --git a/legacy/core-utils/build.gradle b/legacy/core-utils/build.gradle
index 7a331a9..c4fed24 100644
--- a/legacy/core-utils/build.gradle
+++ b/legacy/core-utils/build.gradle
@@ -9,7 +9,7 @@
dependencies {
api("androidx.annotation:annotation:1.1.0")
- api("androidx.core:core:1.1.0-rc01")
+ api("androidx.core:core:1.1.0")
api(project(":documentfile"))
api(project(":loader:loader"))
api(project(":localbroadcastmanager"))
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-livedata/api/2.1.0-beta02.txt b/lifecycle/lifecycle-livedata/api/2.1.0-beta02.txt
index 2f5616b..b9a2837 100644
--- a/lifecycle/lifecycle-livedata/api/2.1.0-beta02.txt
+++ b/lifecycle/lifecycle-livedata/api/2.1.0-beta02.txt
@@ -3,14 +3,14 @@
public class MediatorLiveData<T> extends androidx.lifecycle.MutableLiveData<T> {
ctor public MediatorLiveData();
- method @MainThread public <S> void addSource(androidx.lifecycle.LiveData<S>, androidx.lifecycle.Observer<? super S>);
- method @MainThread public <S> void removeSource(androidx.lifecycle.LiveData<S>);
+ method @MainThread public <S> void addSource(androidx.lifecycle.LiveData<S!>, androidx.lifecycle.Observer<? super S>);
+ method @MainThread public <S> void removeSource(androidx.lifecycle.LiveData<S!>);
}
public class Transformations {
- method @MainThread public static <X> androidx.lifecycle.LiveData<X> distinctUntilChanged(androidx.lifecycle.LiveData<X>);
- method @MainThread public static <X, Y> androidx.lifecycle.LiveData<Y> map(androidx.lifecycle.LiveData<X>, androidx.arch.core.util.Function<X,Y>);
- method @MainThread public static <X, Y> androidx.lifecycle.LiveData<Y> switchMap(androidx.lifecycle.LiveData<X>, androidx.arch.core.util.Function<X,androidx.lifecycle.LiveData<Y>>);
+ method @MainThread public static <X> androidx.lifecycle.LiveData<X!> distinctUntilChanged(androidx.lifecycle.LiveData<X!>);
+ method @MainThread public static <X, Y> androidx.lifecycle.LiveData<Y!> map(androidx.lifecycle.LiveData<X!>, androidx.arch.core.util.Function<X!,Y!>);
+ method @MainThread public static <X, Y> androidx.lifecycle.LiveData<Y!> switchMap(androidx.lifecycle.LiveData<X!>, androidx.arch.core.util.Function<X!,androidx.lifecycle.LiveData<Y!>!>);
}
}
diff --git a/lifecycle/lifecycle-reactivestreams/api/2.1.0-beta02.txt b/lifecycle/lifecycle-reactivestreams/api/2.1.0-beta02.txt
index f3d107a..518fc95 100644
--- a/lifecycle/lifecycle-reactivestreams/api/2.1.0-beta02.txt
+++ b/lifecycle/lifecycle-reactivestreams/api/2.1.0-beta02.txt
@@ -2,8 +2,8 @@
package androidx.lifecycle {
public final class LiveDataReactiveStreams {
- method public static <T> androidx.lifecycle.LiveData<T> fromPublisher(org.reactivestreams.Publisher<T>);
- method public static <T> org.reactivestreams.Publisher<T> toPublisher(androidx.lifecycle.LifecycleOwner, androidx.lifecycle.LiveData<T>);
+ method public static <T> androidx.lifecycle.LiveData<T!> fromPublisher(org.reactivestreams.Publisher<T!>);
+ method public static <T> org.reactivestreams.Publisher<T!> toPublisher(androidx.lifecycle.LifecycleOwner, androidx.lifecycle.LiveData<T!>);
}
}
diff --git a/lifecycle/lifecycle-viewmodel-ktx/api/2.2.0-alpha03.ignore b/lifecycle/lifecycle-viewmodel-ktx/api/2.2.0-alpha03.ignore
deleted file mode 100644
index fd00442..0000000
--- a/lifecycle/lifecycle-viewmodel-ktx/api/2.2.0-alpha03.ignore
+++ /dev/null
@@ -1,3 +0,0 @@
-// Baseline format: 1.0
-ChangedType: androidx.lifecycle.ViewModelProviderKt#get(androidx.lifecycle.ViewModelProvider):
- Method androidx.lifecycle.ViewModelProviderKt.get has changed return type from java.lang.VM to VM
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/loader/loader-ktx/src/androidTest/java/androidx/loader/app/LoaderManagerTest.kt b/loader/loader-ktx/src/androidTest/java/androidx/loader/app/LoaderManagerTest.kt
index 3a92843..ef302aa 100644
--- a/loader/loader-ktx/src/androidTest/java/androidx/loader/app/LoaderManagerTest.kt
+++ b/loader/loader-ktx/src/androidTest/java/androidx/loader/app/LoaderManagerTest.kt
@@ -20,7 +20,7 @@
import androidx.loader.app.test.LoaderOwner
import androidx.loader.content.Loader
import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.MediumTest
+import androidx.test.filters.LargeTest
import androidx.test.platform.app.InstrumentationRegistry
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -31,7 +31,7 @@
import java.util.concurrent.TimeUnit
@RunWith(AndroidJUnit4::class)
-@MediumTest
+@LargeTest
class LoaderManagerTest {
private lateinit var loaderManager: LoaderManager
diff --git a/loader/loader/src/androidTest/java/androidx/loader/content/AsyncTaskLoaderTest.java b/loader/loader/src/androidTest/java/androidx/loader/content/AsyncTaskLoaderTest.java
index adc78b4..0659891 100644
--- a/loader/loader/src/androidTest/java/androidx/loader/content/AsyncTaskLoaderTest.java
+++ b/loader/loader/src/androidTest/java/androidx/loader/content/AsyncTaskLoaderTest.java
@@ -22,7 +22,8 @@
import androidx.annotation.NonNull;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
+import androidx.test.filters.MediumTest;
+import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -33,13 +34,18 @@
import java.util.concurrent.atomic.AtomicInteger;
@RunWith(AndroidJUnit4.class)
-@SmallTest
+@MediumTest
public class AsyncTaskLoaderTest {
@Test
public void testForceLoad_runsAsyncTask() throws InterruptedException {
- TestAsyncTaskLoader loader = new TestAsyncTaskLoader(1);
- loader.forceLoad();
+ final TestAsyncTaskLoader loader = new TestAsyncTaskLoader(1);
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ loader.forceLoad();
+ }
+ });
assertTrue(loader.mLoadInBackgoundLatch.await(1, TimeUnit.SECONDS));
}
@@ -47,15 +53,20 @@
@Test
public void testForceLoad_runsOnCustomExecutor() throws InterruptedException {
final CountDownLatch executorLatch = new CountDownLatch(1);
- TestAsyncTaskLoader loader = new TestAsyncTaskLoader(1);
+ final TestAsyncTaskLoader loader = new TestAsyncTaskLoader(1);
loader.mExecutor = new Executor() {
@Override
- public void execute(Runnable command) {
+ public void execute(@NonNull Runnable command) {
executorLatch.countDown();
command.run();
}
};
- loader.forceLoad();
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ loader.forceLoad();
+ }
+ });
assertTrue(executorLatch.await(1, TimeUnit.SECONDS));
}
@@ -70,7 +81,7 @@
@Test
public void testGetExecutor_forceLoadMultipleTimes_getExecutorCalledOnce()
throws InterruptedException {
- TestAsyncTaskLoader loader = new TestAsyncTaskLoader(3);
+ final TestAsyncTaskLoader loader = new TestAsyncTaskLoader(3);
final AtomicInteger loadCount = new AtomicInteger(3);
loader.registerListener(0, new Loader.OnLoadCompleteListener<Void>() {
@Override
@@ -80,7 +91,12 @@
}
}
});
- loader.forceLoad();
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ loader.forceLoad();
+ }
+ });
assertTrue(loader.mLoadInBackgoundLatch.await(1, TimeUnit.SECONDS));
assertEquals(1, loader.mGetExecutorCallCount);
diff --git a/media/OWNERS b/media/OWNERS
index 29116e8..763e0cf 100644
--- a/media/OWNERS
+++ b/media/OWNERS
@@ -1,11 +1,6 @@
-akersten@google.com
-dwkang@google.com
gyumin@google.com
hdmoon@google.com
insun@google.com
jaewan@google.com
jinpark@google.com
-juliacr@google.com
-robertshih@google.com
sungsoo@google.com
-wjia@google.com
diff --git a/media/build.gradle b/media/build.gradle
index f0a49ef..863a93c 100644
--- a/media/build.gradle
+++ b/media/build.gradle
@@ -9,7 +9,7 @@
}
dependencies {
- api("androidx.core:core:1.1.0-rc01")
+ api("androidx.core:core:1.1.0")
implementation("androidx.collection:collection:1.0.0")
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
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..a6bf8dd 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) {
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/OWNERS b/media2/OWNERS
index 79fe538..e4d9120 100644
--- a/media2/OWNERS
+++ b/media2/OWNERS
@@ -1,13 +1,8 @@
-akersten@google.com
andrewlewis@google.com
-dwkang@google.com
gyumin@google.com
hdmoon@google.com
insun@google.com
jaewan@google.com
jinpark@google.com
-juliacr@google.com
-robertshih@google.com
sungsoo@google.com
-wjia@google.com
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 e99df48..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
*/
@@ -681,7 +681,7 @@
public abstract ListenableFuture<PlayerResult> skipToNextPlaylistItem();
/**
- * Skips to the the media item.
+ * Skips to the item in the playlist at the index.
* <p>
* The implementation must notify registered callbacks with
* {@link PlayerCallback#onCurrentMediaItemChanged(SessionPlayer, MediaItem)} when it's
@@ -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) {
}
/**
@@ -1375,15 +1383,17 @@
}
/**
- * Called when the tracks are first retrieved after media is prepared or when new tracks are
- * found during playback.
+ * Called when the tracks of the current media item is changed such as
+ * 1) when tracks of a media item become available,
+ * 2) when new tracks are found during playback, or
+ * 3) when the current media item is changed.
* <p>
* When it's called, you should invalidate previous track information and use the new
* tracks to call {@link #selectTrackInternal(SessionPlayer.TrackInfo)} or
* {@link #deselectTrackInternal(SessionPlayer.TrackInfo)}.
*
* @param player the player associated with this callback
- * @param trackInfos the list of track
+ * @param trackInfos the list of tracks. It can be empty.
* @see #getTrackInfoInternal()
* @hide
*/
diff --git a/media2/integration-tests/testapp/build.gradle b/media2/integration-tests/testapp/build.gradle
index b269e6a..ffefd37 100644
--- a/media2/integration-tests/testapp/build.gradle
+++ b/media2/integration-tests/testapp/build.gradle
@@ -30,7 +30,7 @@
implementation(project(":media2:media2-player"))
implementation(project(":media2:media2-widget"))
implementation("androidx.appcompat:appcompat:1.0.2")
- implementation("androidx.core:core:1.1.0-rc01")
+ implementation("androidx.core:core:1.1.0")
}
android {
diff --git a/media2/integration-tests/testapp/src/main/AndroidManifest.xml b/media2/integration-tests/testapp/src/main/AndroidManifest.xml
index 55024fe..bb0694d 100644
--- a/media2/integration-tests/testapp/src/main/AndroidManifest.xml
+++ b/media2/integration-tests/testapp/src/main/AndroidManifest.xml
@@ -44,7 +44,7 @@
</intent-filter>
</activity>
- <service android:name=".VideoSessionService">
+ <service android:name=".VideoSessionService" android:exported="false">
<intent-filter>
<action android:name="androidx.media2.session.MediaSessionService" />
</intent-filter>
diff --git a/media2/integration-tests/testapp/src/main/java/androidx/media2/integration/testapp/VideoPlayerActivity.java b/media2/integration-tests/testapp/src/main/java/androidx/media2/integration/testapp/VideoPlayerActivity.java
index 4e0d37e..20553f2 100644
--- a/media2/integration-tests/testapp/src/main/java/androidx/media2/integration/testapp/VideoPlayerActivity.java
+++ b/media2/integration-tests/testapp/src/main/java/androidx/media2/integration/testapp/VideoPlayerActivity.java
@@ -47,6 +47,7 @@
import androidx.media2.widget.MediaControlView;
import androidx.media2.widget.VideoView;
+import java.util.Locale;
import java.util.concurrent.Executor;
/**
@@ -172,8 +173,9 @@
mSpeed += 0.1f;
}
mMediaController.setPlaybackSpeed(mSpeed);
- Toast.makeText(this, "speed rate: " + String.format("%.2f", mSpeed), Toast.LENGTH_SHORT)
- .show();
+ Toast.makeText(this,
+ "speed rate: " + String.format(Locale.US, "%.2f", mSpeed),
+ Toast.LENGTH_SHORT).show();
}
return super.onTouchEvent(ev);
}
diff --git a/media2/integration-tests/testapp/src/main/java/androidx/media2/integration/testapp/VideoSelectorActivity.java b/media2/integration-tests/testapp/src/main/java/androidx/media2/integration/testapp/VideoSelectorActivity.java
index 123b7a0..36a69f1 100644
--- a/media2/integration-tests/testapp/src/main/java/androidx/media2/integration/testapp/VideoSelectorActivity.java
+++ b/media2/integration-tests/testapp/src/main/java/androidx/media2/integration/testapp/VideoSelectorActivity.java
@@ -290,12 +290,12 @@
}
}
- // now add the the sorted directories to the result set.
+ // now add the sorted directories to the result set.
for (VideoItem vi : dirs.values()) {
retVal.add(vi);
}
- // finally add the the sorted files to the result set.
+ // finally add the sorted files to the result set.
for (VideoItem vi : files.values()) {
retVal.add(vi);
}
diff --git a/media2/integration-tests/testapp/src/main/java/androidx/media2/integration/testapp/VideoSessionService.java b/media2/integration-tests/testapp/src/main/java/androidx/media2/integration/testapp/VideoSessionService.java
index ca7fbf4..9d513d3 100644
--- a/media2/integration-tests/testapp/src/main/java/androidx/media2/integration/testapp/VideoSessionService.java
+++ b/media2/integration-tests/testapp/src/main/java/androidx/media2/integration/testapp/VideoSessionService.java
@@ -16,6 +16,8 @@
package androidx.media2.integration.testapp;
+import static androidx.media2.common.MediaMetadata.METADATA_KEY_MEDIA_ID;
+
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
@@ -34,6 +36,7 @@
import androidx.media2.session.MediaSession;
import androidx.media2.session.MediaSessionService;
+import java.lang.ref.WeakReference;
import java.util.List;
import java.util.concurrent.Executor;
@@ -45,7 +48,6 @@
private MediaPlayer mMediaPlayer;
private MediaSession mMediaSession;
- private UriMediaItem mCurrentItem;
private AudioAttributesCompat mAudioAttributes;
@Override
@@ -106,29 +108,29 @@
public MediaItem onCreateMediaItem(@NonNull MediaSession session,
@NonNull MediaSession.ControllerInfo controller, @NonNull String mediaId) {
MediaMetadata metadata = new MediaMetadata.Builder()
- .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, mediaId)
+ .putString(METADATA_KEY_MEDIA_ID, mediaId)
.build();
- mCurrentItem = new UriMediaItem.Builder(Uri.parse(mediaId))
+ UriMediaItem currentItem = new UriMediaItem.Builder(Uri.parse(mediaId))
.setMetadata(metadata)
.build();
- MetadataExtractTask task = new MetadataExtractTask(mCurrentItem,
+ MetadataExtractTask task = new MetadataExtractTask(currentItem,
VideoSessionService.this);
task.execute();
// TODO: Temporary fix for multiple calls of setMediaItem not working properly.
// (b/135728285)
mMediaPlayer.reset();
mMediaPlayer.setAudioAttributes(mAudioAttributes);
- return mCurrentItem;
+ return currentItem;
}
}
- private class MetadataExtractTask extends AsyncTask<Void, Void, MediaMetadata> {
+ private static class MetadataExtractTask extends AsyncTask<Void, Void, MediaMetadata> {
private MediaItem mItem;
- private Context mContext;
+ private WeakReference<Context> mRefContext;
MetadataExtractTask(MediaItem mediaItem, Context context) {
mItem = mediaItem;
- mContext = context;
+ mRefContext = new WeakReference<>(context);
}
@Override
@@ -145,25 +147,23 @@
MediaMetadata extractMetadata(MediaItem mediaItem) {
MediaMetadataRetriever retriever = null;
+ Context context = mRefContext.get();
try {
if (mediaItem == null) {
return null;
- } else if (mediaItem instanceof UriMediaItem) {
+ } else if (mediaItem instanceof UriMediaItem && context != null) {
Uri uri = ((UriMediaItem) mediaItem).getUri();
retriever = new MediaMetadataRetriever();
- retriever.setDataSource(mContext, uri);
+ retriever.setDataSource(mRefContext.get(), uri);
}
} catch (IllegalArgumentException e) {
- retriever = null;
+ return mediaItem.getMetadata();
}
- // Do not extract metadata of a media item which is not the current item.
- if (mediaItem != mCurrentItem) {
- if (retriever != null) {
- retriever.release();
- }
- return null;
+ if (retriever == null) {
+ return mediaItem.getMetadata();
}
+
String title = extractString(retriever, MediaMetadataRetriever.METADATA_KEY_TITLE);
String musicArtistText = extractString(retriever,
MediaMetadataRetriever.METADATA_KEY_ARTIST);
@@ -173,14 +173,9 @@
retriever.release();
}
- // Set duration and title values as MediaMetadata for MediaControlView
- MediaMetadata.Builder builder = new MediaMetadata.Builder(mCurrentItem.getMetadata());
-
+ MediaMetadata.Builder builder = new MediaMetadata.Builder(mediaItem.getMetadata());
builder.putString(MediaMetadata.METADATA_KEY_TITLE, title);
builder.putString(MediaMetadata.METADATA_KEY_ARTIST, musicArtistText);
- builder.putString(
- MediaMetadata.METADATA_KEY_MEDIA_ID, mediaItem.getMediaId());
- builder.putLong(MediaMetadata.METADATA_KEY_PLAYABLE, 1);
builder.putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, musicAlbumBitmap);
return builder.build();
}
diff --git a/media2/integration-tests/testapp/src/main/res/layout/activity_video_selector.xml b/media2/integration-tests/testapp/src/main/res/layout/activity_video_selector.xml
index 510c3a1..99323ec 100644
--- a/media2/integration-tests/testapp/src/main/res/layout/activity_video_selector.xml
+++ b/media2/integration-tests/testapp/src/main/res/layout/activity_video_selector.xml
@@ -57,8 +57,7 @@
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
- android:layout_toLeftOf="@id/play_button"
- android:editable="true" />
+ android:layout_toLeftOf="@id/play_button" />
<ListView
android:id="@+id/select_list"
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/MediaPlayer2Test.java b/media2/player/src/androidTest/java/androidx/media2/player/MediaPlayer2Test.java
index 2f84cbd..b8fefc9 100644
--- a/media2/player/src/androidTest/java/androidx/media2/player/MediaPlayer2Test.java
+++ b/media2/player/src/androidTest/java/androidx/media2/player/MediaPlayer2Test.java
@@ -1730,8 +1730,6 @@
public void onInfo(MediaPlayer2 mp, MediaItem item, int what, int extra) {
if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
mOnPrepareCalled.signal();
- } else if (what == MediaPlayer2.MEDIA_INFO_METADATA_UPDATE) {
- mOnInfoCalled.signal();
}
}
@@ -1755,6 +1753,17 @@
assertNotNull(data.getData());
mOnSubtitleDataCalled.signal();
}
+
+ @Override
+ public void onTrackInfoChanged(@NonNull MediaPlayer2 mp,
+ @NonNull List<TrackInfo> tracks) {
+ assertNotNull(tracks);
+ if (tracks.size() < 3) {
+ // This callback can be called before tracks are available after setMediaItem.
+ return;
+ }
+ mTracksFullyFound.signal();
+ }
};
synchronized (mEventCbLock) {
mEventCallbacks.add(ecb);
@@ -1773,10 +1782,7 @@
// Closed caption tracks are in-band.
// So, those tracks will be found after processing a number of frames.
- mOnInfoCalled.waitForSignal(1500);
-
- mOnInfoCalled.reset();
- mOnInfoCalled.waitForSignal(1500);
+ assertTrue(mTracksFullyFound.waitForSignal(3000));
readTracks();
@@ -1816,8 +1822,6 @@
public void onInfo(MediaPlayer2 mp, MediaItem item, int what, int extra) {
if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
mOnPrepareCalled.signal();
- } else if (what == MediaPlayer2.MEDIA_INFO_METADATA_UPDATE) {
- mOnInfoCalled.signal();
}
}
@@ -1836,6 +1840,17 @@
assertNotNull(data.getData());
mOnSubtitleDataCalled.signal();
}
+
+ @Override
+ public void onTrackInfoChanged(@NonNull MediaPlayer2 mp,
+ @NonNull List<TrackInfo> tracks) {
+ assertNotNull(tracks);
+ if (tracks.size() < 3) {
+ // This callback can be called before tracks are available after setMediaItem.
+ return;
+ }
+ mTracksFullyFound.signal();
+ }
};
synchronized (mEventCbLock) {
mEventCallbacks.add(ecb);
@@ -1854,10 +1869,7 @@
// Closed caption tracks are in-band.
// So, those tracks will be found after processing a number of frames.
- mOnInfoCalled.waitForSignal(1500);
-
- mOnInfoCalled.reset();
- mOnInfoCalled.waitForSignal(1500);
+ assertTrue(mTracksFullyFound.waitForSignal(3000));
readTracks();
@@ -1893,8 +1905,6 @@
public void onInfo(MediaPlayer2 mp, MediaItem item, int what, int extra) {
if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
mOnPrepareCalled.signal();
- } else if (what == MediaPlayer2.MEDIA_INFO_METADATA_UPDATE) {
- mOnInfoCalled.signal();
}
}
@@ -1905,6 +1915,17 @@
mOnPlayCalled.signal();
}
}
+
+ @Override
+ public void onTrackInfoChanged(@NonNull MediaPlayer2 mp,
+ @NonNull List<TrackInfo> tracks) {
+ assertNotNull(tracks);
+ if (tracks.size() < 3) {
+ // This callback can be called before tracks are available after setMediaItem.
+ return;
+ }
+ mTracksFullyFound.signal();
+ }
};
synchronized (mEventCbLock) {
mEventCallbacks.add(ecb);
@@ -1924,10 +1945,7 @@
// The media metadata will be changed while playing since closed caption tracks are in-band
// and those tracks will be found after processing a number of frames. These tracks will be
// found within one second.
- mOnInfoCalled.waitForSignal(1500);
-
- mOnInfoCalled.reset();
- mOnInfoCalled.waitForSignal(1500);
+ assertTrue(mTracksFullyFound.waitForSignal(3000));
readTracks();
assertEquals(2, mSubtitleTrackInfos.size());
@@ -2189,8 +2207,6 @@
@Override
public void onInfo(MediaPlayer2 mp, MediaItem item, int what, int extra) {
- mOnInfoCalled.signal();
-
if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
mOnPrepareCalled.signal();
} else if (what == MediaPlayer2.MEDIA_INFO_DATA_SOURCE_END) {
diff --git a/media2/player/src/androidTest/java/androidx/media2/player/MediaPlayer2TestBase.java b/media2/player/src/androidTest/java/androidx/media2/player/MediaPlayer2TestBase.java
index 4b6da5f..524baa3 100644
--- a/media2/player/src/androidTest/java/androidx/media2/player/MediaPlayer2TestBase.java
+++ b/media2/player/src/androidTest/java/androidx/media2/player/MediaPlayer2TestBase.java
@@ -80,7 +80,7 @@
protected Monitor mOnDeselectTrackCalled = new Monitor();
protected Monitor mOnSeekCompleteCalled = new Monitor();
protected Monitor mOnCompletionCalled = new Monitor();
- protected Monitor mOnInfoCalled = new Monitor();
+ protected Monitor mTracksFullyFound = new Monitor();
protected Monitor mOnErrorCalled = new Monitor();
protected Monitor mOnMediaTimeDiscontinuityCalled = new Monitor();
protected int mCallStatus;
@@ -352,6 +352,7 @@
}
}
}
+
@Override
public void onSubtitleData(@NonNull MediaPlayer2 mp, @NonNull MediaItem item,
@NonNull TrackInfo track, @NonNull SubtitleData data) {
@@ -361,6 +362,16 @@
}
}
}
+
+ @Override
+ public void onTrackInfoChanged(@NonNull MediaPlayer2 mp,
+ @NonNull List<TrackInfo> tracks) {
+ synchronized (cbLock) {
+ for (MediaPlayer2.EventCallback ecb : ecbs) {
+ ecb.onTrackInfoChanged(mp, tracks);
+ }
+ }
+ }
});
}
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 8214453..00e85db 100644
--- a/media2/player/src/androidTest/java/androidx/media2/player/MediaPlayerTest.java
+++ b/media2/player/src/androidTest/java/androidx/media2/player/MediaPlayerTest.java
@@ -90,8 +90,7 @@
private final List<TrackInfo> mSubtitleTrackInfos = new ArrayList<>();
private TrackInfo mSelectedTrack = null;
private final Monitor mOnSubtitleDataCalled = new Monitor();
- private final Monitor mOnInfoCalled = new Monitor();
- private final Monitor mOnTrackInfoChangedCalled = new Monitor();
+ private final Monitor mTracksFullyFound = new Monitor();
private final Monitor mOnMediaTimeDiscontinuityCalled = new Monitor();
private final Monitor mOnErrorCalled = new Monitor();
@@ -200,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());
@@ -227,7 +236,7 @@
mPlayer.prepare();
mPlayer.play();
- onVideoSizeChangedCalled.waitForSignal();
+ onVideoSizeChangedCalled.waitForCountedSignals(2);
onVideoRenderingStartCalled.waitForSignal();
mPlayer.setPlayerVolume(volume);
@@ -300,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());
@@ -327,7 +346,7 @@
mPlayer.prepare();
mPlayer.play();
- onVideoSizeChangedCalled.waitForSignal();
+ onVideoSizeChangedCalled.waitForCountedSignals(2);
onVideoRenderingStartCalled.waitForSignal();
}
@@ -616,14 +635,6 @@
mInstrumentation.waitForIdleSync();
MediaPlayer.PlayerCallback callback = new MediaPlayer.PlayerCallback() {
- // TODO: Change this to onTrackInfoChanged callback
- @Override
- public void onInfo(MediaPlayer mp, MediaItem dsd, int what, int extra) {
- if (what == MediaPlayer.MEDIA_INFO_METADATA_UPDATE) {
- mOnInfoCalled.signal();
- }
- }
-
@Override
public void onSubtitleData(@NonNull SessionPlayer player, @NonNull MediaItem item,
@NonNull SessionPlayer.TrackInfo track, @NonNull SubtitleData data) {
@@ -631,19 +642,27 @@
mOnSubtitleDataCalled.signal();
}
}
+
+ @Override
+ public void onTrackInfoChanged(@NonNull SessionPlayer player,
+ @NonNull List<SessionPlayer.TrackInfo> tracks) {
+ assertNotNull(tracks);
+ if (tracks.size() < 3) {
+ // This callback can be called before tracks are available after setMediaItem.
+ return;
+ }
+ mTracksFullyFound.signal();
+ }
};
mPlayer.registerPlayerCallback(mExecutor, callback);
mPlayer.setSurface(mActivity.getSurfaceHolder().getSurface());
mPlayer.prepare();
mPlayer.play().get();
- assertTrue(mPlayer.getPlayerState() == MediaPlayer.PLAYER_STATE_PLAYING);
+ assertEquals(MediaPlayer.PLAYER_STATE_PLAYING, mPlayer.getPlayerState());
// Closed caption tracks are in-band.
// So, those tracks will be found after processing a number of frames.
- mOnInfoCalled.waitForSignal(1500);
-
- mOnInfoCalled.reset();
- mOnInfoCalled.waitForSignal(1500);
+ assertTrue(mTracksFullyFound.waitForSignal(3000));
readTracks();
@@ -673,14 +692,6 @@
}
MediaPlayer.PlayerCallback callback = new MediaPlayer.PlayerCallback() {
- // TODO: Change this to onTrackInfoChanged callback
- @Override
- public void onInfo(MediaPlayer mp, MediaItem dsd, int what, int extra) {
- if (what == MediaPlayer.MEDIA_INFO_METADATA_UPDATE) {
- mOnInfoCalled.signal();
- }
- }
-
@Override
public void onSubtitleData(@NonNull SessionPlayer player, @NonNull MediaItem item,
@NonNull SessionPlayer.TrackInfo track, @NonNull SubtitleData data) {
@@ -688,19 +699,27 @@
mOnSubtitleDataCalled.signal();
}
}
+
+ @Override
+ public void onTrackInfoChanged(@NonNull SessionPlayer player,
+ @NonNull List<SessionPlayer.TrackInfo> tracks) {
+ assertNotNull(tracks);
+ if (tracks.size() < 3) {
+ // This callback can be called before tracks are available after setMediaItem.
+ return;
+ }
+ mTracksFullyFound.signal();
+ }
};
mPlayer.registerPlayerCallback(mExecutor, callback);
mPlayer.setSurface(mActivity.getSurfaceHolder().getSurface());
mPlayer.prepare();
mPlayer.play().get();
- assertTrue(mPlayer.getPlayerState() == MediaPlayer.PLAYER_STATE_PLAYING);
+ assertEquals(MediaPlayer.PLAYER_STATE_PLAYING, mPlayer.getPlayerState());
// Closed caption tracks are in-band.
// So, those tracks will be found after processing a number of frames.
- mOnInfoCalled.waitForSignal(1500);
-
- mOnInfoCalled.reset();
- mOnInfoCalled.waitForSignal(1500);
+ assertTrue(mTracksFullyFound.waitForSignal(3000));
readTracks();
assertEquals(mSelectedTrack, mPlayer.getSelectedTrack(TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE));
@@ -727,27 +746,27 @@
}
MediaPlayer.PlayerCallback callback = new MediaPlayer.PlayerCallback() {
- // TODO: Change this to onTrackInfoChanged callback
@Override
- public void onInfo(MediaPlayer mp, MediaItem dsd, int what, int extra) {
- if (what == MediaPlayer.MEDIA_INFO_METADATA_UPDATE) {
- mOnInfoCalled.signal();
+ public void onTrackInfoChanged(@NonNull SessionPlayer player,
+ @NonNull List<SessionPlayer.TrackInfo> tracks) {
+ assertNotNull(tracks);
+ if (tracks.size() < 3) {
+ // This callback can be called before tracks are available after setMediaItem.
+ return;
}
+ mTracksFullyFound.signal();
}
};
mPlayer.registerPlayerCallback(mExecutor, callback);
mPlayer.setSurface(mActivity.getSurfaceHolder().getSurface());
mPlayer.prepare();
mPlayer.play().get();
- assertTrue(mPlayer.getPlayerState() == MediaPlayer.PLAYER_STATE_PLAYING);
+ assertEquals(MediaPlayer.PLAYER_STATE_PLAYING, mPlayer.getPlayerState());
// The media metadata will be changed while playing since closed caption tracks are in-band
// and those tracks will be found after processing a number of frames. These tracks will be
// found within one second.
- mOnInfoCalled.waitForSignal(1500);
-
- mOnInfoCalled.reset();
- mOnInfoCalled.waitForSignal(1500);
+ assertTrue(mTracksFullyFound.waitForSignal(3000));
readTracks();
assertEquals(2, mSubtitleTrackInfos.size());
@@ -766,14 +785,19 @@
MediaPlayer.PlayerCallback callback = new MediaPlayer.PlayerCallback() {
@Override
public void onTrackInfoChanged(@NonNull SessionPlayer player,
- @NonNull List<SessionPlayer.TrackInfo> trackInfos) {
- mOnTrackInfoChangedCalled.signal();
+ @NonNull List<SessionPlayer.TrackInfo> tracks) {
+ assertNotNull(tracks);
+ if (tracks.size() < 2) {
+ // This callback can be called before tracks are available after setMediaItem.
+ return;
+ }
+ mTracksFullyFound.signal();
}
};
mPlayer.registerPlayerCallback(mExecutor, callback);
mPlayer.prepare();
- mOnTrackInfoChangedCalled.waitForSignal(1500);
+ mTracksFullyFound.waitForSignal(1500);
readTracks();
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 fdad296..7e62724 100644
--- a/media2/player/src/main/java/androidx/media2/player/MediaPlayer.java
+++ b/media2/player/src/main/java/androidx/media2/player/MediaPlayer.java
@@ -2308,16 +2308,15 @@
* </p>
* @param trackInfo metadata corresponding to the track to be selected. A {@code trackInfo}
* object can be obtained from {@link #getTrackInfo()}.
- * Note that the {@link TrackInfo}s may become invalid
- * when {@link PlayerCallback#onInfo(MediaPlayer, MediaItem, int, int)} is called with
- * {@link #MEDIA_INFO_METADATA_UPDATE}.
*
* @see #getTrackInfo
* @return a {@link ListenableFuture} which represents the pending completion of the command.
* {@link SessionPlayer.PlayerResult} will be delivered when the command completed.
*/
// TODO: support subtitle track selection (b/130312596)
- // TODO: revise doc when onTrackInfoChanged is becoming public (b/132928418)
+ // TODO: Revise doc to let developers know the tracks from getTrackInfo may be invalidated
+ // when onTrackInfoChanged is called after onTrackInfoChanged is becoming public
+ // (b/132928418).
@NonNull
public ListenableFuture<PlayerResult> selectTrack(@NonNull final TrackInfo trackInfo) {
return selectTrackInternal(trackInfo.toInternal());
@@ -2331,9 +2330,6 @@
* </p>
* @param trackInfo metadata corresponding to the track to be selected. A {@code trackInfo}
* object can be obtained from {@link #getTrackInfo()}.
- * Note that the {@link TrackInfo}s may become invalid
- * when {@link PlayerCallback#onInfo(MediaPlayer, MediaItem, int, int)} is called with
- * {@link #MEDIA_INFO_METADATA_UPDATE}.
*
* @see #getTrackInfo
* @return a {@link ListenableFuture} which represents the pending completion of the command.
@@ -2341,7 +2337,9 @@
*
* @hide TODO: unhide this when we support subtitle track selection (b/130312596)
*/
- // TODO: revise doc when onTrackInfoChanged is becoming public (b/132928418)
+ // TODO: Revise doc to let developers know the tracks from getTrackInfo may be invalidated
+ // when onTrackInfoChanged is called after onTrackInfoChanged is becoming public
+ // (b/132928418).
@RestrictTo(LIBRARY_GROUP_PREFIX)
@NonNull
public ListenableFuture<PlayerResult> deselectTrack(@NonNull final TrackInfo trackInfo) {
@@ -3014,13 +3012,16 @@
break;
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);
}
});
break;
@@ -3128,6 +3129,7 @@
notifySessionPlayerCallback(new SessionPlayerCallbackNotifier() {
@Override
public void callCallback(SessionPlayer.PlayerCallback callback) {
+ callback.onVideoSizeChanged(MediaPlayer.this, commonSize);
callback.onVideoSizeChangedInternal(MediaPlayer.this, item, commonSize);
}
});
@@ -3166,14 +3168,6 @@
setBufferingState(item, BUFFERING_STATE_BUFFERING_AND_STARVED);
break;
case MediaPlayer2.MEDIA_INFO_PREPARED:
- notifySessionPlayerCallback(new SessionPlayerCallbackNotifier() {
- @Override
- public void callCallback(SessionPlayer.PlayerCallback callback) {
- callback.onTrackInfoChanged(MediaPlayer.this, getTrackInfoInternal());
- }
- });
- setBufferingState(item, BUFFERING_STATE_BUFFERING_AND_PLAYABLE);
- break;
case MediaPlayer2.MEDIA_INFO_BUFFERING_END:
setBufferingState(item, BUFFERING_STATE_BUFFERING_AND_PLAYABLE);
break;
@@ -3191,14 +3185,6 @@
}
});
break;
- case MediaPlayer2.MEDIA_INFO_METADATA_UPDATE:
- notifySessionPlayerCallback(new SessionPlayerCallbackNotifier() {
- @Override
- public void callCallback(SessionPlayer.PlayerCallback callback) {
- callback.onTrackInfoChanged(MediaPlayer.this, getTrackInfoInternal());
- }
- });
- break;
}
if (sInfoCodeMap.containsKey(mp2What)) {
final int what = sInfoCodeMap.get(mp2What);
@@ -3243,6 +3229,13 @@
}
});
}
+
+ @Override
+ public void onTrackInfoChanged(@NonNull MediaPlayer2 mp,
+ @NonNull List<SessionPlayer.TrackInfo> tracks) {
+ notifySessionPlayerCallback(callback -> callback.onTrackInfoChanged(MediaPlayer.this,
+ tracks));
+ }
}
/**
@@ -3265,9 +3258,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,
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 88d7fa5..379bfac 100644
--- a/media2/player/src/main/java/androidx/media2/player/MediaPlayer2.java
+++ b/media2/player/src/main/java/androidx/media2/player/MediaPlayer2.java
@@ -777,9 +777,8 @@
* </p>
* @param trackId the id of the track to be selected. The id can be obtained by calling
* {@link TrackInfo#getId()} to an {@link TrackInfo} returned by {@link #getTrackInfo()}.
- * Note that the {@link TrackInfo}s may become invalid
- * when {@link EventCallback#onInfo(MediaPlayer2, MediaItem, int, int)} is called with
- * {@code what} of {@link #MEDIA_INFO_PREPARED} or {@link #MEDIA_INFO_METADATA_UPDATE}.
+ * Note that the {@link TrackInfo}s may become invalid when
+ * {@link EventCallback#onTrackInfoChanged} is called.
*
* @see TrackInfo#getId()
* @see #getTrackInfo
@@ -798,9 +797,8 @@
* </p>
* @param trackId the id of the track to be deselected. The id can be obtained by calling
* {@link TrackInfo#getId()} to an {@link TrackInfo} returned by {@link #getTrackInfo()} or
- * {@link #getSelectedTrack(int)}. Note that the {@link TrackInfo}s may become invalid
- * when {@link EventCallback#onInfo(MediaPlayer2, MediaItem, int, int)} is called with
- * {@code what} of {@link #MEDIA_INFO_PREPARED} or {@link #MEDIA_INFO_METADATA_UPDATE}.
+ * {@link #getSelectedTrack(int)}. Note that the {@link TrackInfo}s may become invalid when
+ * {@link EventCallback#onTrackInfoChanged} is called.
*
* @see TrackInfo#getId()
* @see #getTrackInfo
@@ -926,6 +924,20 @@
*/
public void onSubtitleData(@NonNull MediaPlayer2 mp, @NonNull MediaItem item,
@NonNull TrackInfo track, @NonNull SubtitleData data) { }
+
+ /**
+ * Called when the tracks of the current media item is changed such as
+ * 1) when tracks of a media item become available, or
+ * 2) when new tracks are found during playback.
+ * <p>
+ * When it's called, the previous tracks may be invalidated so it's recommended to use the
+ * most recent tracks to call {@link #selectTrack} or {@link #deselectTrack}.
+ *
+ * @param mp the player associated with this callback
+ * @param tracks the list of tracks. It can be empty.
+ */
+ public void onTrackInfoChanged(@NonNull MediaPlayer2 mp,
+ @NonNull List<TrackInfo> tracks) { }
}
/**
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/ExoPlayerMediaPlayer2Impl.java b/media2/player/src/main/java/androidx/media2/player/exoplayer/ExoPlayerMediaPlayer2Impl.java
index 7b8568e..74c82e5 100644
--- a/media2/player/src/main/java/androidx/media2/player/exoplayer/ExoPlayerMediaPlayer2Impl.java
+++ b/media2/player/src/main/java/androidx/media2/player/exoplayer/ExoPlayerMediaPlayer2Impl.java
@@ -667,8 +667,9 @@
}
@Override
- public void onMetadataChanged(MediaItem mediaItem) {
- notifyOnInfo(mediaItem, MEDIA_INFO_METADATA_UPDATE);
+ public void onTrackInfoChanged(@NonNull final List<TrackInfo> tracks) {
+ notifyMediaPlayer2Event(cb -> cb.onTrackInfoChanged(ExoPlayerMediaPlayer2Impl.this,
+ tracks));
}
@Override
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 c578ad8..f7b801f 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
@@ -103,8 +103,8 @@
/** Called when the player is prepared. */
void onPrepared(MediaItem mediaItem);
- /** Called when metadata (e.g., the set of available tracks) changes. */
- void onMetadataChanged(MediaItem mediaItem);
+ /** Called when the list of available tracks changes. */
+ void onTrackInfoChanged(@NonNull List<TrackInfo> tracks);
/** Called when a seek request has completed. */
void onSeekCompleted();
@@ -567,8 +567,8 @@
@SuppressWarnings("WeakerAccess") /* synthetic access */
void handleTextRendererChannelAvailable(int type, int channel) {
mTrackSelector.handleTextRendererChannelAvailable(type, channel);
- if (mTrackSelector.hasPendingMetadataUpdate()) {
- mListener.onMetadataChanged(getCurrentMediaItem());
+ if (mTrackSelector.hasPendingTracksUpdate()) {
+ mListener.onTrackInfoChanged(getTrackInfo());
}
}
@@ -576,8 +576,8 @@
void handlePlayerTracksChanged(TrackSelectionArray trackSelections) {
MediaItem currentMediaItem = getCurrentMediaItem();
mTrackSelector.handlePlayerTracksChanged(currentMediaItem, trackSelections);
- if (mTrackSelector.hasPendingMetadataUpdate()) {
- mListener.onMetadataChanged(currentMediaItem);
+ if (mTrackSelector.hasPendingTracksUpdate()) {
+ mListener.onTrackInfoChanged(getTrackInfo());
}
}
diff --git a/media2/player/src/main/java/androidx/media2/player/exoplayer/TrackSelector.java b/media2/player/src/main/java/androidx/media2/player/exoplayer/TrackSelector.java
index fcf14e3..6f67804 100644
--- a/media2/player/src/main/java/androidx/media2/player/exoplayer/TrackSelector.java
+++ b/media2/player/src/main/java/androidx/media2/player/exoplayer/TrackSelector.java
@@ -74,7 +74,7 @@
private final SparseArray<InternalTrackInfo> mMetadataTracks;
private final SparseArray<InternalTextTrackInfo> mTextTracks;
- private boolean mPendingMetadataUpdate;
+ private boolean mPendingTracksUpdate;
private InternalTrackInfo mSelectedAudioTrack;
private InternalTrackInfo mSelectedVideoTrack;
private InternalTrackInfo mSelectedMetadataTrack;
@@ -109,7 +109,7 @@
final boolean itemChanged = mCurrentMediaItem != item;
mCurrentMediaItem = item;
- mPendingMetadataUpdate = true;
+ mPendingTracksUpdate = true;
// Clear all selection state.
mDefaultTrackSelector.setParameters(
@@ -233,14 +233,14 @@
InternalTextTrackInfo textTrack = new InternalTextTrackInfo(
mPlayerTextTrackIndex, type, /* format= */ null, channel, mNextTrackId++);
mTextTracks.put(textTrack.mExternalTrackInfo.getId(), textTrack);
- mPendingMetadataUpdate = true;
+ mPendingTracksUpdate = true;
}
}
- public boolean hasPendingMetadataUpdate() {
- boolean pendingMetadataUpdate = mPendingMetadataUpdate;
- mPendingMetadataUpdate = false;
- return pendingMetadataUpdate;
+ public boolean hasPendingTracksUpdate() {
+ boolean pendingTracksUpdate = mPendingTracksUpdate;
+ mPendingTracksUpdate = false;
+ return pendingTracksUpdate;
}
public TrackInfo getSelectedTrack(int trackType) {
diff --git a/media2/session/api/1.1.0-alpha01.txt b/media2/session/api/1.1.0-alpha01.txt
index d16e2fb..4d96ad8 100644
--- a/media2/session/api/1.1.0-alpha01.txt
+++ b/media2/session/api/1.1.0-alpha01.txt
@@ -190,6 +190,7 @@
method public java.util.List<androidx.media2.session.MediaSession.ControllerInfo!> getConnectedControllers();
method public String getId();
method public androidx.media2.common.SessionPlayer getPlayer();
+ method public android.support.v4.media.session.MediaSessionCompat getSessionCompat();
method public androidx.media2.session.SessionToken getToken();
method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> sendCustomCommand(androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand, android.os.Bundle?);
method public void setAllowedCommands(androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommandGroup);
diff --git a/media2/session/api/current.txt b/media2/session/api/current.txt
index d16e2fb..4d96ad8 100644
--- a/media2/session/api/current.txt
+++ b/media2/session/api/current.txt
@@ -190,6 +190,7 @@
method public java.util.List<androidx.media2.session.MediaSession.ControllerInfo!> getConnectedControllers();
method public String getId();
method public androidx.media2.common.SessionPlayer getPlayer();
+ method public android.support.v4.media.session.MediaSessionCompat getSessionCompat();
method public androidx.media2.session.SessionToken getToken();
method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> sendCustomCommand(androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand, android.os.Bundle?);
method public void setAllowedCommands(androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommandGroup);
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 3373a35..57af209 100644
--- a/media2/session/src/androidTest/java/androidx/media2/session/MediaControllerTest.java
+++ b/media2/session/src/androidTest/java/androidx/media2/session/MediaControllerTest.java
@@ -308,15 +308,16 @@
}
@Test
- public void testSetMediaItem() throws InterruptedException {
+ public void testSetMediaItem() throws Exception {
prepareLooper();
- final MediaItem item = TestUtils.createMediaItemWithMetadata();
- mController.setMediaItem(item.getMetadata()
- .getString(MediaMetadata.METADATA_KEY_MEDIA_ID));
+ String mediaId = "testSetMediaItem";
+ int resultCode = mController.setMediaItem(mediaId).get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
+ .getResultCode();
+ assertEquals(RESULT_SUCCESS, resultCode);
assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertNull(mPlayer.mMetadata);
- assertEquals(item.getMediaId(), mPlayer.mItem.getMediaId());
+ assertEquals(mediaId, mPlayer.mItem.getMediaId());
}
/**
@@ -1648,8 +1649,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,
@@ -1658,8 +1660,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/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 c66a659..4b7cee8 100644
--- a/media2/session/src/main/java/androidx/media2/session/MediaController.java
+++ b/media2/session/src/main/java/androidx/media2/session/MediaController.java
@@ -823,7 +823,7 @@
throw new IllegalArgumentException("mediaId shouldn't be empty");
}
if (isConnected()) {
- getImpl().setMediaItem(mediaId);
+ return getImpl().setMediaItem(mediaId);
}
return createDisconnectedFuture();
}
@@ -1034,7 +1034,7 @@
* <p>
* This calls {@link SessionPlayer#skipToPlaylistItem(int)}.
*
- * @param index The item in the playlist you want to play
+ * @param index The index of the item you want to play in the playlist
*/
@NonNull
public ListenableFuture<SessionResult> skipToPlaylistItem(@IntRange(from = 0) int index) {
@@ -1877,21 +1877,32 @@
public void onPlaybackCompleted(@NonNull MediaController controller) {}
/**
- * 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
- *
+ * @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 the tracks are first retrieved after media is prepared or when new tracks are
- * found during playback.
+ * Called when video size is changed.
+ *
+ * @param controller the controller for this event
+ * @param videoSize the size of video
+ *
+ * @hide
+ */
+ // TODO: Unhide this (b/134749006)
+ @RestrictTo(LIBRARY_GROUP)
+ public void onVideoSizeChanged(@NonNull MediaController controller,
+ @NonNull VideoSize videoSize) {}
+
+ /**
+ * Called when the tracks of the current media item is changed such as
+ * 1) when tracks of a media item become available,
+ * 2) when new tracks are found during playback, or
+ * 3) when the current media item is changed.
* <p>
* When it's called, you should invalidate previous track information and use the new
* tracks to call {@link #selectTrack(TrackInfo)} or
@@ -1905,7 +1916,7 @@
* @see TrackInfo#MEDIA_TRACK_TYPE_METADATA
*
* @param controller the controller for this event
- * @param trackInfos the list of track infos
+ * @param trackInfos the list of tracks. It can be empty.
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
@@ -1966,7 +1977,7 @@
}
/**
- * Holds information about the the way volume is handled for this session.
+ * Holds information about the way volume is handled for this session.
*/
// The same as MediaController.PlaybackInfo
@VersionedParcelize
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/MediaControllerImplLegacy.java b/media2/session/src/main/java/androidx/media2/session/MediaControllerImplLegacy.java
index e1144e6..625be61 100644
--- a/media2/session/src/main/java/androidx/media2/session/MediaControllerImplLegacy.java
+++ b/media2/session/src/main/java/androidx/media2/session/MediaControllerImplLegacy.java
@@ -672,18 +672,17 @@
}
@Override
- public ListenableFuture<SessionResult> replacePlaylistItem(int index,
- @NonNull String mediaId) {
+ public ListenableFuture<SessionResult> replacePlaylistItem(int index, @NonNull String mediaId) {
synchronized (mLock) {
if (!mConnected) {
Log.w(TAG, "Session isn't active", new IllegalStateException());
return createFutureWithResult(RESULT_ERROR_SESSION_DISCONNECTED);
}
- if (mPlaylist == null || index < 0 || mPlaylist.size() <= index) {
+ if (mQueue == null || index < 0 || index >= mQueue.size()) {
return createFutureWithResult(RESULT_ERROR_BAD_VALUE);
}
- removePlaylistItem(index);
- addPlaylistItem(index, mediaId);
+ mControllerCompat.removeQueueItem(mQueue.get(index).getDescription());
+ mControllerCompat.addQueueItem(MediaUtils.createMediaDescriptionCompat(mediaId), index);
}
return createFutureWithResult(RESULT_SUCCESS);
}
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 c6f997e..9895f8c 100644
--- a/media2/session/src/main/java/androidx/media2/session/MediaSession.java
+++ b/media2/session/src/main/java/androidx/media2/session/MediaSession.java
@@ -95,7 +95,7 @@
* session.
* <p>
* When a session receive transport control commands, the session sends the commands directly to
- * the the underlying media player set by {@link Builder} or {@link #updatePlayer}.
+ * the underlying media player set by {@link Builder} or {@link #updatePlayer}.
* <p>
* When an app is finished performing playback it must call {@link #close()} to clean up the session
* and notify any controllers. The app is responsible for closing the underlying player after
@@ -386,10 +386,12 @@
}
/**
- * @hide
- * @return Bundle
+ * Gets the {@link MediaSessionCompat} for this session to handle incoming commands from the
+ * {@link android.support.v4.media.session.MediaSessionCompat} for legacy support.
+ *
+ * @return MediaSessionCompat
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @NonNull
public MediaSessionCompat getSessionCompat() {
return mImpl.getSessionCompat();
}
@@ -1201,8 +1203,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)
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 04fd45a..eee1bea 100644
--- a/media2/session/src/main/java/androidx/media2/session/MediaSessionImplBase.java
+++ b/media2/session/src/main/java/androidx/media2/session/MediaSessionImplBase.java
@@ -290,11 +290,15 @@
new VolumeProviderCompat(remotePlayer.getVolumeControlType(),
remotePlayer.getMaxVolume(),
remotePlayer.getVolume()) {
+ // TODO(b/138091975) Do not ignore the returned Future.
+ @SuppressWarnings("FutureReturnValueIgnored")
@Override
public void onSetVolumeTo(int volume) {
remotePlayer.setVolume(volume);
}
+ // TODO(b/138091975) Do not ignore the returned Future.
+ @SuppressWarnings("FutureReturnValueIgnored")
@Override
public void onAdjustVolume(int direction) {
remotePlayer.adjustVolume(direction);
@@ -1532,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 016d316..4776445 100644
--- a/media2/session/src/main/java/androidx/media2/session/MediaSessionLegacyStub.java
+++ b/media2/session/src/main/java/androidx/media2/session/MediaSessionLegacyStub.java
@@ -121,6 +121,8 @@
@Override
public void onPrepare() {
dispatchSessionTask(SessionCommand.COMMAND_CODE_PLAYER_PREPARE, new SessionTask() {
+ // TODO(b/138091975) Do not ignore the returned Future.
+ @SuppressWarnings("FutureReturnValueIgnored")
@Override
public void run(ControllerInfo controller) throws RemoteException {
mSessionImpl.prepare();
@@ -180,6 +182,8 @@
@Override
public void onPlay() {
dispatchSessionTask(SessionCommand.COMMAND_CODE_PLAYER_PLAY, new SessionTask() {
+ // TODO(b/138091975) Do not ignore the returned Future.
+ @SuppressWarnings("FutureReturnValueIgnored")
@Override
public void run(ControllerInfo controller) throws RemoteException {
mSessionImpl.play();
@@ -239,6 +243,8 @@
@Override
public void onPause() {
dispatchSessionTask(SessionCommand.COMMAND_CODE_PLAYER_PAUSE, new SessionTask() {
+ // TODO(b/138091975) Do not ignore the returned Future.
+ @SuppressWarnings("FutureReturnValueIgnored")
@Override
public void run(ControllerInfo controller) throws RemoteException {
mSessionImpl.pause();
@@ -256,6 +262,8 @@
public void run(ControllerInfo controller) throws RemoteException {
handleTaskOnExecutor(controller, null,
SessionCommand.COMMAND_CODE_PLAYER_SEEK_TO, new SessionTask() {
+ // TODO(b/138091975) Do not ignore the returned Future.
+ @SuppressWarnings("FutureReturnValueIgnored")
@Override
public void run(ControllerInfo controller) throws RemoteException {
mSessionImpl.pause();
@@ -269,6 +277,8 @@
@Override
public void onSeekTo(final long pos) {
dispatchSessionTask(SessionCommand.COMMAND_CODE_PLAYER_SEEK_TO, new SessionTask() {
+ // TODO(b/138091975) Do not ignore the returned Future.
+ @SuppressWarnings("FutureReturnValueIgnored")
@Override
public void run(ControllerInfo controller) throws RemoteException {
mSessionImpl.seekTo(pos);
@@ -280,6 +290,8 @@
public void onSkipToNext() {
dispatchSessionTask(SessionCommand.COMMAND_CODE_PLAYER_SKIP_TO_NEXT_PLAYLIST_ITEM,
new SessionTask() {
+ // TODO(b/138091975) Do not ignore the returned Future.
+ @SuppressWarnings("FutureReturnValueIgnored")
@Override
public void run(ControllerInfo controller) throws RemoteException {
mSessionImpl.skipToNextItem();
@@ -291,6 +303,8 @@
public void onSkipToPrevious() {
dispatchSessionTask(SessionCommand.COMMAND_CODE_PLAYER_SKIP_TO_PREVIOUS_PLAYLIST_ITEM,
new SessionTask() {
+ // TODO(b/138091975) Do not ignore the returned Future.
+ @SuppressWarnings("FutureReturnValueIgnored")
@Override
public void run(ControllerInfo controller) throws RemoteException {
mSessionImpl.skipToPreviousItem();
@@ -301,6 +315,8 @@
@Override
public void onSetPlaybackSpeed(final float speed) {
dispatchSessionTask(SessionCommand.COMMAND_CODE_PLAYER_SET_SPEED, new SessionTask() {
+ // TODO(b/138091975) Do not ignore the returned Future.
+ @SuppressWarnings("FutureReturnValueIgnored")
@Override
public void run(ControllerInfo controller) throws RemoteException {
mSessionImpl.setPlaybackSpeed(speed);
@@ -312,6 +328,8 @@
public void onSkipToQueueItem(final long queueId) {
dispatchSessionTask(SessionCommand.COMMAND_CODE_PLAYER_SKIP_TO_PLAYLIST_ITEM,
new SessionTask() {
+ // TODO(b/138091975) Do not ignore the returned Future.
+ @SuppressWarnings("FutureReturnValueIgnored")
@Override
public void run(ControllerInfo controller) throws RemoteException {
List<MediaItem> playlist = mSessionImpl.getPlayer().getPlaylist();
@@ -388,6 +406,8 @@
public void onSetRepeatMode(final int repeatMode) {
dispatchSessionTask(SessionCommand.COMMAND_CODE_PLAYER_SET_REPEAT_MODE,
new SessionTask() {
+ // TODO(b/138091975) Do not ignore the returned Future.
+ @SuppressWarnings("FutureReturnValueIgnored")
@Override
public void run(ControllerInfo controller) throws RemoteException {
mSessionImpl.setRepeatMode(repeatMode);
@@ -399,6 +419,8 @@
public void onSetShuffleMode(final int shuffleMode) {
dispatchSessionTask(SessionCommand.COMMAND_CODE_PLAYER_SET_SHUFFLE_MODE,
new SessionTask() {
+ // TODO(b/138091975) Do not ignore the returned Future.
+ @SuppressWarnings("FutureReturnValueIgnored")
@Override
public void run(ControllerInfo controller) throws RemoteException {
mSessionImpl.setShuffleMode(shuffleMode);
@@ -418,6 +440,8 @@
}
dispatchSessionTask(SessionCommand.COMMAND_CODE_PLAYER_ADD_PLAYLIST_ITEM,
new SessionTask() {
+ // TODO(b/138091975) Do not ignore the returned Future.
+ @SuppressWarnings("FutureReturnValueIgnored")
@Override
public void run(ControllerInfo controller) throws RemoteException {
String mediaId = description.getMediaId();
@@ -439,6 +463,8 @@
}
dispatchSessionTask(SessionCommand.COMMAND_CODE_PLAYER_REMOVE_PLAYLIST_ITEM,
new SessionTask() {
+ // TODO(b/138091975) Do not ignore the returned Future.
+ @SuppressWarnings("FutureReturnValueIgnored")
@Override
public void run(ControllerInfo controller) throws RemoteException {
String mediaId = description.getMediaId();
@@ -462,6 +488,8 @@
public void onRemoveQueueItemAt(final int index) {
dispatchSessionTask(SessionCommand.COMMAND_CODE_PLAYER_REMOVE_PLAYLIST_ITEM,
new SessionTask() {
+ // TODO(b/138091975) Do not ignore the returned Future.
+ @SuppressWarnings("FutureReturnValueIgnored")
@Override
public void run(ControllerInfo controller) throws RemoteException {
if (index < 0) {
@@ -704,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
}
@@ -919,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/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/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/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/androidTest/java/androidx/media2/widget/MediaControlView_WithPlayerTest.java b/media2/widget/src/androidTest/java/androidx/media2/widget/MediaControlView_WithPlayerTest.java
index d0c7292..e0ddf03 100644
--- a/media2/widget/src/androidTest/java/androidx/media2/widget/MediaControlView_WithPlayerTest.java
+++ b/media2/widget/src/androidTest/java/androidx/media2/widget/MediaControlView_WithPlayerTest.java
@@ -125,7 +125,7 @@
latchForPlayingState.countDown();
}
}
- }, mFileSchemeMediaItem);
+ }, mFileSchemeMediaItem, null);
setPlayerWrapper(playerWrapper);
assertTrue(latchForPausedState.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
onView(allOf(withId(R.id.pause), isCompletelyDisplayed()))
@@ -162,7 +162,7 @@
}
mState = state;
}
- }, mFileSchemeMediaItem);
+ }, mFileSchemeMediaItem, null);
assertTrue(latchForPreparedState.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
playerWrapper.play();
assertTrue(latchForPlayingState.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
@@ -178,7 +178,7 @@
@Test
public void testSetPlayerAndController_MultipleTimes() throws Throwable {
DefaultPlayerCallback callback1 = new DefaultPlayerCallback();
- PlayerWrapper wrapper1 = createPlayerWrapper(callback1, mFileSchemeMediaItem);
+ PlayerWrapper wrapper1 = createPlayerWrapper(callback1, mFileSchemeMediaItem, null);
assertTrue(callback1.mPausedLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
DefaultPlayerCallback callback2 = new DefaultPlayerCallback();
@@ -191,7 +191,7 @@
assertTrue(callback3.mPausedLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
DefaultPlayerCallback callback4 = new DefaultPlayerCallback();
- PlayerWrapper wrapper4 = createPlayerWrapper(callback4, mFileSchemeMediaItem);
+ PlayerWrapper wrapper4 = createPlayerWrapper(callback4, mFileSchemeMediaItem, null);
assertTrue(callback4.mPausedLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
setPlayerWrapper(wrapper1);
@@ -244,7 +244,7 @@
latchForPausedState.countDown();
}
}
- }, mFileSchemeMediaItem);
+ }, mFileSchemeMediaItem, null);
setPlayerWrapper(playerWrapper);
assertTrue(latchForPausedState.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
onView(allOf(withId(R.id.ffwd), isCompletelyDisplayed())).perform(click());
@@ -285,7 +285,7 @@
private boolean equalsSeekPosition(long expected, long actual, long delta) {
return (actual < expected + delta) && (actual > expected - delta);
}
- }, mFileSchemeMediaItem);
+ }, mFileSchemeMediaItem, null);
setPlayerWrapper(playerWrapper);
assertTrue(latchForFfwd.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
onView(allOf(withId(R.id.rew), isCompletelyDisplayed())).perform(click());
@@ -293,6 +293,40 @@
}
@Test
+ public void testPrevNextButtonClick() throws Throwable {
+ DefaultPlayerCallback callback = new DefaultPlayerCallback();
+ final List<MediaItem> playlist = createTestPlaylist();
+ final PlayerWrapper playerWrapper = createPlayerWrapper(callback, null, playlist);
+ setPlayerWrapper(playerWrapper);
+
+ assertTrue(callback.mItemLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ assertEquals(0, playerWrapper.getCurrentMediaItemIndex());
+ onView(allOf(withId(R.id.prev), not(isClickable())));
+ onView(allOf(withId(R.id.next), isClickable()));
+ callback.mItemLatch = new CountDownLatch(1);
+ onView(allOf(withId(R.id.next), isCompletelyDisplayed())).perform(click());
+
+ assertTrue(callback.mItemLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ assertEquals(1, playerWrapper.getCurrentMediaItemIndex());
+ onView(allOf(withId(R.id.prev), isClickable()));
+ onView(allOf(withId(R.id.next), isClickable()));
+ callback.mItemLatch = new CountDownLatch(1);
+ onView(allOf(withId(R.id.next), isCompletelyDisplayed())).perform(click());
+
+ assertTrue(callback.mItemLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ assertEquals(2, playerWrapper.getCurrentMediaItemIndex());
+ onView(allOf(withId(R.id.prev), isClickable()));
+ onView(allOf(withId(R.id.next), not(isClickable())));
+ callback.mItemLatch = new CountDownLatch(1);
+ onView(allOf(withId(R.id.prev), isCompletelyDisplayed())).perform(click());
+
+ assertTrue(callback.mItemLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ assertEquals(1, playerWrapper.getCurrentMediaItemIndex());
+ onView(allOf(withId(R.id.prev), isClickable()));
+ onView(allOf(withId(R.id.next), isClickable()));
+ }
+
+ @Test
public void testSetMetadataForNonMusicFile() throws Throwable {
final String title = "BigBuckBunny";
final CountDownLatch latch = new CountDownLatch(1);
@@ -309,7 +343,7 @@
latch.countDown();
}
}
- }, mFileSchemeMediaItem);
+ }, mFileSchemeMediaItem, null);
setPlayerWrapper(playerWrapper);
assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
onView(withId(R.id.title_text)).check(matches(withText(title)));
@@ -325,10 +359,15 @@
final PlayerWrapper playerWrapper = createPlayerWrapper(new PlayerWrapper.PlayerCallback() {
@Override
public void onTrackInfoChanged(@NonNull PlayerWrapper player,
- @NonNull List<TrackInfo> trackInfos) {
+ @NonNull List<TrackInfo> tracks) {
+ assertNotNull(tracks);
+ if (tracks.isEmpty()) {
+ // This callback can be called before tracks are available after setMediaItem
+ return;
+ }
latch.countDown();
}
- }, uriMediaItem);
+ }, uriMediaItem, null);
setPlayerWrapper(playerWrapper);
assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
onView(withId(R.id.subtitle)).check(matches(not(isDisplayed())));
@@ -390,7 +429,7 @@
assertEquals(mFirstSubtitleTrack, trackInfo);
latchForSubtitleDeselect.countDown();
}
- }, mediaItem);
+ }, mediaItem, null);
setPlayerWrapper(playerWrapper);
assertTrue(latchForReady.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
// MediaPlayer needs a surface to be set in order to produce subtitle tracks
@@ -425,7 +464,7 @@
latchForPlayingState.countDown();
}
}
- }, mFileSchemeMediaItem);
+ }, mFileSchemeMediaItem, null);
mActivityRule.runOnUiThread(new Runnable() {
@Override
public void run() {
@@ -464,7 +503,7 @@
}
private PlayerWrapper createPlayerWrapper(@NonNull PlayerWrapper.PlayerCallback callback,
- @Nullable MediaItem item) {
- return createPlayerWrapperOfType(callback, item, null, mPlayerType);
+ @Nullable MediaItem item, @Nullable List<MediaItem> playlist) {
+ return createPlayerWrapperOfType(callback, item, playlist, mPlayerType);
}
}
diff --git a/media2/widget/src/androidTest/java/androidx/media2/widget/MediaWidgetTestBase.java b/media2/widget/src/androidTest/java/androidx/media2/widget/MediaWidgetTestBase.java
index d7b3eb8..e005a01 100644
--- a/media2/widget/src/androidTest/java/androidx/media2/widget/MediaWidgetTestBase.java
+++ b/media2/widget/src/androidTest/java/androidx/media2/widget/MediaWidgetTestBase.java
@@ -130,12 +130,19 @@
return new UriMediaItem.Builder(uri).build();
}
+ List<MediaItem> createTestPlaylist() {
+ List<MediaItem> list = new ArrayList<>();
+ list.add(createTestMediaItem(Uri.parse("android.resource://" + mContext.getPackageName()
+ + "/" + R.raw.test_file_scheme_video)));
+ list.add(createTestMediaItem(Uri.parse("android.resource://" + mContext.getPackageName()
+ + "/" + R.raw.test_music)));
+ list.add(createTestMediaItem(Uri.parse("android.resource://" + mContext.getPackageName()
+ + "/" + R.raw.testvideo_with_2_subtitle_tracks)));
+ return list;
+ }
+
PlayerWrapper createPlayerWrapperOfController(@NonNull PlayerWrapper.PlayerCallback callback,
@Nullable MediaItem item, @Nullable List<MediaItem> playlist) {
- if (item == null && playlist == null) {
- return null;
- }
-
prepareLooper();
SessionPlayer player = new MediaPlayer(mContext);
@@ -164,10 +171,6 @@
PlayerWrapper createPlayerWrapperOfPlayer(@NonNull PlayerWrapper.PlayerCallback callback,
@Nullable MediaItem item, @Nullable List<MediaItem> playlist) {
- if (item == null && playlist == null) {
- return null;
- }
-
SessionPlayer player = new MediaPlayer(mContext);
synchronized (mLock) {
mPlayers.add(player);
@@ -218,7 +221,7 @@
}
class DefaultPlayerCallback extends PlayerWrapper.PlayerCallback {
- CountDownLatch mItemLatch = new CountDownLatch(1);
+ volatile CountDownLatch mItemLatch = new CountDownLatch(1);
CountDownLatch mPausedLatch = new CountDownLatch(1);
CountDownLatch mPlayingLatch = new CountDownLatch(1);
diff --git a/media2/widget/src/main/java/androidx/media2/widget/MediaControlView.java b/media2/widget/src/main/java/androidx/media2/widget/MediaControlView.java
index 94373aa..f3145de 100644
--- a/media2/widget/src/main/java/androidx/media2/widget/MediaControlView.java
+++ b/media2/widget/src/main/java/androidx/media2/widget/MediaControlView.java
@@ -1660,6 +1660,33 @@
}
}
+ void updatePrevNextButtons(int prevIndex, int nextIndex) {
+ int n = mTransportControlsMap.size();
+ for (int i = 0; i < n; i++) {
+ int sizeType = mTransportControlsMap.keyAt(i);
+ View prevButton = findControlButton(sizeType, R.id.prev);
+ if (prevButton != null) {
+ if (prevIndex > SessionPlayer.INVALID_ITEM_INDEX) {
+ prevButton.setAlpha(1.0f);
+ prevButton.setEnabled(true);
+ } else {
+ prevButton.setAlpha(0.5f);
+ prevButton.setEnabled(false);
+ }
+ }
+ View nextButton = findControlButton(sizeType, R.id.next);
+ if (nextButton != null) {
+ if (nextIndex > SessionPlayer.INVALID_ITEM_INDEX) {
+ nextButton.setAlpha(1.0f);
+ nextButton.setEnabled(true);
+ } else {
+ nextButton.setAlpha(0.5f);
+ nextButton.setEnabled(false);
+ }
+ }
+ }
+ }
+
boolean shouldNotHideBars() {
return (isCurrentItemMusic() && mSizeType == SIZE_TYPE_FULL)
|| mAccessibilityManager.isTouchExplorationEnabled()
@@ -2079,6 +2106,20 @@
}
updateTimeViews(mediaItem);
updateTitleView(mediaItem);
+ updatePrevNextButtons(player.getPreviousMediaItemIndex(),
+ player.getNextMediaItemIndex());
+ }
+
+ @Override
+ void onPlaylistChanged(@NonNull PlayerWrapper player, @Nullable List<MediaItem> list,
+ @Nullable MediaMetadata metadata) {
+ if (player != mPlayer) return;
+
+ if (DEBUG) {
+ Log.d(TAG, "onPlaylistChanged(): list: " + list + ", metadata: " + metadata);
+ }
+ updatePrevNextButtons(player.getPreviousMediaItemIndex(),
+ player.getNextMediaItemIndex());
}
@Override
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 08df637..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) {
@@ -406,6 +404,33 @@
return null;
}
+ int getCurrentMediaItemIndex() {
+ if (mController != null) {
+ return mController.getCurrentMediaItemIndex();
+ } else if (mPlayer != null) {
+ return mPlayer.getCurrentMediaItemIndex();
+ }
+ return SessionPlayer.INVALID_ITEM_INDEX;
+ }
+
+ int getPreviousMediaItemIndex() {
+ if (mController != null) {
+ return mController.getPreviousMediaItemIndex();
+ } else if (mPlayer != null) {
+ return mPlayer.getPreviousMediaItemIndex();
+ }
+ return SessionPlayer.INVALID_ITEM_INDEX;
+ }
+
+ int getNextMediaItemIndex() {
+ if (mController != null) {
+ return mController.getNextMediaItemIndex();
+ } else if (mPlayer != null) {
+ return mPlayer.getNextMediaItemIndex();
+ }
+ return SessionPlayer.INVALID_ITEM_INDEX;
+ }
+
private class MediaControllerCallback extends MediaController.ControllerCallback {
MediaControllerCallback() {
}
@@ -450,6 +475,12 @@
}
@Override
+ public void onPlaylistChanged(@NonNull MediaController controller,
+ @Nullable List<MediaItem> list, @Nullable MediaMetadata metadata) {
+ mWrapperCallback.onPlaylistChanged(PlayerWrapper.this, list, metadata);
+ }
+
+ @Override
public void onPlaybackCompleted(@NonNull MediaController controller) {
mWrapperCallback.onPlaybackCompleted(PlayerWrapper.this);
}
@@ -485,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() {
}
@@ -514,6 +558,12 @@
}
@Override
+ public void onPlaylistChanged(@NonNull SessionPlayer player, @Nullable List<MediaItem> list,
+ @Nullable MediaMetadata metadata) {
+ mWrapperCallback.onPlaylistChanged(PlayerWrapper.this, list, metadata);
+ }
+
+ @Override
public void onPlaybackCompleted(@NonNull SessionPlayer player) {
mWrapperCallback.onPlaybackCompleted(PlayerWrapper.this);
}
@@ -555,6 +605,9 @@
}
void onCurrentMediaItemChanged(@NonNull PlayerWrapper player, @Nullable MediaItem item) {
}
+ void onPlaylistChanged(@NonNull PlayerWrapper player, @Nullable List<MediaItem> list,
+ @Nullable MediaMetadata metadata) {
+ }
void onPlayerStateChanged(@NonNull PlayerWrapper player, int state) {
}
void onPlaybackSpeedChanged(@NonNull PlayerWrapper player, float speed) {
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-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/src/androidTest/java/androidx/navigation/NavControllerTest.kt b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt
index e20b2c8..3b2698d 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,14 @@
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.ViewModelProvider
import androidx.lifecycle.ViewModelStore
import androidx.navigation.test.R
import androidx.navigation.testing.TestNavigator
@@ -890,6 +893,25 @@
}
@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 testSaveRestoreGetViewModelStoreOwner() {
val hostStore = ViewModelStore()
val navController = createNavController()
@@ -966,6 +988,8 @@
}
}
+class TestAndroidViewModel(application: Application) : AndroidViewModel(application)
+
/**
* [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..0b9c4e5d 100644
--- a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavBackStackEntry.java
+++ b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavBackStackEntry.java
@@ -16,10 +16,14 @@
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.ViewModelProvider;
import androidx.lifecycle.ViewModelStore;
import androidx.lifecycle.ViewModelStoreOwner;
@@ -28,7 +32,8 @@
/**
* Representation of an entry in the back stack of a {@link NavController}.
*/
-final class NavBackStackEntry implements ViewModelStoreOwner {
+final class NavBackStackEntry implements ViewModelStoreOwner, HasDefaultViewModelProviderFactory {
+ private final Context mContext;
private final NavDestination mDestination;
private final Bundle mArgs;
@@ -37,13 +42,17 @@
final UUID mId;
private NavControllerViewModel mNavControllerViewModel;
- NavBackStackEntry(@NonNull NavDestination destination, @Nullable Bundle args,
+ NavBackStackEntry(@NonNull Context context,
+ @NonNull NavDestination destination, @Nullable Bundle args,
@Nullable NavControllerViewModel navControllerViewModel) {
- this(UUID.randomUUID(), destination, args, navControllerViewModel);
+ this(context, destination, args, navControllerViewModel, UUID.randomUUID());
}
- NavBackStackEntry(@NonNull UUID uuid, @NonNull NavDestination destination,
- @Nullable Bundle args, @Nullable NavControllerViewModel navControllerViewModel) {
+ NavBackStackEntry(@NonNull Context context,
+ @NonNull NavDestination destination, @Nullable Bundle args,
+ @Nullable NavControllerViewModel navControllerViewModel,
+ @NonNull UUID uuid) {
+ mContext = context;
mId = uuid;
mDestination = destination;
mArgs = args;
@@ -77,4 +86,11 @@
public ViewModelStore getViewModelStore() {
return mNavControllerViewModel.getViewModelStore(mId);
}
+
+ @NonNull
+ @Override
+ public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
+ return ViewModelProvider.AndroidViewModelFactory.getInstance(
+ (Application) mContext.getApplicationContext());
+ }
}
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..a39a6e1
--- /dev/null
+++ b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavBackStackEntryState.java
@@ -0,0 +1,87 @@
+/*
+ * 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;
+
+ NavBackStackEntryState(NavBackStackEntry entry) {
+ mUUID = entry.mId;
+ mDestinationId = entry.getDestination().getId();
+ mArgs = entry.getArguments();
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ NavBackStackEntryState(Parcel in) {
+ mUUID = UUID.fromString(in.readString());
+ mDestinationId = in.readInt();
+ mArgs = in.readBundle(getClass().getClassLoader());
+ }
+
+ @NonNull
+ UUID getUUID() {
+ return mUUID;
+ }
+
+ int getDestinationId() {
+ return mDestinationId;
+ }
+
+ @Nullable
+ Bundle getArgs() {
+ return mArgs;
+ }
+
+ @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);
+ }
+
+ 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..112e24e 100644
--- a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.java
+++ b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.java
@@ -42,7 +42,6 @@
import java.util.Deque;
import java.util.Iterator;
import java.util.Map;
-import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
/**
@@ -64,11 +63,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";
@@ -85,9 +81,7 @@
private NavInflater mInflater;
private 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<>();
@@ -474,25 +468,23 @@
}
}
}
- 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));
+ mBackStack.add(new NavBackStackEntry(mContext, node, args, mViewModel,
+ state.getUUID()));
}
updateOnBackPressedCallbackEnabled();
- mBackStackUUIDsToRestore = null;
- mBackStackIdsToRestore = null;
- mBackStackArgsToRestore = null;
+ mBackStackToRestore = null;
}
if (mGraph != null && mBackStack.isEmpty()) {
boolean deepLinked = !mDeepLinkHandled && mActivity != null
@@ -873,7 +865,7 @@
}
// The mGraph should always be on the back stack after you navigate()
if (mBackStack.isEmpty()) {
- mBackStack.add(new NavBackStackEntry(mGraph, finalArgs, mViewModel));
+ mBackStack.add(new NavBackStackEntry(mContext, mGraph, finalArgs, mViewModel));
}
// Now ensure all intermediate NavGraphs are put on the back stack
// to ensure that global actions work.
@@ -882,13 +874,14 @@
while (destination != null && findDestination(destination.getId()) == null) {
NavGraph parent = destination.getParent();
if (parent != null) {
- hierarchy.addFirst(new NavBackStackEntry(parent, finalArgs, mViewModel));
+ hierarchy.addFirst(new NavBackStackEntry(mContext, parent, finalArgs,
+ mViewModel));
}
destination = parent;
}
mBackStack.addAll(hierarchy);
// And finally, add the new destination with its default args
- NavBackStackEntry newBackStackEntry = new NavBackStackEntry(newDest,
+ NavBackStackEntry newBackStackEntry = new NavBackStackEntry(mContext, newDest,
newDest.addInDefaultArgs(finalArgs), mViewModel);
mBackStack.add(newBackStackEntry);
}
@@ -971,18 +964,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,9 +998,7 @@
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);
}
@@ -1052,26 +1037,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/paging/common/api/3.0.0-alpha01.txt b/paging/common/api/3.0.0-alpha01.txt
index 903b245..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;
@@ -243,18 +238,15 @@
public abstract class PagedSource<Key, Value> {
ctor public PagedSource();
- method public abstract boolean getInvalid();
+ method public final boolean getInvalid();
method public abstract androidx.paging.PagedSource.KeyProvider<Key,Value> getKeyProvider();
- method public abstract void invalidate();
+ 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);
- property public abstract boolean invalid;
+ 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..dec4b46 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):
diff --git a/paging/common/api/current.txt b/paging/common/api/current.txt
index 903b245..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;
@@ -243,18 +238,15 @@
public abstract class PagedSource<Key, Value> {
ctor public PagedSource();
- method public abstract boolean getInvalid();
+ method public final boolean getInvalid();
method public abstract androidx.paging.PagedSource.KeyProvider<Key,Value> getKeyProvider();
- method public abstract void invalidate();
+ 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);
- property public abstract boolean invalid;
+ 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 ccaf366..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;
@@ -266,18 +261,15 @@
public abstract class PagedSource<Key, Value> {
ctor public PagedSource();
- method public abstract boolean getInvalid();
+ method public final boolean getInvalid();
method public abstract androidx.paging.PagedSource.KeyProvider<Key,Value> getKeyProvider();
- method public abstract void invalidate();
+ 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);
- property public abstract boolean invalid;
+ 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,14 +331,15 @@
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 boolean getInvalid();
method public androidx.paging.PagedSource.KeyProvider<Key,Value> getKeyProvider();
- method public void invalidate();
method public boolean isRetryableError(Throwable error);
method public suspend Object load(androidx.paging.PagedSource.LoadParams<Key> params, kotlin.coroutines.Continuation<? super androidx.paging.PagedSource.LoadResult<Key,Value>> p);
- property public boolean invalid;
property public androidx.paging.PagedSource.KeyProvider<Key,Value> keyProvider;
}
@@ -366,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);
}
@@ -381,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 ccaf366..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;
@@ -266,18 +261,15 @@
public abstract class PagedSource<Key, Value> {
ctor public PagedSource();
- method public abstract boolean getInvalid();
+ method public final boolean getInvalid();
method public abstract androidx.paging.PagedSource.KeyProvider<Key,Value> getKeyProvider();
- method public abstract void invalidate();
+ 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);
- property public abstract boolean invalid;
+ 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,14 +331,15 @@
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 boolean getInvalid();
method public androidx.paging.PagedSource.KeyProvider<Key,Value> getKeyProvider();
- method public void invalidate();
method public boolean isRetryableError(Throwable error);
method public suspend Object load(androidx.paging.PagedSource.LoadParams<Key> params, kotlin.coroutines.Continuation<? super androidx.paging.PagedSource.LoadResult<Key,Value>> p);
- property public boolean invalid;
property public androidx.paging.PagedSource.KeyProvider<Key,Value> keyProvider;
}
@@ -366,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);
}
@@ -381,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 a5b495d..90542b1 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PagedSource.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PagedSource.kt
@@ -16,10 +16,24 @@
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
@@ -122,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]
@@ -146,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>
@@ -182,19 +195,53 @@
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
* [PagedSource] represents changes since it was first instantiated.
*/
- abstract val invalid: Boolean
+ val invalid: Boolean
+ get() = _invalid.get()
/**
- * Signal the [PagedSource] to stop loading, and notify its callback.
+ * Signal the [PagedSource] to stop loading.
*
- * This method should be idempotent. i.e., If [invalidate] has already been called, subsequent
- * calls to this method should have no effect.
+ * This method is idempotent. i.e., If [invalidate] has already been called, subsequent calls to
+ * this method should have no effect.
+ *
+ * TODO(b/137971356): Investigate making this not open when able to remove [PagedSourceWrapper].
*/
- abstract fun invalidate()
+ 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].
@@ -210,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 d18dd40..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.
@@ -32,6 +29,10 @@
class PagedSourceWrapper<Key : Any, Value : Any>(
internal val dataSource: DataSource<Key, Value>
) : PagedSource<Key, Value>() {
+ init {
+ dataSource.addInvalidatedCallback { invalidate() }
+ }
+
override val keyProvider: KeyProvider<Key, Value> = when (dataSource.type) {
DataSource.KeyType.POSITIONAL -> {
@Suppress("UNCHECKED_CAST") // Guaranteed to be the correct key type.
@@ -43,11 +44,6 @@
}
}
- override val invalid: Boolean
- get() = dataSource.isInvalid
-
- override fun invalidate() = dataSource.invalidate()
-
override suspend fun load(params: LoadParams<Key>): LoadResult<Key, Value> {
val loadType = when (params.loadType) {
LoadType.INITIAL -> DataSource.LoadType.INITIAL
@@ -67,72 +63,9 @@
}
override fun isRetryableError(error: Throwable) = dataSource.isRetryableError(error)
-}
-
-/**
- * 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()
+ super.invalidate()
+ dataSource.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 d5cae09..c548b73 100644
--- a/paging/common/src/test/kotlin/androidx/paging/ContiguousPagedListTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/ContiguousPagedListTest.kt
@@ -56,19 +56,10 @@
*/
private inner class TestPagedSource(val listData: List<Item> = ITEMS) :
PagedSource<Int, Item>() {
- private var _invalid = false
-
override val keyProvider = object : KeyProvider.ItemKey<Int, Item>() {
override fun getKey(item: Item) = item.pos
}
- override val invalid: Boolean
- get() = _invalid
-
- override fun invalidate() {
- _invalid = true
- }
-
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Item> {
return when (params.loadType) {
LoadType.INITIAL -> loadInitial(params)
@@ -96,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 28b1658..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
@@ -39,22 +38,13 @@
private val pagedSource = object : PagedSource<Int, String>() {
override val keyProvider = KeyProvider.Positional<String>()
- private var _invalid = false
- override val invalid: Boolean
- get() = _invalid
-
- override fun invalidate() {
- _invalid = true
- }
-
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, String> =
when (params.loadType) {
LoadType.INITIAL -> LoadResult(
0,
0,
data = listOf("a"),
- offset = 0,
- counted = true
+ offset = 0
)
else -> throw NotImplementedError("Test should fail if we get here")
}
@@ -110,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()
@@ -131,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/PagedSourceWrapperTest.kt b/paging/common/src/test/kotlin/androidx/paging/PagedSourceWrapperTest.kt
index eca3a11..3f8b5f2 100644
--- a/paging/common/src/test/kotlin/androidx/paging/PagedSourceWrapperTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/PagedSourceWrapperTest.kt
@@ -20,6 +20,8 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
@RunWith(JUnit4::class)
class PagedSourceWrapperTest {
@@ -44,10 +46,15 @@
override fun getKey(item: String) = item.hashCode()
}
val pagedSource = PagedSourceWrapper(dataSource)
- assert(pagedSource.keyProvider is PagedSource.KeyProvider.ItemKey)
- assert(!pagedSource.invalid)
+ assertTrue { pagedSource.keyProvider is PagedSource.KeyProvider.ItemKey }
+
+ assertFalse { pagedSource.invalid }
+ assertFalse { dataSource.isInvalid }
+
pagedSource.invalidate()
- assert(pagedSource.invalid)
+
+ assertTrue { pagedSource.invalid }
+ assertTrue { dataSource.isInvalid }
}
@Test
@@ -69,30 +76,62 @@
}
}
val pagedSource = PagedSourceWrapper(dataSource)
- assert(pagedSource.keyProvider is PagedSource.KeyProvider.PageKey)
- assert(!pagedSource.invalid)
+ assertTrue { pagedSource.keyProvider is PagedSource.KeyProvider.PageKey }
+
+ assertFalse { pagedSource.invalid }
+ assertFalse { dataSource.isInvalid }
+
pagedSource.invalidate()
- assert(pagedSource.invalid)
+
+ assertTrue { pagedSource.invalid }
+ assertTrue { dataSource.isInvalid }
}
@Test
fun positional() {
- val dataSource = object : PositionalDataSource<String>() {
- override fun loadInitial(
- params: LoadInitialParams,
- callback: LoadInitialCallback<String>
- ) {
- Assert.fail("loadInitial not expected")
- }
-
- override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<String>) {
- Assert.fail("loadRange not expected")
- }
- }
+ val dataSource = createTestPositionalDataSource()
val pagedSource = PagedSourceWrapper(dataSource)
- assert(pagedSource.keyProvider is PagedSource.KeyProvider.Positional)
- assert(!pagedSource.invalid)
+ assertTrue { pagedSource.keyProvider is PagedSource.KeyProvider.Positional }
+ }
+
+ @Test
+ fun invalidateFromPagedSource() {
+ val dataSource = createTestPositionalDataSource()
+ val pagedSource = PagedSourceWrapper(dataSource)
+
+ assertFalse { pagedSource.invalid }
+ assertFalse { dataSource.isInvalid }
+
pagedSource.invalidate()
- assert(pagedSource.invalid)
+
+ assertTrue { pagedSource.invalid }
+ assertTrue { dataSource.isInvalid }
+ }
+
+ @Test
+ fun invalidateFromDataSource() {
+ val dataSource = createTestPositionalDataSource()
+ val pagedSource = PagedSourceWrapper(dataSource)
+
+ assertFalse { pagedSource.invalid }
+ assertFalse { dataSource.isInvalid }
+
+ dataSource.invalidate()
+
+ assertTrue { pagedSource.invalid }
+ assertTrue { dataSource.isInvalid }
+ }
+
+ private fun createTestPositionalDataSource() = object : PositionalDataSource<String>() {
+ override fun loadInitial(
+ params: LoadInitialParams,
+ callback: LoadInitialCallback<String>
+ ) {
+ Assert.fail("loadInitial not expected")
+ }
+
+ override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<String>) {
+ Assert.fail("loadRange not expected")
+ }
}
}
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 cc831a34..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/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/paging/rxjava2/src/test/java/androidx/paging/RxPagedListBuilderTest.kt b/paging/rxjava2/src/test/java/androidx/paging/RxPagedListBuilderTest.kt
index 3cb666c..40152ff 100644
--- a/paging/rxjava2/src/test/java/androidx/paging/RxPagedListBuilderTest.kt
+++ b/paging/rxjava2/src/test/java/androidx/paging/RxPagedListBuilderTest.kt
@@ -23,6 +23,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
+import kotlin.test.assertTrue
@RunWith(JUnit4::class)
class RxPagedListBuilderTest {
@@ -63,9 +64,11 @@
assertEquals(listOf("a", "b"), observer.values().first())
// invalidate triggers second load
- observer.values().first().pagedSource.invalidate()
+ @Suppress("DEPRECATION")
+ observer.values().first().dataSource.invalidate()
scheduler.triggerActions()
observer.assertValueCount(2)
+ assertTrue { observer.values().first().pagedSource.invalid }
assertEquals(listOf("c", "d"), observer.values().last())
}
diff --git a/palette/palette/build.gradle b/palette/palette/build.gradle
index 1a785db..87eb258b 100644
--- a/palette/palette/build.gradle
+++ b/palette/palette/build.gradle
@@ -9,7 +9,7 @@
}
dependencies {
- api("androidx.core:core:1.1.0-rc01")
+ api("androidx.core:core:1.1.0")
implementation("androidx.collection:collection:1.1.0")
annotationProcessor(NULLAWAY)
diff --git a/percentlayout/percentlayout/build.gradle b/percentlayout/percentlayout/build.gradle
index 5b2ac05..e5c8470 100644
--- a/percentlayout/percentlayout/build.gradle
+++ b/percentlayout/percentlayout/build.gradle
@@ -10,7 +10,7 @@
dependencies {
api(ANDROIDX_ANNOTATION)
- implementation("androidx.core:core:1.1.0-rc01")
+ implementation("androidx.core:core:1.1.0")
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
androidTestImplementation(ANDROIDX_TEST_CORE)
diff --git a/preference/build.gradle b/preference/build.gradle
index 43b0f07..8235c7a 100644
--- a/preference/build.gradle
+++ b/preference/build.gradle
@@ -30,7 +30,7 @@
implementation("androidx.annotation:annotation:1.1.0")
api("androidx.appcompat:appcompat:1.1.0-rc01")
// TODO: change to alpha05 after release
- api("androidx.core:core:1.1.0-rc01")
+ api("androidx.core:core:1.1.0")
implementation("androidx.collection:collection:1.0.0")
api("androidx.fragment:fragment:1.1.0-rc01")
api("androidx.recyclerview:recyclerview:1.0.0")
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-selection/api/restricted_1.0.0.txt b/recyclerview/recyclerview-selection/api/restricted_1.0.0.txt
index 8281ab7..358c695 100644
--- a/recyclerview/recyclerview-selection/api/restricted_1.0.0.txt
+++ b/recyclerview/recyclerview-selection/api/restricted_1.0.0.txt
@@ -22,31 +22,6 @@
method public boolean canInitiate(android.view.MotionEvent);
}
- @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public class DefaultSelectionTracker<K> extends androidx.recyclerview.selection.SelectionTracker<K> {
- ctor public DefaultSelectionTracker(String, androidx.recyclerview.selection.ItemKeyProvider, androidx.recyclerview.selection.SelectionTracker.SelectionPredicate, androidx.recyclerview.selection.StorageStrategy<K!>);
- method public void addObserver(androidx.recyclerview.selection.SelectionTracker.SelectionObserver);
- method public void anchorRange(int);
- method public void clearProvisionalSelection();
- method public boolean clearSelection();
- method public void copySelection(androidx.recyclerview.selection.MutableSelection);
- method public boolean deselect(K);
- method public void endRange();
- method public void extendProvisionalRange(int);
- method public void extendRange(int);
- method public androidx.recyclerview.selection.Selection! getSelection();
- method public boolean hasSelection();
- method public boolean isRangeActive();
- method public boolean isSelected(K?);
- method public void mergeProvisionalSelection();
- method public final void onRestoreInstanceState(android.os.Bundle?);
- method public final void onSaveInstanceState(android.os.Bundle);
- method protected void restoreSelection(androidx.recyclerview.selection.Selection);
- method public boolean select(K);
- method public boolean setItemsSelected(Iterable<K!>, boolean);
- method public void setProvisionalSelection(java.util.Set<K!>);
- method public void startRange(int);
- }
-
public abstract class FocusDelegate<K> {
ctor public FocusDelegate();
method public abstract void clearFocus();
diff --git a/recyclerview/recyclerview-selection/api/restricted_1.1.0-alpha07.ignore b/recyclerview/recyclerview-selection/api/restricted_1.1.0-alpha07.ignore
index 3f1f595..21be473 100644
--- a/recyclerview/recyclerview-selection/api/restricted_1.1.0-alpha07.ignore
+++ b/recyclerview/recyclerview-selection/api/restricted_1.1.0-alpha07.ignore
@@ -1,25 +1,3 @@
// Baseline format: 1.0
-AddedAbstractMethod: androidx.recyclerview.selection.SelectionTracker#anchorRange(int):
- Added method androidx.recyclerview.selection.SelectionTracker.anchorRange(int)
-AddedAbstractMethod: androidx.recyclerview.selection.SelectionTracker#clearProvisionalSelection():
- Added method androidx.recyclerview.selection.SelectionTracker.clearProvisionalSelection()
-AddedAbstractMethod: androidx.recyclerview.selection.SelectionTracker#endRange():
- Added method androidx.recyclerview.selection.SelectionTracker.endRange()
-AddedAbstractMethod: androidx.recyclerview.selection.SelectionTracker#extendProvisionalRange(int):
- Added method androidx.recyclerview.selection.SelectionTracker.extendProvisionalRange(int)
-AddedAbstractMethod: androidx.recyclerview.selection.SelectionTracker#extendRange(int):
- Added method androidx.recyclerview.selection.SelectionTracker.extendRange(int)
-AddedAbstractMethod: androidx.recyclerview.selection.SelectionTracker#getAdapterDataObserver():
- Added method androidx.recyclerview.selection.SelectionTracker.getAdapterDataObserver()
-AddedAbstractMethod: androidx.recyclerview.selection.SelectionTracker#isRangeActive():
- Added method androidx.recyclerview.selection.SelectionTracker.isRangeActive()
-AddedAbstractMethod: androidx.recyclerview.selection.SelectionTracker#mergeProvisionalSelection():
- Added method androidx.recyclerview.selection.SelectionTracker.mergeProvisionalSelection()
-AddedAbstractMethod: androidx.recyclerview.selection.SelectionTracker#setProvisionalSelection(java.util.Set<K>):
- Added method androidx.recyclerview.selection.SelectionTracker.setProvisionalSelection(java.util.Set<K>)
-AddedAbstractMethod: androidx.recyclerview.selection.SelectionTracker#startRange(int):
- Added method androidx.recyclerview.selection.SelectionTracker.startRange(int)
-
-
RemovedClass: androidx.recyclerview.selection.AutoScroller:
Removed class androidx.recyclerview.selection.AutoScroller
diff --git a/recyclerview/recyclerview-selection/build.gradle b/recyclerview/recyclerview-selection/build.gradle
index ac2f787..a364be0 100644
--- a/recyclerview/recyclerview-selection/build.gradle
+++ b/recyclerview/recyclerview-selection/build.gradle
@@ -27,7 +27,7 @@
dependencies {
api(project(":recyclerview:recyclerview"))
api("androidx.annotation:annotation:1.1.0")
- api("androidx.core:core:1.1.0-rc01")
+ api("androidx.core:core:1.1.0")
implementation("androidx.collection:collection:1.1.0")
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
diff --git a/recyclerview/recyclerview/build.gradle b/recyclerview/recyclerview/build.gradle
index 16d7734..4ec3faf 100644
--- a/recyclerview/recyclerview/build.gradle
+++ b/recyclerview/recyclerview/build.gradle
@@ -11,7 +11,7 @@
dependencies {
api("androidx.annotation:annotation:1.1.0")
- api("androidx.core:core:1.1.0-rc02")
+ api("androidx.core:core:1.1.0")
implementation("androidx.collection:collection:1.0.0")
api("androidx.customview:customview:1.0.0")
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/androidTest/java/androidx/recyclerview/widget/RecyclerViewAccessibilityLifecycleTest.java b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewAccessibilityLifecycleTest.java
index 0abcd0f..a5bc429 100644
--- a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewAccessibilityLifecycleTest.java
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewAccessibilityLifecycleTest.java
@@ -161,7 +161,7 @@
}
@Test
- public void notClearCustomViewDelegate() throws Throwable {
+ public void notClearCustomViewDelegateAndMaintainItemDelegate() throws Throwable {
final RecyclerView recyclerView = new RecyclerView(getActivity()) {
@Override
boolean isAccessibilityEnabled() {
@@ -203,7 +203,7 @@
recyclerView.setItemViewCacheSize(0); // no cache, directly goes to pool
recyclerView.setLayoutManager(layoutManager);
setRecyclerView(recyclerView);
- mActivityRule.runOnUiThread(new Runnable() {
+ mActivityRule.runOnUiThread(new Runnable() {
@Override
public void run() {
recyclerView.setAdapter(adapter);
@@ -223,9 +223,9 @@
AccessibilityNodeInfo info = recyclerView.getChildAt(i)
.createAccessibilityNodeInfo();
assertTrue("custom delegate sets isChecked", info.isChecked());
- assertFalse(recyclerView.findContainingViewHolder(view).hasAnyOfTheFlags(
- RecyclerView.ViewHolder.FLAG_SET_A11Y_ITEM_DELEGATE));
- assertTrue(delegateCompat.equals(ViewCompat.getAccessibilityDelegate(view)));
+ if (Build.VERSION.SDK_INT >= 19) {
+ assertNotNull(info.getCollectionItemInfo());
+ }
children.add(view);
}
}
@@ -248,9 +248,9 @@
assertTrue(children.contains(view));
AccessibilityNodeInfo info = view.createAccessibilityNodeInfo();
assertTrue("custom delegate sets isChecked", info.isChecked());
- assertFalse(recyclerView.findContainingViewHolder(view).hasAnyOfTheFlags(
- RecyclerView.ViewHolder.FLAG_SET_A11Y_ITEM_DELEGATE));
- assertTrue(delegateCompat.equals(ViewCompat.getAccessibilityDelegate(view)));
+ if (Build.VERSION.SDK_INT >= 19) {
+ assertNotNull(info.getCollectionItemInfo());
+ }
}
}
});
@@ -287,6 +287,7 @@
@Override
public void run() {
recyclerView.setAdapter(adapter);
+
}
});
layoutManager.waitForLayout(1);
@@ -298,8 +299,6 @@
for (int i = 0; i < recyclerView.getChildCount(); i++) {
View view = recyclerView.getChildAt(i);
assertEquals(i, recyclerView.getChildAdapterPosition(view));
- assertTrue(recyclerView.findContainingViewHolder(view).hasAnyOfTheFlags(
- RecyclerView.ViewHolder.FLAG_SET_A11Y_ITEM_DELEGATE));
assertTrue(accessibiltyDelegateIsItemDelegate(recyclerView, view));
AccessibilityNodeInfo info = view.createAccessibilityNodeInfo();
if (Build.VERSION.SDK_INT >= 19) {
@@ -326,8 +325,6 @@
View view = vh.itemView;
assertEquals(RecyclerView.NO_POSITION,
recyclerView.getChildAdapterPosition(view));
- assertFalse(vh.hasAnyOfTheFlags(
- RecyclerView.ViewHolder.FLAG_SET_A11Y_ITEM_DELEGATE));
assertFalse(accessibiltyDelegateIsItemDelegate(recyclerView, view));
}
}
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewNestedScrolling3RequestDisallowInterceptTouchTest.java b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewNestedScrolling3RequestDisallowInterceptTouchTest.java
new file mode 100644
index 0000000..931599f
--- /dev/null
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewNestedScrolling3RequestDisallowInterceptTouchTest.java
@@ -0,0 +1,291 @@
+/*
+ * 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.recyclerview.widget;
+
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+
+import static androidx.test.espresso.action.GeneralLocation.CENTER;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import androidx.annotation.NonNull;
+import androidx.core.view.NestedScrollingParent3;
+import androidx.core.view.NestedScrollingParentHelper;
+import androidx.core.view.ViewCompat;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+import androidx.testutils.SwipeInjector;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Large integration tests that verify that a {@link RecyclerView} interacts with nested scrolling
+ * correctly.
+ */
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class RecyclerViewNestedScrolling3RequestDisallowInterceptTouchTest {
+
+ private static final int ITEM_HEIGHT = 20;
+ private static final int NSV_HEIGHT = 400;
+ private static final int PARENT_HEIGHT = 400;
+ private static final int WIDTH = 400;
+ private static final int SWIPE_DURATION = 300;
+
+ private RecyclerView mRecyclerView;
+ private NestedScrollingSpyView mParent;
+
+ @Rule
+ public final ActivityTestRule<TestContentViewActivity> mActivityTestRule;
+
+ public RecyclerViewNestedScrolling3RequestDisallowInterceptTouchTest() {
+ mActivityTestRule = new ActivityTestRule<>(TestContentViewActivity.class);
+ }
+
+ @Before
+ public void setUp() throws Throwable {
+ setup();
+ attachToActivity();
+ }
+
+ @Test
+ public void parentConsumes1pxRvConsumes0px() {
+ mParent.consumeX = 0;
+ mParent.consumeY = 1;
+
+ int touchSlop = ViewConfiguration.get(mRecyclerView.getContext()).getScaledTouchSlop();
+ // RecyclerView consumes nothing because we scroll up, and we're already at the top
+ swipeVertically(touchSlop + 100);
+
+ verify(mParent, atLeastOnce()).requestDisallowInterceptTouchEvent(eq(true));
+ }
+
+ @Test
+ public void parentConsumes0pxRvConsumes1px() {
+ mParent.consumeX = 0;
+ mParent.consumeY = 0;
+
+ int touchSlop = ViewConfiguration.get(mRecyclerView.getContext()).getScaledTouchSlop();
+ // RecyclerView consumes all because we scroll down, and we're already at the top
+ swipeVertically(-touchSlop - 1);
+
+ verify(mParent, atLeastOnce()).requestDisallowInterceptTouchEvent(eq(true));
+ }
+
+ @Test
+ public void parentConsumes0pxRvConsumes0px() {
+ mParent.consumeX = 0;
+ mParent.consumeY = 0;
+
+ int touchSlop = ViewConfiguration.get(mRecyclerView.getContext()).getScaledTouchSlop();
+ // RecyclerView consumes nothing because we scroll up, and we're already at the top
+ swipeVertically(touchSlop + 100);
+
+ verify(mParent, never()).requestDisallowInterceptTouchEvent(eq(true));
+ }
+
+ private void setup() {
+ Context context = mActivityTestRule.getActivity();
+
+ mRecyclerView = new RecyclerView(context);
+ mRecyclerView.setLayoutParams(new ViewGroup.LayoutParams(WIDTH, NSV_HEIGHT));
+ mRecyclerView.setBackgroundColor(0xFF0000FF);
+ mRecyclerView.setLayoutManager(new LinearLayoutManager(context));
+ mRecyclerView.setAdapter(new TestAdapter(100, ITEM_HEIGHT));
+
+ mParent = spy(new NestedScrollingSpyView(context));
+ mParent.setLayoutParams(new ViewGroup.LayoutParams(WIDTH, PARENT_HEIGHT));
+ mParent.setBackgroundColor(0xFF0000FF);
+ mParent.addView(mRecyclerView);
+ }
+
+ private void attachToActivity() throws Throwable {
+ final TestContentView testContentView = mActivityTestRule.getActivity().getContentView();
+ testContentView.expectLayouts(1);
+ mActivityTestRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ testContentView.addView(mParent);
+ }
+ });
+ testContentView.awaitLayouts(2);
+ }
+
+ private void swipeVertically(int dy) {
+ SwipeInjector swiper = new SwipeInjector(InstrumentationRegistry.getInstrumentation());
+ swiper.startDrag(CENTER, mRecyclerView);
+ swiper.dragBy(0, dy, SWIPE_DURATION);
+ swiper.finishDrag();
+ }
+
+ @SuppressWarnings("NullableProblems")
+ public static class NestedScrollingSpyView extends FrameLayout implements
+ NestedScrollingParent3 {
+
+ private final NestedScrollingParentHelper mNestedScrollingParentHelper;
+ private final int[] mNestedScrollingV2ConsumedCompat = new int[2];
+
+ // The amount of pixels to consume during a nested scroll
+ // Use only positive values, value is used in both directions
+ public int consumeX = 0;
+ public int consumeY = 0;
+
+ public NestedScrollingSpyView(Context context) {
+ super(context);
+ mNestedScrollingParentHelper = new NestedScrollingParentHelper(this);
+ }
+
+ // NestedScrollingParent3
+
+ @Override
+ public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed,
+ int dxUnconsumed, int dyUnconsumed, int type, @NonNull int[] consumed) {
+ if (consumeX != 0) {
+ consumed[0] += dxUnconsumed > 0
+ ? Math.min(consumeX, dxUnconsumed)
+ : Math.max(-consumeX, dxUnconsumed);
+ }
+ if (consumeY != 0) {
+ consumed[1] += dyUnconsumed > 0
+ ? Math.min(consumeY, dyUnconsumed)
+ : Math.max(-consumeY, dyUnconsumed);
+ }
+ }
+
+ // NestedScrollingParent2
+
+ @Override
+ public boolean onStartNestedScroll(@NonNull View child, @NonNull View target, int axes,
+ int type) {
+ return onStartNestedScroll(child, target, axes);
+ }
+
+ @Override
+ public void onNestedScrollAccepted(@NonNull View child, @NonNull View target, int axes,
+ int type) {
+ onNestedScrollAccepted(child, target, axes);
+ }
+
+ @Override
+ public void onStopNestedScroll(@NonNull View target, int type) {
+ onStopNestedScroll(target);
+ }
+
+ @Override
+ public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed,
+ int dxUnconsumed, int dyUnconsumed, int type) {
+ onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type,
+ mNestedScrollingV2ConsumedCompat);
+ }
+
+ @Override
+ public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed,
+ int type) {
+ onNestedPreScroll(target, dx, dy, consumed);
+ }
+
+ // NestedScrollingParent1
+
+ @Override
+ public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
+ return true;
+ }
+
+ @Override
+ public void onNestedScrollAccepted(View child, View target, int axes) {
+ mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, axes);
+ }
+
+ @Override
+ public void onStopNestedScroll(View child) {
+ mNestedScrollingParentHelper.onStopNestedScroll(child);
+ }
+
+ @Override
+ public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed,
+ int dyUnconsumed) {
+ onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,
+ ViewCompat.TYPE_TOUCH, mNestedScrollingV2ConsumedCompat);
+ }
+
+ @Override
+ public void onNestedPreScroll(@NonNull View target, int dx, int dy,
+ @NonNull int[] consumed) {
+ ViewCompat.dispatchNestedPreScroll(this, dx, dy, consumed, null);
+ }
+
+ @Override
+ public boolean onNestedFling(@NonNull View target, float velocityX, float velocityY,
+ boolean consumed) {
+ return ViewCompat.dispatchNestedFling(this, velocityX, velocityY, consumed);
+ }
+
+ @Override
+ public boolean onNestedPreFling(@NonNull View target, float velocityX, float velocityY) {
+ return ViewCompat.dispatchNestedPreFling(this, velocityX, velocityY);
+ }
+
+ @Override
+ public int getNestedScrollAxes() {
+ return mNestedScrollingParentHelper.getNestedScrollAxes();
+ }
+ }
+
+ private class TestAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
+
+ private int mItemHeight;
+ private int mItemCount;
+
+ TestAdapter(int itemCount, int itemHeight) {
+ mItemHeight = itemHeight;
+ mItemCount = itemCount;
+ }
+
+ @NonNull
+ @Override
+ public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ View view = new View(parent.getContext());
+ view.setLayoutParams(new ViewGroup.LayoutParams(MATCH_PARENT, mItemHeight));
+ return new RecyclerView.ViewHolder(view) {};
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
+ }
+
+ @Override
+ public int getItemCount() {
+ return mItemCount;
+ }
+ }
+
+}
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 4dc8c31..74cd10a 100644
--- a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java
+++ b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java
@@ -1953,6 +1953,7 @@
TYPE_TOUCH, mReusableIntPair);
unconsumedX -= mReusableIntPair[0];
unconsumedY -= mReusableIntPair[1];
+ boolean consumedNestedScroll = mReusableIntPair[0] != 0 || mReusableIntPair[1] != 0;
// Update the last touch co-ords, taking any scroll offset into account
mLastTouchX -= mScrollOffset[0];
@@ -1972,7 +1973,7 @@
if (!awakenScrollBars()) {
invalidate();
}
- return consumedX != 0 || consumedY != 0;
+ return consumedNestedScroll || consumedX != 0 || consumedY != 0;
}
/**
@@ -6288,14 +6289,16 @@
ViewCompat.setImportantForAccessibility(itemView,
ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
}
- AccessibilityDelegateCompat delegate =
- ViewCompat.getAccessibilityDelegate(itemView);
- if (delegate == null
- || delegate.getClass().equals(AccessibilityDelegateCompat.class)) {
- holder.addFlags(ViewHolder.FLAG_SET_A11Y_ITEM_DELEGATE);
- ViewCompat.setAccessibilityDelegate(itemView,
- mAccessibilityDelegate.getItemDelegate());
+ if (mAccessibilityDelegate == null) {
+ return;
}
+ RecyclerViewAccessibilityDelegate.ItemDelegate itemDelegate =
+ mAccessibilityDelegate.mItemDelegate;
+ // If there was already an a11y delegate set on the itemView, store it in the
+ // itemDelegate and then set the itemDelegate as the a11y delegate.
+ itemDelegate.setOriginalDelegateForItem(itemView,
+ ViewCompat.getAccessibilityDelegate(itemView));
+ ViewCompat.setAccessibilityDelegate(itemView, itemDelegate);
}
}
@@ -6504,9 +6507,12 @@
*/
void addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled) {
clearNestedRecyclerViewIfNotNested(holder);
- if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_SET_A11Y_ITEM_DELEGATE)) {
- holder.setFlags(0, ViewHolder.FLAG_SET_A11Y_ITEM_DELEGATE);
- ViewCompat.setAccessibilityDelegate(holder.itemView, null);
+ View itemView = holder.itemView;
+ if (mAccessibilityDelegate != null) {
+ AccessibilityDelegateCompat originalDelegate = mAccessibilityDelegate
+ .mItemDelegate.getAndRemoveOriginalDelegateForItem(itemView);
+ // Set the a11y delegate back to whatever the original delegate was.
+ ViewCompat.setAccessibilityDelegate(itemView, originalDelegate);
}
if (dispatchRecycled) {
dispatchViewRecycled(holder);
@@ -11028,12 +11034,6 @@
*/
static final int FLAG_BOUNCED_FROM_HIDDEN_LIST = 1 << 13;
- /**
- * Flags that RecyclerView assigned {@link RecyclerViewAccessibilityDelegate
- * #getItemDelegate()} in onBindView when app does not provide a delegate.
- */
- static final int FLAG_SET_A11Y_ITEM_DELEGATE = 1 << 14;
-
int mFlags;
private static final List<Object> FULLUPDATE_PAYLOADS = Collections.emptyList();
diff --git a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerViewAccessibilityDelegate.java b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerViewAccessibilityDelegate.java
index adfbb43..920929b 100644
--- a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerViewAccessibilityDelegate.java
+++ b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerViewAccessibilityDelegate.java
@@ -24,6 +24,9 @@
import androidx.core.view.AccessibilityDelegateCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
+import java.util.Map;
+import java.util.WeakHashMap;
+
/**
* The AccessibilityDelegate used by RecyclerView.
* <p>
@@ -31,7 +34,7 @@
*/
public class RecyclerViewAccessibilityDelegate extends AccessibilityDelegateCompat {
final RecyclerView mRecyclerView;
- final AccessibilityDelegateCompat mItemDelegate;
+ final ItemDelegate mItemDelegate;
public RecyclerViewAccessibilityDelegate(@NonNull RecyclerView recyclerView) {
@@ -94,6 +97,7 @@
*/
public static class ItemDelegate extends AccessibilityDelegateCompat {
final RecyclerViewAccessibilityDelegate mRecyclerViewDelegate;
+ private Map<View, AccessibilityDelegateCompat> mOriginalItemDelegates = new WeakHashMap<>();
/**
* Creates an item delegate for the given {@code RecyclerViewAccessibilityDelegate}.
@@ -104,6 +108,17 @@
mRecyclerViewDelegate = recyclerViewDelegate;
}
+ void setOriginalDelegateForItem(View itemView, AccessibilityDelegateCompat delegate) {
+ if (delegate != null) {
+ mOriginalItemDelegates.put(itemView, delegate);
+ }
+
+ }
+
+ AccessibilityDelegateCompat getAndRemoveOriginalDelegateForItem(View itemView) {
+ return mOriginalItemDelegates.remove(itemView);
+ }
+
@Override
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
super.onInitializeAccessibilityNodeInfo(host, info);
@@ -111,6 +126,10 @@
&& mRecyclerViewDelegate.mRecyclerView.getLayoutManager() != null) {
mRecyclerViewDelegate.mRecyclerView.getLayoutManager()
.onInitializeAccessibilityNodeInfoForItem(host, info);
+ AccessibilityDelegateCompat originalDelegate = mOriginalItemDelegates.get(host);
+ if (originalDelegate != null) {
+ originalDelegate.onInitializeAccessibilityNodeInfo(host, info);
+ }
}
}
@@ -121,8 +140,14 @@
}
if (!mRecyclerViewDelegate.shouldIgnore()
&& mRecyclerViewDelegate.mRecyclerView.getLayoutManager() != null) {
- return mRecyclerViewDelegate.mRecyclerView.getLayoutManager()
- .performAccessibilityActionForItem(host, action, args);
+ AccessibilityDelegateCompat originalDelegate = mOriginalItemDelegates.get(host);
+ if (originalDelegate != null
+ && originalDelegate.performAccessibilityAction(host, action, args)) {
+ return true;
+ } else {
+ return mRecyclerViewDelegate.mRecyclerView.getLayoutManager()
+ .performAccessibilityActionForItem(host, action, args);
+ }
}
return false;
}
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/compiler/src/main/kotlin/androidx/room/ext/element_ext.kt b/room/compiler/src/main/kotlin/androidx/room/ext/element_ext.kt
index e47b44d..1e02fad 100644
--- a/room/compiler/src/main/kotlin/androidx/room/ext/element_ext.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/ext/element_ext.kt
@@ -32,6 +32,7 @@
import javax.lang.model.element.Modifier
import javax.lang.model.element.TypeElement
import javax.lang.model.element.VariableElement
+import javax.lang.model.type.ExecutableType
import javax.lang.model.type.TypeKind
import javax.lang.model.type.TypeMirror
import javax.lang.model.type.WildcardType
@@ -380,12 +381,12 @@
/**
* Finds the Kotlin's suspend function return type by inspecting the type param of the Continuation
- * parameter of the function. This method assumes the executable element is a suspend function.
+ * parameter of the function. This method assumes the executable type is a suspend function.
* @see KotlinMetadataElement.isSuspendFunction
*/
-fun ExecutableElement.getSuspendFunctionReturnType(): TypeMirror {
+fun ExecutableType.getSuspendFunctionReturnType(): TypeMirror {
// the continuation parameter is always the last parameter of a suspend function and it only has
// one type parameter, e.g Continuation<? super T>
- val typeParam = MoreTypes.asDeclared(parameters.last().asType()).typeArguments.first()
+ val typeParam = MoreTypes.asDeclared(parameterTypes.last()).typeArguments.first()
return typeParam.extendsBoundOrSelf() // reduce the type param
}
\ No newline at end of file
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/MethodProcessorDelegate.kt b/room/compiler/src/main/kotlin/androidx/room/processor/MethodProcessorDelegate.kt
index 4d8cfa7..6274c80 100644
--- a/room/compiler/src/main/kotlin/androidx/room/processor/MethodProcessorDelegate.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/MethodProcessorDelegate.kt
@@ -181,7 +181,10 @@
}
}
- override fun extractReturnType() = executableElement.getSuspendFunctionReturnType()
+ override fun extractReturnType(): TypeMirror {
+ val asMember = context.processingEnv.typeUtils.asMemberOf(containing, executableElement)
+ return MoreTypes.asExecutable(asMember).getSuspendFunctionReturnType()
+ }
override fun extractParams() =
executableElement.parameters.filterNot { it == continuationParam }
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/solver/TypeAdapterStore.kt b/room/compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt
index 8153400..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
@@ -81,6 +82,7 @@
import androidx.room.solver.types.BoxedBooleanToBoxedIntConverter
import androidx.room.solver.types.BoxedPrimitiveColumnTypeAdapter
import androidx.room.solver.types.ByteArrayColumnTypeAdapter
+import androidx.room.solver.types.ByteBufferColumnTypeAdapter
import androidx.room.solver.types.ColumnTypeAdapter
import androidx.room.solver.types.CompositeAdapter
import androidx.room.solver.types.CompositeTypeConverter
@@ -155,6 +157,7 @@
.forEach(::addColumnAdapter)
addColumnAdapter(StringColumnTypeAdapter(context.processingEnv))
addColumnAdapter(ByteArrayColumnTypeAdapter(context.processingEnv))
+ addColumnAdapter(ByteBufferColumnTypeAdapter(context.processingEnv))
PrimitiveBooleanToIntConverter.create(context.processingEnv).forEach(::addTypeConverter)
BoxedBooleanToBoxedIntConverter.create(context.processingEnv)
.forEach(::addTypeConverter)
@@ -173,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/CoroutineFlowResultBinder.kt b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/CoroutineFlowResultBinder.kt
new file mode 100644
index 0000000..bc2fb8e
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/CoroutineFlowResultBinder.kt
@@ -0,0 +1,114 @@
+/*
+ * 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
+ ) {
+ val callableImpl = CallableTypeSpecBuilder(typeArg.typeName()) {
+ createRunQueryAndReturnStatements(
+ builder = this,
+ roomSQLiteQueryVar = roomSQLiteQueryVar,
+ canReleaseQuery = canReleaseQuery,
+ dbField = dbField,
+ inTransaction = inTransaction,
+ scope = scope)
+ }.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
+ ) {
+ 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)",
+ AndroidTypeNames.CURSOR,
+ cursorVar,
+ RoomTypeNames.DB_UTIL,
+ dbField,
+ roomSQLiteQueryVar,
+ if (shouldCopyCursor) "true" else "false")
+ 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/types/ByteBufferColumnTypeAdapter.kt b/room/compiler/src/main/kotlin/androidx/room/solver/types/ByteBufferColumnTypeAdapter.kt
new file mode 100644
index 0000000..95dfb99
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/types/ByteBufferColumnTypeAdapter.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.room.solver.types
+
+import androidx.room.ext.L
+import androidx.room.ext.T
+import androidx.room.parser.SQLTypeAffinity
+import androidx.room.solver.CodeGenScope
+import com.squareup.javapoet.TypeName
+import java.nio.ByteBuffer
+import javax.annotation.processing.ProcessingEnvironment
+
+class ByteBufferColumnTypeAdapter(env: ProcessingEnvironment) : ColumnTypeAdapter(
+ out = env.elementUtils.getTypeElement("java.nio.ByteBuffer").asType(),
+ typeAffinity = SQLTypeAffinity.BLOB
+) {
+ override fun readFromCursor(
+ outVarName: String,
+ cursorVarName: String,
+ indexVarName: String,
+ scope: CodeGenScope
+ ) {
+ scope.builder()
+ .addStatement("$L = $T.wrap($L.getBlob($L))",
+ outVarName, TypeName.get(ByteBuffer::class.java), cursorVarName, indexVarName)
+ }
+
+ override fun bindToStmt(
+ stmtName: String,
+ indexVarName: String,
+ valueVarName: String,
+ scope: CodeGenScope
+ ) {
+ scope.builder().apply {
+ beginControlFlow("if ($L == null)", valueVarName)
+ .addStatement("$L.bindNull($L)", stmtName, indexVarName)
+ nextControlFlow("else")
+ .addStatement("$L.bindBlob($L, $L.array())", stmtName, indexVarName, valueVarName)
+ endControlFlow()
+ }
+ }
+}
\ No newline at end of file
diff --git a/room/compiler/src/main/kotlin/androidx/room/vo/RelationCollector.kt b/room/compiler/src/main/kotlin/androidx/room/vo/RelationCollector.kt
index 3fca29d..ac182c2 100644
--- a/room/compiler/src/main/kotlin/androidx/room/vo/RelationCollector.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/vo/RelationCollector.kt
@@ -38,15 +38,14 @@
import androidx.room.writer.QueryWriter
import androidx.room.writer.RelationCollectorMethodWriter
import com.google.auto.common.MoreTypes
-import com.squareup.javapoet.ArrayTypeName
import com.squareup.javapoet.ClassName
import com.squareup.javapoet.CodeBlock
import com.squareup.javapoet.ParameterizedTypeName
import com.squareup.javapoet.TypeName
import stripNonJava
+import java.nio.ByteBuffer
import java.util.ArrayList
import java.util.HashSet
-import javax.lang.model.type.TypeKind
import javax.lang.model.type.TypeMirror
/**
@@ -160,15 +159,21 @@
keyTypeName
}
val tmpVar = scope.getTmpVar("_tmpKey")
- if (relation.parentField.nonNull) {
- addStatement("final $T $L = $L.$L($L)",
+ fun addKeyReadStatement() {
+ if (keyTypeName == TypeName.get(ByteBuffer::class.java)) {
+ addStatement("final $T $L = $T.wrap($L.$L($L))",
+ keyType, tmpVar, keyTypeName, cursorVarName, cursorGetter, indexVar)
+ } else {
+ addStatement("final $T $L = $L.$L($L)",
keyType, tmpVar, cursorVarName, cursorGetter, indexVar)
+ }
this.postRead(tmpVar)
+ }
+ if (relation.parentField.nonNull) {
+ addKeyReadStatement()
} else {
beginControlFlow("if (!$L.isNull($L))", cursorVarName, indexVar).apply {
- addStatement("final $T $L = $L.$L($L)",
- keyType, tmpVar, cursorVarName, cursorGetter, indexVar)
- this.postRead(tmpVar)
+ addKeyReadStatement()
}
endControlFlow()
}
@@ -219,92 +224,13 @@
relations: List<Relation>
): List<RelationCollector> {
return relations.map { relation ->
- // decide on the affinity
val context = baseContext.fork(
element = relation.field.element,
forceSuppressedWarnings = setOf(Warning.CURSOR_MISMATCH))
-
- fun checkAffinity(
- first: SQLTypeAffinity?,
- second: SQLTypeAffinity?,
- onAffinityMismatch: () -> Unit
- ) = if (first != null && first == second) {
- first
- } else {
- onAffinityMismatch()
- SQLTypeAffinity.TEXT
- }
-
- val parentAffinity = relation.parentField.cursorValueReader?.affinity()
- val childAffinity = relation.entityField.cursorValueReader?.affinity()
- val junctionParentAffinity =
- relation.junction?.parentField?.cursorValueReader?.affinity()
- val junctionChildAffinity =
- relation.junction?.entityField?.cursorValueReader?.affinity()
- val affinity = if (relation.junction != null) {
- checkAffinity(childAffinity, junctionChildAffinity) {
- context.logger.w(Warning.RELATION_TYPE_MISMATCH, relation.field.element,
- relationJunctionChildAffinityMismatch(
- childColumn = relation.entityField.columnName,
- junctionChildColumn = relation.junction.entityField.columnName,
- childAffinity = childAffinity,
- junctionChildAffinity = junctionChildAffinity))
- }
- checkAffinity(parentAffinity, junctionParentAffinity) {
- context.logger.w(Warning.RELATION_TYPE_MISMATCH, relation.field.element,
- relationJunctionParentAffinityMismatch(
- parentColumn = relation.parentField.columnName,
- junctionParentColumn = relation.junction.parentField.columnName,
- parentAffinity = parentAffinity,
- junctionParentAffinity = junctionParentAffinity))
- }
- } else {
- checkAffinity(parentAffinity, childAffinity) {
- context.logger.w(Warning.RELATION_TYPE_MISMATCH, relation.field.element,
- relationAffinityMismatch(
- parentColumn = relation.parentField.columnName,
- childColumn = relation.entityField.columnName,
- parentAffinity = parentAffinity,
- childAffinity = childAffinity))
- }
- }
+ val affinity = affinityFor(context, relation)
val keyType = keyTypeFor(context, affinity)
- val (relationTypeName, isRelationCollection) =
- if (relation.field.typeName is ParameterizedTypeName) {
- val paramType = relation.field.typeName as ParameterizedTypeName
- val paramTypeName = if (paramType.rawType == CommonTypeNames.LIST) {
- ParameterizedTypeName.get(ClassName.get(ArrayList::class.java),
- relation.pojoTypeName)
- } else if (paramType.rawType == CommonTypeNames.SET) {
- ParameterizedTypeName.get(ClassName.get(HashSet::class.java),
- relation.pojoTypeName)
- } else {
- ParameterizedTypeName.get(ClassName.get(ArrayList::class.java),
- relation.pojoTypeName)
- }
- paramTypeName to true
- } else {
- relation.pojoTypeName to false
- }
-
- val canUseLongSparseArray = context.processingEnv.elementUtils
- .getTypeElement(CollectionTypeNames.LONG_SPARSE_ARRAY.toString()) != null
- val canUseArrayMap = context.processingEnv.elementUtils
- .getTypeElement(CollectionTypeNames.ARRAY_MAP.toString()) != null
- val tmpMapType = when {
- canUseLongSparseArray && affinity == SQLTypeAffinity.INTEGER -> {
- ParameterizedTypeName.get(CollectionTypeNames.LONG_SPARSE_ARRAY,
- relationTypeName)
- }
- canUseArrayMap -> {
- ParameterizedTypeName.get(CollectionTypeNames.ARRAY_MAP,
- keyType, relationTypeName)
- }
- else -> {
- ParameterizedTypeName.get(ClassName.get(java.util.HashMap::class.java),
- keyType, relationTypeName)
- }
- }
+ val (relationTypeName, isRelationCollection) = relationTypeFor(relation)
+ val tmpMapType = temporaryMapTypeFor(context, affinity, keyType, relationTypeName)
val loadAllQuery = relation.createLoadAllSql()
val parsedQuery = SqlParser.parse(loadAllQuery)
@@ -340,7 +266,7 @@
sqlName = RelationCollectorMethodWriter.KEY_SET_VARIABLE,
type = keySet,
queryParamAdapter = context.typeAdapterStore.findQueryParameterAdapter(
- keySet)
+ keySet)
)
}
@@ -392,26 +318,121 @@
}.filterNotNull()
}
+ // Gets and check the affinity of the relating columns.
+ private fun affinityFor(context: Context, relation: Relation): SQLTypeAffinity {
+ fun checkAffinity(
+ first: SQLTypeAffinity?,
+ second: SQLTypeAffinity?,
+ onAffinityMismatch: () -> Unit
+ ) = if (first != null && first == second) {
+ first
+ } else {
+ onAffinityMismatch()
+ SQLTypeAffinity.TEXT
+ }
+
+ val parentAffinity = relation.parentField.cursorValueReader?.affinity()
+ val childAffinity = relation.entityField.cursorValueReader?.affinity()
+ val junctionParentAffinity =
+ relation.junction?.parentField?.cursorValueReader?.affinity()
+ val junctionChildAffinity =
+ relation.junction?.entityField?.cursorValueReader?.affinity()
+ return if (relation.junction != null) {
+ checkAffinity(childAffinity, junctionChildAffinity) {
+ context.logger.w(Warning.RELATION_TYPE_MISMATCH, relation.field.element,
+ relationJunctionChildAffinityMismatch(
+ childColumn = relation.entityField.columnName,
+ junctionChildColumn = relation.junction.entityField.columnName,
+ childAffinity = childAffinity,
+ junctionChildAffinity = junctionChildAffinity))
+ }
+ checkAffinity(parentAffinity, junctionParentAffinity) {
+ context.logger.w(Warning.RELATION_TYPE_MISMATCH, relation.field.element,
+ relationJunctionParentAffinityMismatch(
+ parentColumn = relation.parentField.columnName,
+ junctionParentColumn = relation.junction.parentField.columnName,
+ parentAffinity = parentAffinity,
+ junctionParentAffinity = junctionParentAffinity))
+ }
+ } else {
+ checkAffinity(parentAffinity, childAffinity) {
+ context.logger.w(Warning.RELATION_TYPE_MISMATCH, relation.field.element,
+ relationAffinityMismatch(
+ parentColumn = relation.parentField.columnName,
+ childColumn = relation.entityField.columnName,
+ parentAffinity = parentAffinity,
+ childAffinity = childAffinity))
+ }
+ }
+ }
+
+ // Gets the resulting relation type name. (i.e. the Pojo's @Relation field type name.)
+ private fun relationTypeFor(relation: Relation) =
+ if (relation.field.typeName is ParameterizedTypeName) {
+ val paramType = relation.field.typeName as ParameterizedTypeName
+ val paramTypeName = if (paramType.rawType == CommonTypeNames.LIST) {
+ ParameterizedTypeName.get(ClassName.get(ArrayList::class.java),
+ relation.pojoTypeName)
+ } else if (paramType.rawType == CommonTypeNames.SET) {
+ ParameterizedTypeName.get(ClassName.get(HashSet::class.java),
+ relation.pojoTypeName)
+ } else {
+ ParameterizedTypeName.get(ClassName.get(ArrayList::class.java),
+ relation.pojoTypeName)
+ }
+ paramTypeName to true
+ } else {
+ relation.pojoTypeName to false
+ }
+
+ // Gets the type name of the temporary key map.
+ private fun temporaryMapTypeFor(
+ context: Context,
+ affinity: SQLTypeAffinity,
+ keyType: TypeName,
+ relationTypeName: TypeName
+ ): ParameterizedTypeName {
+ val canUseLongSparseArray = context.processingEnv.elementUtils
+ .getTypeElement(CollectionTypeNames.LONG_SPARSE_ARRAY.toString()) != null
+ val canUseArrayMap = context.processingEnv.elementUtils
+ .getTypeElement(CollectionTypeNames.ARRAY_MAP.toString()) != null
+ return when {
+ canUseLongSparseArray && affinity == SQLTypeAffinity.INTEGER -> {
+ ParameterizedTypeName.get(CollectionTypeNames.LONG_SPARSE_ARRAY,
+ relationTypeName)
+ }
+ canUseArrayMap -> {
+ ParameterizedTypeName.get(CollectionTypeNames.ARRAY_MAP,
+ keyType, relationTypeName)
+ }
+ else -> {
+ ParameterizedTypeName.get(ClassName.get(java.util.HashMap::class.java),
+ keyType, relationTypeName)
+ }
+ }
+ }
+
+ // Gets the type mirror of the relationship key.
private fun keyTypeMirrorFor(context: Context, affinity: SQLTypeAffinity): TypeMirror {
- val types = context.processingEnv.typeUtils
val elements = context.processingEnv.elementUtils
return when (affinity) {
SQLTypeAffinity.INTEGER -> elements.getTypeElement("java.lang.Long").asType()
SQLTypeAffinity.REAL -> elements.getTypeElement("java.lang.Double").asType()
SQLTypeAffinity.TEXT -> context.COMMON_TYPES.STRING
- SQLTypeAffinity.BLOB -> types.getArrayType(types.getPrimitiveType(TypeKind.BYTE))
+ SQLTypeAffinity.BLOB -> elements.getTypeElement("java.nio.ByteBuffer").asType()
else -> {
context.COMMON_TYPES.STRING
}
}
}
+ // Gets the type name of the relationship key.
private fun keyTypeFor(context: Context, affinity: SQLTypeAffinity): TypeName {
return when (affinity) {
SQLTypeAffinity.INTEGER -> TypeName.LONG.box()
SQLTypeAffinity.REAL -> TypeName.DOUBLE.box()
SQLTypeAffinity.TEXT -> TypeName.get(String::class.java)
- SQLTypeAffinity.BLOB -> ArrayTypeName.of(TypeName.BYTE)
+ SQLTypeAffinity.BLOB -> TypeName.get(ByteBuffer::class.java)
else -> {
// no affinity select from type
context.COMMON_TYPES.STRING.typeName()
diff --git a/room/compiler/src/test/data/common/input/coroutines/Channel.java b/room/compiler/src/test/data/common/input/coroutines/Channel.java
new file mode 100644
index 0000000..092782a
--- /dev/null
+++ b/room/compiler/src/test/data/common/input/coroutines/Channel.java
@@ -0,0 +1,21 @@
+/*
+ * 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 kotlinx.coroutines.channels;
+
+public interface Channel<T> extends SendChannel<T>, ReceiveChannel<T> {
+
+}
\ No newline at end of file
diff --git a/room/compiler/src/test/data/common/input/coroutines/ReceiveChannel.java b/room/compiler/src/test/data/common/input/coroutines/ReceiveChannel.java
new file mode 100644
index 0000000..a822127
--- /dev/null
+++ b/room/compiler/src/test/data/common/input/coroutines/ReceiveChannel.java
@@ -0,0 +1,21 @@
+/*
+ * 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 kotlinx.coroutines.channels;
+
+public interface ReceiveChannel<T> {
+
+}
\ No newline at end of file
diff --git a/room/compiler/src/test/data/common/input/coroutines/SendChannel.java b/room/compiler/src/test/data/common/input/coroutines/SendChannel.java
new file mode 100644
index 0000000..a8c4b11
--- /dev/null
+++ b/room/compiler/src/test/data/common/input/coroutines/SendChannel.java
@@ -0,0 +1,21 @@
+/*
+ * 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 kotlinx.coroutines.channels;
+
+public interface SendChannel<T> {
+
+}
\ No newline at end of file
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/BaseDao.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/dao/BaseDao.kt
index 1653a0e..3419b5b 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/dao/BaseDao.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/dao/BaseDao.kt
@@ -19,7 +19,9 @@
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
+import androidx.room.RawQuery
import androidx.room.Update
+import androidx.sqlite.db.SupportSQLiteQuery
interface BaseDao<T> {
@@ -46,4 +48,13 @@
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun suspendInsert(t: T)
+
+ @Update
+ suspend fun suspendUpdate(t: T)
+
+ @Delete
+ suspend fun suspendDelete(t: T)
+
+ @RawQuery
+ suspend fun rawQuery(query: SupportSQLiteQuery): List<T>
}
\ No newline at end of file
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..6cadb2f 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
@@ -43,6 +43,7 @@
import io.reactivex.Flowable
import io.reactivex.Maybe
import io.reactivex.Single
+import kotlinx.coroutines.flow.Flow
import java.util.Date
@Dao
@@ -376,4 +377,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/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 a6c6aba..f32c5cc 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
@@ -26,6 +26,7 @@
import androidx.room.integration.testapp.dao.PetDao;
import androidx.room.integration.testapp.dao.ProductDao;
import androidx.room.integration.testapp.dao.RawDao;
+import androidx.room.integration.testapp.dao.RobotsDao;
import androidx.room.integration.testapp.dao.SchoolDao;
import androidx.room.integration.testapp.dao.SpecificDogDao;
import androidx.room.integration.testapp.dao.ToyDao;
@@ -37,22 +38,26 @@
import androidx.room.integration.testapp.vo.Day;
import androidx.room.integration.testapp.vo.FriendsJunction;
import androidx.room.integration.testapp.vo.FunnyNamedEntity;
+import androidx.room.integration.testapp.vo.Hivemind;
import androidx.room.integration.testapp.vo.House;
import androidx.room.integration.testapp.vo.Pet;
import androidx.room.integration.testapp.vo.PetCouple;
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.School;
import androidx.room.integration.testapp.vo.Toy;
import androidx.room.integration.testapp.vo.User;
+import java.nio.ByteBuffer;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
+import java.util.UUID;
@Database(entities = {User.class, Pet.class, School.class, PetCouple.class, Toy.class,
BlobEntity.class, Product.class, FunnyNamedEntity.class, House.class,
- FriendsJunction.class},
+ FriendsJunction.class, Hivemind.class, Robot.class},
views = {PetWithUser.class},
version = 1, exportSchema = false)
@TypeConverters(TestDatabase.Converters.class)
@@ -70,6 +75,7 @@
public abstract FunnyNamedDao getFunnyNamedDao();
public abstract RawDao getRawDao();
public abstract UserHouseDao getUserHouseDao();
+ public abstract RobotsDao getRobotsDao();
@SuppressWarnings("unused")
public static class Converters {
@@ -106,5 +112,21 @@
}
return result;
}
+
+ @TypeConverter
+ public UUID asUuid(byte[] bytes) {
+ ByteBuffer bb = ByteBuffer.wrap(bytes);
+ long firstLong = bb.getLong();
+ long secondLong = bb.getLong();
+ return new UUID(firstLong, secondLong);
+ }
+
+ @TypeConverter
+ public byte[] asBytes(UUID uuid) {
+ ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
+ bb.putLong(uuid.getMostSignificantBits());
+ bb.putLong(uuid.getLeastSignificantBits());
+ return bb.array();
+ }
}
}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/RobotsDao.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/RobotsDao.java
new file mode 100644
index 0000000..ee4acd8
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/RobotsDao.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.room.integration.testapp.dao;
+
+import androidx.room.Dao;
+import androidx.room.Insert;
+import androidx.room.Query;
+import androidx.room.integration.testapp.vo.Cluster;
+import androidx.room.integration.testapp.vo.Hivemind;
+import androidx.room.integration.testapp.vo.Robot;
+
+import java.util.List;
+import java.util.UUID;
+
+@Dao
+public interface RobotsDao {
+
+ @Insert
+ void putHivemind(Hivemind hivemind);
+
+ @Insert
+ void putRobot(Robot robot);
+
+ @Query("SELECT * FROM Hivemind")
+ List<Cluster> getCluster();
+
+ @Query("SELECT * FROM Robot WHERE mHiveId = :hiveId")
+ List<Robot> getHiveRobots(UUID hiveId);
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/UserDao.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/UserDao.java
index c489c27..449f2b5 100644
--- a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/UserDao.java
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/UserDao.java
@@ -33,6 +33,7 @@
import androidx.room.integration.testapp.vo.Day;
import androidx.room.integration.testapp.vo.IdUsername;
import androidx.room.integration.testapp.vo.NameAndLastName;
+import androidx.room.integration.testapp.vo.NameAndUsers;
import androidx.room.integration.testapp.vo.User;
import androidx.room.integration.testapp.vo.UserAndFriends;
import androidx.room.integration.testapp.vo.UserSummary;
@@ -318,4 +319,7 @@
@Query("UPDATE user SET mName = :name, mLastName = :name WHERE mId = :userId")
public abstract void setSameNames(String name, int userId);
+
+ @Query("SELECT mName FROM User")
+ public abstract List<NameAndUsers> getNameAndUsers();
}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/PojoWithRelationTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/PojoWithRelationTest.java
index 18540a3..1cced11 100644
--- a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/PojoWithRelationTest.java
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/PojoWithRelationTest.java
@@ -20,11 +20,14 @@
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
+import androidx.room.integration.testapp.vo.Cluster;
import androidx.room.integration.testapp.vo.EmbeddedUserAndAllPets;
+import androidx.room.integration.testapp.vo.Hivemind;
import androidx.room.integration.testapp.vo.House;
import androidx.room.integration.testapp.vo.Pet;
import androidx.room.integration.testapp.vo.PetAndOwner;
import androidx.room.integration.testapp.vo.PetWithToyIds;
+import androidx.room.integration.testapp.vo.Robot;
import androidx.room.integration.testapp.vo.Toy;
import androidx.room.integration.testapp.vo.User;
import androidx.room.integration.testapp.vo.UserAndAllPets;
@@ -44,6 +47,7 @@
import java.util.Collections;
import java.util.Date;
import java.util.List;
+import java.util.UUID;
@RunWith(AndroidJUnit4.class)
@LargeTest
@@ -295,4 +299,36 @@
assertThat(petAndOwners.get(1).getUser().getId(), is(1));
assertThat(petAndOwners.get(2).getUser().getId(), is(2));
}
+
+ @Test
+ public void relationWithBlobKey() {
+ UUID hiveId1 = UUID.randomUUID();
+ UUID hiveId2 = UUID.randomUUID();
+ UUID robotId1 = UUID.randomUUID();
+ UUID robotId2 = UUID.randomUUID();
+ UUID robotId3 = UUID.randomUUID();
+
+ mRobotsDao.putHivemind(new Hivemind(hiveId1));
+ mRobotsDao.putHivemind(new Hivemind(hiveId2));
+ mRobotsDao.putRobot(new Robot(robotId1, hiveId1));
+ mRobotsDao.putRobot(new Robot(robotId2, hiveId1));
+ mRobotsDao.putRobot(new Robot(robotId3, hiveId2));
+
+ List<Robot> firstHiveRobots = mRobotsDao.getHiveRobots(hiveId1);
+ assertThat(firstHiveRobots.size(), is(2));
+ assertThat(firstHiveRobots.get(0).mId, is(robotId1));
+ assertThat(firstHiveRobots.get(1).mId, is(robotId2));
+
+ List<Robot> secondHiveRobots = mRobotsDao.getHiveRobots(hiveId2);
+ assertThat(secondHiveRobots.size(), is(1));
+ assertThat(secondHiveRobots.get(0).mId, is(robotId3));
+
+ List<Cluster> clusterResult = mRobotsDao.getCluster();
+ assertThat(clusterResult.size(), is(2));
+ assertThat(clusterResult.get(0).mRobotList.size(), is(2));
+ assertThat(clusterResult.get(0).mRobotList.get(0).mId, is(robotId1));
+ assertThat(clusterResult.get(0).mRobotList.get(1).mId, is(robotId2));
+ assertThat(clusterResult.get(1).mRobotList.size(), is(1));
+ assertThat(clusterResult.get(1).mRobotList.get(0).mId, is(robotId3));
+ }
}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/TestDatabaseTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/TestDatabaseTest.java
index bc535c6..6f6e8b8 100644
--- a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/TestDatabaseTest.java
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/TestDatabaseTest.java
@@ -24,6 +24,7 @@
import androidx.room.integration.testapp.dao.PetCoupleDao;
import androidx.room.integration.testapp.dao.PetDao;
import androidx.room.integration.testapp.dao.RawDao;
+import androidx.room.integration.testapp.dao.RobotsDao;
import androidx.room.integration.testapp.dao.SchoolDao;
import androidx.room.integration.testapp.dao.SpecificDogDao;
import androidx.room.integration.testapp.dao.ToyDao;
@@ -49,6 +50,7 @@
protected FunnyNamedDao mFunnyNamedDao;
protected RawDao mRawDao;
protected UserHouseDao mUserHouseDao;
+ protected RobotsDao mRobotsDao;
@Before
public void createDb() {
@@ -65,5 +67,6 @@
mFunnyNamedDao = mDatabase.getFunnyNamedDao();
mRawDao = mDatabase.getRawDao();
mUserHouseDao = mDatabase.getUserHouseDao();
+ mRobotsDao = mDatabase.getRobotsDao();
}
}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/Cluster.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/Cluster.java
new file mode 100644
index 0000000..b2c55a2
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/Cluster.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.room.integration.testapp.vo;
+
+import androidx.room.Embedded;
+import androidx.room.Relation;
+
+import java.util.List;
+
+public class Cluster {
+
+ @Embedded
+ public final Hivemind mHivemind;
+
+ @Relation(parentColumn = "mId", entity = Robot.class, entityColumn = "mHiveId")
+ public final List<Robot> mRobotList;
+
+ public Cluster(Hivemind hivemind, List<Robot> robotList) {
+ mHivemind = hivemind;
+ mRobotList = robotList;
+ }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/Hivemind.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/Hivemind.java
new file mode 100644
index 0000000..935ee51
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/Hivemind.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.room.integration.testapp.vo;
+
+import androidx.annotation.NonNull;
+import androidx.room.Entity;
+import androidx.room.PrimaryKey;
+
+import java.util.UUID;
+
+@Entity
+public class Hivemind {
+
+ @PrimaryKey
+ @NonNull
+ public final UUID mId;
+
+ public Hivemind(@NonNull UUID id) {
+ mId = id;
+ }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/NameAndUsers.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/NameAndUsers.java
new file mode 100644
index 0000000..a1b7f26
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/NameAndUsers.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.room.integration.testapp.vo;
+
+import androidx.room.Relation;
+
+import java.util.List;
+
+public class NameAndUsers {
+ String mName;
+ @Relation(parentColumn = "mName", entityColumn = "mName")
+ List<User> mUsers;
+
+ public NameAndUsers(String name, List<User> users) {
+ mName = name;
+ mUsers = users;
+ }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/Robot.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/Robot.java
new file mode 100644
index 0000000..0d7be3a
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/Robot.java
@@ -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.
+ */
+
+package androidx.room.integration.testapp.vo;
+
+import androidx.annotation.NonNull;
+import androidx.room.Entity;
+import androidx.room.PrimaryKey;
+
+import java.util.UUID;
+
+@Entity
+public class Robot {
+
+ @PrimaryKey
+ @NonNull
+ public final UUID mId;
+ @NonNull
+ public final UUID mHiveId;
+
+ public Robot(@NonNull UUID id, @NonNull UUID hiveId) {
+ mId = id;
+ mHiveId = hiveId;
+ }
+}
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.1.0-beta02.txt b/room/runtime/api/2.1.0-beta02.txt
deleted file mode 100644
index 687fdf9..0000000
--- a/room/runtime/api/2.1.0-beta02.txt
+++ /dev/null
@@ -1,111 +0,0 @@
-// Signature format: 3.0
-package androidx.room {
-
- public class DatabaseConfiguration {
- method public boolean isMigrationRequired(int, int);
- method @Deprecated public boolean isMigrationRequiredFrom(int);
- field public final boolean allowDestructiveMigrationOnDowngrade;
- field public final boolean allowMainThreadQueries;
- field public final java.util.List<androidx.room.RoomDatabase.Callback!>? callbacks;
- field public final android.content.Context context;
- field public final androidx.room.RoomDatabase.JournalMode! journalMode;
- field public final androidx.room.RoomDatabase.MigrationContainer migrationContainer;
- field public final boolean multiInstanceInvalidation;
- field public final String? name;
- field public final java.util.concurrent.Executor queryExecutor;
- field public final boolean requireMigration;
- field public final androidx.sqlite.db.SupportSQLiteOpenHelper.Factory sqliteOpenHelperFactory;
- field public final java.util.concurrent.Executor transactionExecutor;
- }
-
- public class InvalidationTracker {
- method @WorkerThread public void addObserver(androidx.room.InvalidationTracker.Observer);
- method public void refreshVersionsAsync();
- method @WorkerThread public void removeObserver(androidx.room.InvalidationTracker.Observer);
- }
-
- public abstract static class InvalidationTracker.Observer {
- ctor protected InvalidationTracker.Observer(String, java.lang.String!...);
- ctor public InvalidationTracker.Observer(String![]);
- method public abstract void onInvalidated(java.util.Set<java.lang.String!>);
- }
-
- public class Room {
- ctor @Deprecated public Room();
- method public static <T extends androidx.room.RoomDatabase> androidx.room.RoomDatabase.Builder<T!> databaseBuilder(android.content.Context, Class<T!>, String);
- method public static <T extends androidx.room.RoomDatabase> androidx.room.RoomDatabase.Builder<T!> inMemoryDatabaseBuilder(android.content.Context, Class<T!>);
- field public static final String MASTER_TABLE_NAME = "room_master_table";
- }
-
- public abstract class RoomDatabase {
- ctor public RoomDatabase();
- method @Deprecated public void beginTransaction();
- method @WorkerThread public abstract void clearAllTables();
- method public void close();
- method public androidx.sqlite.db.SupportSQLiteStatement! compileStatement(String);
- method protected abstract androidx.room.InvalidationTracker createInvalidationTracker();
- method protected abstract androidx.sqlite.db.SupportSQLiteOpenHelper createOpenHelper(androidx.room.DatabaseConfiguration!);
- method @Deprecated public void endTransaction();
- method public androidx.room.InvalidationTracker getInvalidationTracker();
- method public androidx.sqlite.db.SupportSQLiteOpenHelper getOpenHelper();
- method public java.util.concurrent.Executor getQueryExecutor();
- method public java.util.concurrent.Executor getTransactionExecutor();
- method public boolean inTransaction();
- 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 void runInTransaction(Runnable);
- method public <V> V! runInTransaction(java.util.concurrent.Callable<V!>);
- method @Deprecated public void setTransactionSuccessful();
- field @Deprecated protected java.util.List<androidx.room.RoomDatabase.Callback!>? mCallbacks;
- field @Deprecated protected volatile androidx.sqlite.db.SupportSQLiteDatabase! mDatabase;
- }
-
- public static class RoomDatabase.Builder<T extends androidx.room.RoomDatabase> {
- method public androidx.room.RoomDatabase.Builder<T!> addCallback(androidx.room.RoomDatabase.Callback);
- method public androidx.room.RoomDatabase.Builder<T!> addMigrations(androidx.room.migration.Migration!...);
- method public androidx.room.RoomDatabase.Builder<T!> allowMainThreadQueries();
- method public T build();
- method public androidx.room.RoomDatabase.Builder<T!> enableMultiInstanceInvalidation();
- method public androidx.room.RoomDatabase.Builder<T!> fallbackToDestructiveMigration();
- method public androidx.room.RoomDatabase.Builder<T!> fallbackToDestructiveMigrationFrom(int...);
- method public androidx.room.RoomDatabase.Builder<T!> fallbackToDestructiveMigrationOnDowngrade();
- method public androidx.room.RoomDatabase.Builder<T!> openHelperFactory(androidx.sqlite.db.SupportSQLiteOpenHelper.Factory?);
- method public androidx.room.RoomDatabase.Builder<T!> setJournalMode(androidx.room.RoomDatabase.JournalMode);
- method public androidx.room.RoomDatabase.Builder<T!> setQueryExecutor(java.util.concurrent.Executor);
- method public androidx.room.RoomDatabase.Builder<T!> setTransactionExecutor(java.util.concurrent.Executor);
- }
-
- public abstract static class RoomDatabase.Callback {
- ctor public RoomDatabase.Callback();
- method public void onCreate(androidx.sqlite.db.SupportSQLiteDatabase);
- method public void onOpen(androidx.sqlite.db.SupportSQLiteDatabase);
- }
-
- public enum RoomDatabase.JournalMode {
- enum_constant public static final androidx.room.RoomDatabase.JournalMode AUTOMATIC;
- enum_constant public static final androidx.room.RoomDatabase.JournalMode TRUNCATE;
- enum_constant @RequiresApi(android.os.Build.VERSION_CODES.JELLY_BEAN) public static final androidx.room.RoomDatabase.JournalMode WRITE_AHEAD_LOGGING;
- }
-
- public static class RoomDatabase.MigrationContainer {
- ctor public RoomDatabase.MigrationContainer();
- method public void addMigrations(androidx.room.migration.Migration!...);
- method public java.util.List<androidx.room.migration.Migration!>? findMigrationPath(int, int);
- }
-
-}
-
-package androidx.room.migration {
-
- public abstract class Migration {
- ctor public Migration(int, int);
- method public abstract void migrate(androidx.sqlite.db.SupportSQLiteDatabase);
- field public final int endVersion;
- field public final int startVersion;
- }
-
-}
-
diff --git a/room/runtime/api/res-2.1.0-beta02.txt b/room/runtime/api/res-2.1.0-beta02.txt
deleted file mode 100644
index e69de29..0000000
--- a/room/runtime/api/res-2.1.0-beta02.txt
+++ /dev/null
diff --git a/room/runtime/api/restricted_2.1.0-beta02.txt b/room/runtime/api/restricted_2.1.0-beta02.txt
deleted file mode 100644
index 8ed4107..0000000
--- a/room/runtime/api/restricted_2.1.0-beta02.txt
+++ /dev/null
@@ -1,185 +0,0 @@
-// Signature format: 3.0
-package androidx.room {
-
- public class DatabaseConfiguration {
- ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public DatabaseConfiguration(android.content.Context, String?, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory, androidx.room.RoomDatabase.MigrationContainer, java.util.List<androidx.room.RoomDatabase.Callback!>?, boolean, androidx.room.RoomDatabase.JournalMode!, java.util.concurrent.Executor, boolean, java.util.Set<java.lang.Integer!>?);
- ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public DatabaseConfiguration(android.content.Context, String?, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory, androidx.room.RoomDatabase.MigrationContainer, java.util.List<androidx.room.RoomDatabase.Callback!>?, boolean, androidx.room.RoomDatabase.JournalMode!, java.util.concurrent.Executor, java.util.concurrent.Executor, boolean, boolean, boolean, java.util.Set<java.lang.Integer!>?);
- }
-
- @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class EntityDeletionOrUpdateAdapter<T> extends androidx.room.SharedSQLiteStatement {
- ctor public EntityDeletionOrUpdateAdapter(androidx.room.RoomDatabase!);
- method protected abstract void bind(androidx.sqlite.db.SupportSQLiteStatement!, T!);
- method public final int handle(T!);
- method public final int handleMultiple(Iterable<T!>!);
- method public final int handleMultiple(T![]!);
- }
-
- @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class EntityInsertionAdapter<T> extends androidx.room.SharedSQLiteStatement {
- ctor public EntityInsertionAdapter(androidx.room.RoomDatabase!);
- method protected abstract void bind(androidx.sqlite.db.SupportSQLiteStatement!, T!);
- method public final void insert(T!);
- method public final void insert(T![]!);
- method public final void insert(Iterable<T!>!);
- method public final long insertAndReturnId(T!);
- method public final long[]! insertAndReturnIdsArray(java.util.Collection<T!>!);
- method public final long[]! insertAndReturnIdsArray(T![]!);
- method public final Long![]! insertAndReturnIdsArrayBox(java.util.Collection<T!>!);
- method public final Long![]! insertAndReturnIdsArrayBox(T![]!);
- method public final java.util.List<java.lang.Long!>! insertAndReturnIdsList(T![]!);
- method public final java.util.List<java.lang.Long!>! insertAndReturnIdsList(java.util.Collection<T!>!);
- }
-
- public class InvalidationTracker {
- ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public InvalidationTracker(androidx.room.RoomDatabase!, java.lang.String!...);
- ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public InvalidationTracker(androidx.room.RoomDatabase!, java.util.Map<java.lang.String!,java.lang.String!>!, java.util.Map<java.lang.String!,java.util.Set<java.lang.String!>!>!, java.lang.String!...);
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void addWeakObserver(androidx.room.InvalidationTracker.Observer!);
- method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public <T> androidx.lifecycle.LiveData<T!>! createLiveData(String![]!, java.util.concurrent.Callable<T!>!);
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public <T> androidx.lifecycle.LiveData<T!>! createLiveData(String![]!, boolean, java.util.concurrent.Callable<T!>!);
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @WorkerThread public void refreshVersionsSync();
- }
-
- @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class MultiInstanceInvalidationService extends android.app.Service {
- ctor public MultiInstanceInvalidationService();
- method public android.os.IBinder? onBind(android.content.Intent!);
- }
-
- public abstract class RoomDatabase {
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void assertNotMainThread();
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public void assertNotSuspendingTransaction();
- field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int MAX_BIND_PARAMETER_CNT = 999; // 0x3e7
- }
-
- @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class RoomOpenHelper extends androidx.sqlite.db.SupportSQLiteOpenHelper.Callback {
- ctor public RoomOpenHelper(androidx.room.DatabaseConfiguration, androidx.room.RoomOpenHelper.Delegate, String, String);
- ctor public RoomOpenHelper(androidx.room.DatabaseConfiguration, androidx.room.RoomOpenHelper.Delegate, String);
- method public void onCreate(androidx.sqlite.db.SupportSQLiteDatabase!);
- method public void onUpgrade(androidx.sqlite.db.SupportSQLiteDatabase!, int, int);
- }
-
- @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract static class RoomOpenHelper.Delegate {
- ctor public RoomOpenHelper.Delegate(int);
- method protected abstract void createAllTables(androidx.sqlite.db.SupportSQLiteDatabase!);
- method protected abstract void dropAllTables(androidx.sqlite.db.SupportSQLiteDatabase!);
- method protected abstract void onCreate(androidx.sqlite.db.SupportSQLiteDatabase!);
- method protected abstract void onOpen(androidx.sqlite.db.SupportSQLiteDatabase!);
- method protected void onPostMigrate(androidx.sqlite.db.SupportSQLiteDatabase!);
- method protected void onPreMigrate(androidx.sqlite.db.SupportSQLiteDatabase!);
- method protected abstract void validateMigration(androidx.sqlite.db.SupportSQLiteDatabase!);
- field public final int version;
- }
-
- @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class RoomSQLiteQuery implements androidx.sqlite.db.SupportSQLiteProgram androidx.sqlite.db.SupportSQLiteQuery {
- method public static androidx.room.RoomSQLiteQuery! acquire(String!, int);
- method public void bindBlob(int, byte[]!);
- method public void bindDouble(int, double);
- method public void bindLong(int, long);
- method public void bindNull(int);
- method public void bindString(int, String!);
- method public void bindTo(androidx.sqlite.db.SupportSQLiteProgram!);
- method public void clearBindings();
- method public void close();
- method public void copyArgumentsFrom(androidx.room.RoomSQLiteQuery!);
- method public static androidx.room.RoomSQLiteQuery! copyFrom(androidx.sqlite.db.SupportSQLiteQuery!);
- method public int getArgCount();
- method public String! getSql();
- method public void release();
- }
-
- @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class SharedSQLiteStatement {
- ctor public SharedSQLiteStatement(androidx.room.RoomDatabase!);
- method public androidx.sqlite.db.SupportSQLiteStatement! acquire();
- method protected void assertNotMainThread();
- method protected abstract String! createQuery();
- method public void release(androidx.sqlite.db.SupportSQLiteStatement!);
- }
-
-}
-
-package androidx.room.paging {
-
- @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class LimitOffsetDataSource<T> extends androidx.paging.PositionalDataSource<T> {
- ctor protected LimitOffsetDataSource(androidx.room.RoomDatabase!, androidx.sqlite.db.SupportSQLiteQuery!, boolean, java.lang.String!...);
- ctor protected LimitOffsetDataSource(androidx.room.RoomDatabase!, androidx.room.RoomSQLiteQuery!, boolean, java.lang.String!...);
- method protected abstract java.util.List<T!>! convertRows(android.database.Cursor!);
- method public void loadInitial(androidx.paging.PositionalDataSource.LoadInitialParams, androidx.paging.PositionalDataSource.LoadInitialCallback<T!>);
- method public void loadRange(androidx.paging.PositionalDataSource.LoadRangeParams, androidx.paging.PositionalDataSource.LoadRangeCallback<T!>);
- }
-
-}
-
-package androidx.room.util {
-
- @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class CursorUtil {
- method public static android.database.Cursor copyAndClose(android.database.Cursor);
- method public static int getColumnIndex(android.database.Cursor, String);
- method public static int getColumnIndexOrThrow(android.database.Cursor, String);
- }
-
- @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);
- }
-
- @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class FtsTableInfo {
- ctor public FtsTableInfo(String!, java.util.Set<java.lang.String!>!, java.util.Set<java.lang.String!>!);
- ctor public FtsTableInfo(String!, java.util.Set<java.lang.String!>!, String!);
- method public static androidx.room.util.FtsTableInfo! read(androidx.sqlite.db.SupportSQLiteDatabase!, String!);
- field public final java.util.Set<java.lang.String!>! columns;
- field public final String! name;
- field public final java.util.Set<java.lang.String!>! options;
- }
-
- @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class StringUtil {
- method public static void appendPlaceholders(StringBuilder!, int);
- method public static String? joinIntoString(java.util.List<java.lang.Integer!>?);
- method public static StringBuilder! newStringBuilder();
- method public static java.util.List<java.lang.Integer!>? splitToIntList(String?);
- field public static final String![]! EMPTY_STRING_ARRAY;
- }
-
- @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class TableInfo {
- ctor public TableInfo(String!, java.util.Map<java.lang.String!,androidx.room.util.TableInfo.Column!>!, java.util.Set<androidx.room.util.TableInfo.ForeignKey!>!, java.util.Set<androidx.room.util.TableInfo.Index!>!);
- ctor public TableInfo(String!, java.util.Map<java.lang.String!,androidx.room.util.TableInfo.Column!>!, java.util.Set<androidx.room.util.TableInfo.ForeignKey!>!);
- method public static androidx.room.util.TableInfo! read(androidx.sqlite.db.SupportSQLiteDatabase!, String!);
- field public final java.util.Map<java.lang.String!,androidx.room.util.TableInfo.Column!>! columns;
- field public final java.util.Set<androidx.room.util.TableInfo.ForeignKey!>! foreignKeys;
- field public final java.util.Set<androidx.room.util.TableInfo.Index!>? indices;
- field public final String! name;
- }
-
- public static class TableInfo.Column {
- ctor public TableInfo.Column(String!, String!, boolean, int);
- method public boolean isPrimaryKey();
- field public final int affinity;
- field public final String! name;
- field public final boolean notNull;
- field public final int primaryKeyPosition;
- field public final String! type;
- }
-
- @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static class TableInfo.ForeignKey {
- ctor public TableInfo.ForeignKey(String, String, String, java.util.List<java.lang.String!>, java.util.List<java.lang.String!>);
- field public final java.util.List<java.lang.String!> columnNames;
- field public final String onDelete;
- field public final String onUpdate;
- field public final java.util.List<java.lang.String!> referenceColumnNames;
- field public final String referenceTable;
- }
-
- @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static class TableInfo.Index {
- ctor public TableInfo.Index(String!, boolean, java.util.List<java.lang.String!>!);
- field public static final String DEFAULT_PREFIX = "index_";
- field public final java.util.List<java.lang.String!>! columns;
- field public final String! name;
- field public final boolean unique;
- }
-
- @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class ViewInfo {
- ctor public ViewInfo(String!, String!);
- method public static androidx.room.util.ViewInfo! read(androidx.sqlite.db.SupportSQLiteDatabase!, String!);
- field public final String! name;
- field public final String! sql;
- }
-
-}
-
diff --git a/room/runtime/api/restricted_2.2.0-alpha02.ignore b/room/runtime/api/restricted_2.2.0-alpha02.ignore
index a1d60ff..6984c60 100644
--- a/room/runtime/api/restricted_2.2.0-alpha02.ignore
+++ b/room/runtime/api/restricted_2.2.0-alpha02.ignore
@@ -1,7 +1,19 @@
// Baseline format: 1.0
-AddedAbstractMethod: androidx.room.RoomDatabase#clearAllTables():
- Added method androidx.room.RoomDatabase.clearAllTables()
-AddedAbstractMethod: androidx.room.RoomDatabase#createInvalidationTracker():
- Added method androidx.room.RoomDatabase.createInvalidationTracker()
-AddedAbstractMethod: androidx.room.RoomDatabase#createOpenHelper(androidx.room.DatabaseConfiguration):
- Added method androidx.room.RoomDatabase.createOpenHelper(androidx.room.DatabaseConfiguration)
+ChangedType: androidx.room.InvalidationTracker#createLiveData(String[], boolean, java.util.concurrent.Callable<T>):
+ Method androidx.room.InvalidationTracker.createLiveData has changed return type from LiveData<T> to androidx.lifecycle.LiveData<T>
+ChangedType: androidx.room.InvalidationTracker#createLiveData(String[], java.util.concurrent.Callable<T>):
+ Method androidx.room.InvalidationTracker.createLiveData has changed return type from LiveData<T> to androidx.lifecycle.LiveData<T>
+
+
+InvalidNullConversion: androidx.room.InvalidationTracker#createLiveData(String[], boolean, java.util.concurrent.Callable<T>):
+ Attempted to remove @NonNull annotation from method androidx.room.InvalidationTracker.createLiveData(String[],boolean,java.util.concurrent.Callable<T>)
+InvalidNullConversion: androidx.room.InvalidationTracker#createLiveData(String[], java.util.concurrent.Callable<T>):
+ Attempted to remove @NonNull annotation from method androidx.room.InvalidationTracker.createLiveData(String[],java.util.concurrent.Callable<T>)
+
+
+RemovedMethod: androidx.room.paging.LimitOffsetDataSource#isInvalid():
+ Removed method androidx.room.paging.LimitOffsetDataSource.isInvalid()
+RemovedMethod: androidx.room.paging.LimitOffsetDataSource#loadInitial(LoadInitialParams, LoadInitialCallback<T>):
+ Removed method androidx.room.paging.LimitOffsetDataSource.loadInitial(LoadInitialParams,LoadInitialCallback<T>)
+RemovedMethod: androidx.room.paging.LimitOffsetDataSource#loadRange(LoadRangeParams, LoadRangeCallback<T>):
+ Removed method androidx.room.paging.LimitOffsetDataSource.loadRange(LoadRangeParams,LoadRangeCallback<T>)
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/samples/Support7Demos/build.gradle b/samples/Support7Demos/build.gradle
index fdaed3e..eac5be8 100644
--- a/samples/Support7Demos/build.gradle
+++ b/samples/Support7Demos/build.gradle
@@ -10,6 +10,7 @@
api 'com.google.android.material:material:1.0.0'
implementation(project(":appcompat"))
implementation(project(":cardview"))
+ implementation(project(":core:core"))
implementation(project(":gridlayout"))
implementation(project(":mediarouter"))
implementation(project(":palette:palette"))
diff --git a/samples/Support7Demos/src/main/java/com/example/android/supportv7/app/AlertDialogUsage.java b/samples/Support7Demos/src/main/java/com/example/android/supportv7/app/AlertDialogUsage.java
index 866c247..f32bfed 100644
--- a/samples/Support7Demos/src/main/java/com/example/android/supportv7/app/AlertDialogUsage.java
+++ b/samples/Support7Demos/src/main/java/com/example/android/supportv7/app/AlertDialogUsage.java
@@ -22,6 +22,7 @@
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.content.res.AppCompatResources;
import com.example.android.supportv7.Cheeses;
import com.example.android.supportv7.R;
@@ -58,9 +59,12 @@
showSimpleButtonsDialog();
break;
case 2:
- showSingleChoiceDialog();
+ showSimpleWithMoreButtonsDialog();
break;
case 3:
+ showSingleChoiceDialog();
+ break;
+ case 4:
showMultiChoiceDialog();
break;
}
@@ -82,6 +86,19 @@
b.show();
}
+ private void showSimpleWithMoreButtonsDialog() {
+ AlertDialog.Builder b = new AlertDialog.Builder(this);
+ b.setTitle(R.string.dialog_title);
+ b.setMessage(R.string.dialog_content);
+ b.setNegativeButton("-ve", null);
+ b.setNegativeButtonIcon(AppCompatResources.getDrawable(this, R.drawable.ic_media_pause));
+ b.setNeutralButton("=ve", null);
+ b.setNeutralButtonIcon(AppCompatResources.getDrawable(this, R.drawable.ic_media_play));
+ b.setPositiveButton("+ve", null);
+ b.setPositiveButtonIcon(AppCompatResources.getDrawable(this, R.drawable.ic_media_stop));
+ b.show();
+ }
+
private void showSingleChoiceDialog() {
AlertDialog.Builder b = new AlertDialog.Builder(this);
b.setTitle(R.string.dialog_title);
diff --git a/samples/Support7Demos/src/main/res/values/arrays.xml b/samples/Support7Demos/src/main/res/values/arrays.xml
index e610f1c..e020222 100644
--- a/samples/Support7Demos/src/main/res/values/arrays.xml
+++ b/samples/Support7Demos/src/main/res/values/arrays.xml
@@ -37,6 +37,7 @@
<string-array name="alert_dialog_types">
<item>Simple</item>
<item>Simple with buttons</item>
+ <item>Simple with more buttons</item>
<item>List (single choice)</item>
<item>List (multi choice)</item>
</string-array>
diff --git a/settings.gradle b/settings.gradle
index f94aa3f9..e953056 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,7 +59,8 @@
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:startup-benchmark", "benchmark/integration-tests/startup-benchmark")
diff --git a/sharetarget/build.gradle b/sharetarget/build.gradle
index b750a70..cc27624 100644
--- a/sharetarget/build.gradle
+++ b/sharetarget/build.gradle
@@ -25,7 +25,7 @@
}
dependencies {
- api("androidx.core:core:1.1.0-rc01")
+ api("androidx.core:core:1.1.0")
implementation("androidx.collection:collection:1.0.0")
api(GUAVA_LISTENABLE_FUTURE)
implementation("androidx.concurrent:concurrent-futures:1.0.0-alpha02")
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/slices/builders/api/restricted_1.1.0-alpha02.ignore b/slices/builders/api/restricted_1.1.0-alpha02.ignore
deleted file mode 100644
index 9235188..0000000
--- a/slices/builders/api/restricted_1.1.0-alpha02.ignore
+++ /dev/null
@@ -1,5 +0,0 @@
-// Baseline format: 1.0
-AddedAbstractMethod: androidx.slice.builders.impl.MessagingBuilder#add(androidx.slice.builders.impl.TemplateBuilderImpl):
- Added method androidx.slice.builders.impl.MessagingBuilder.add(androidx.slice.builders.impl.TemplateBuilderImpl)
-AddedAbstractMethod: androidx.slice.builders.impl.MessagingBuilder#createMessageBuilder():
- Added method androidx.slice.builders.impl.MessagingBuilder.createMessageBuilder()
diff --git a/slices/builders/build.gradle b/slices/builders/build.gradle
index 914633c..63bbe8d 100644
--- a/slices/builders/build.gradle
+++ b/slices/builders/build.gradle
@@ -28,7 +28,7 @@
implementation(project(":slice-core"))
api(project(":remotecallback"))
implementation "androidx.annotation:annotation:1.1.0"
- implementation "androidx.core:core:1.1.0-rc01"
+ implementation "androidx.core:core:1.1.0"
implementation project(':collection:collection')
}
diff --git a/slices/core/api/restricted_1.1.0-alpha02.ignore b/slices/core/api/restricted_1.1.0-alpha02.ignore
index 8a634d1..03bee74 100644
--- a/slices/core/api/restricted_1.1.0-alpha02.ignore
+++ b/slices/core/api/restricted_1.1.0-alpha02.ignore
@@ -1,12 +1,14 @@
// Baseline format: 1.0
-RemovedClass: androidx.slice.compat.CompatPermissionManager.PermissionState:
- Removed class androidx.slice.compat.CompatPermissionManager.PermissionState
-RemovedClass: androidx.slice.compat.SliceProviderWrapperContainer.SliceProviderWrapper:
- Removed class androidx.slice.compat.SliceProviderWrapperContainer.SliceProviderWrapper
+AddedAbstractMethod: androidx.slice.SliceManager#getPinnedSpecs(android.net.Uri):
+ Added method androidx.slice.SliceManager.getPinnedSpecs(android.net.Uri)
+RemovedMethod: androidx.slice.SliceProvider#createPermissionIntent(android.content.Context, android.net.Uri, String):
+ Removed method androidx.slice.SliceProvider.createPermissionIntent(android.content.Context,android.net.Uri,String)
RemovedMethod: androidx.slice.SliceProvider#createPermissionSlice(android.content.Context, android.net.Uri, String):
Removed method androidx.slice.SliceProvider.createPermissionSlice(android.content.Context,android.net.Uri,String)
+RemovedMethod: androidx.slice.SliceProvider#getPermissionString(android.content.Context, String):
+ Removed method androidx.slice.SliceProvider.getPermissionString(android.content.Context,String)
RemovedMethod: androidx.slice.core.SliceQuery#stream(androidx.slice.Slice):
Removed method androidx.slice.core.SliceQuery.stream(androidx.slice.Slice)
RemovedMethod: androidx.slice.core.SliceQuery#stream(androidx.slice.SliceItem):
diff --git a/slices/core/src/androidTest/java/androidx/slice/compat/CompatPermissionManagerTest.java b/slices/core/src/androidTest/java/androidx/slice/compat/CompatPermissionManagerTest.java
index 97f2bea..583cb44 100644
--- a/slices/core/src/androidTest/java/androidx/slice/compat/CompatPermissionManagerTest.java
+++ b/slices/core/src/androidTest/java/androidx/slice/compat/CompatPermissionManagerTest.java
@@ -36,6 +36,7 @@
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.FlakyTest;
import androidx.test.filters.LargeTest;
import androidx.test.filters.SdkSuppress;
@@ -51,6 +52,7 @@
private final Context mContext = ApplicationProvider.getApplicationContext();
+ @FlakyTest
@Test
public void testAutoGrant() {
final Uri uri = new Uri.Builder()
diff --git a/slidingpanelayout/build.gradle b/slidingpanelayout/build.gradle
index b9732d5..7738665 100644
--- a/slidingpanelayout/build.gradle
+++ b/slidingpanelayout/build.gradle
@@ -9,7 +9,7 @@
dependencies {
api("androidx.annotation:annotation:1.1.0")
- implementation("androidx.core:core:1.1.0-rc01")
+ implementation("androidx.core:core:1.1.0")
api(project(":customview"))
}
diff --git a/swiperefreshlayout/build.gradle b/swiperefreshlayout/build.gradle
index 1309851..46c3da4 100644
--- a/swiperefreshlayout/build.gradle
+++ b/swiperefreshlayout/build.gradle
@@ -10,7 +10,7 @@
dependencies {
api("androidx.annotation:annotation:1.1.0")
- api("androidx.core:core:1.1.0-rc01")
+ api("androidx.core:core:1.1.0")
api("androidx.interpolator:interpolator:1.0.0")
androidTestImplementation(JUNIT)
diff --git a/swiperefreshlayout/src/androidTest/java/androidx/swiperefreshlayout/widget/SwipeInjector.java b/swiperefreshlayout/src/androidTest/java/androidx/swiperefreshlayout/widget/SwipeInjector.java
deleted file mode 100644
index 20ef90b..0000000
--- a/swiperefreshlayout/src/androidTest/java/androidx/swiperefreshlayout/widget/SwipeInjector.java
+++ /dev/null
@@ -1,125 +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.swiperefreshlayout.widget;
-
-import static android.os.SystemClock.uptimeMillis;
-
-import android.app.Instrumentation;
-import android.os.SystemClock;
-import android.view.MotionEvent;
-import android.view.View;
-
-import androidx.test.espresso.action.CoordinatesProvider;
-
-public class SwipeInjector {
-
- private static final int X = 0;
- private static final int Y = 1;
-
- private Instrumentation mInstrumentation;
-
- private long mStartTime = 0;
- private long mCurrentTime = 0;
- private float[] mFloats = new float[2];
-
- public SwipeInjector(Instrumentation instrumentation) {
- mInstrumentation = instrumentation;
- }
-
- public void startDrag(CoordinatesProvider provider, View view) {
- float[] floats = provider.calculateCoordinates(view);
- startDrag(floats[X], floats[Y]);
- }
-
- public void startDrag(float x, float y) {
- mStartTime = uptimeMillis();
- mFloats[X] = x;
- mFloats[Y] = y;
- injectMotionEvent(obtainDownEvent(mStartTime, mFloats));
- }
-
- public void dragTo(CoordinatesProvider provider, View view) {
- float[] floats = provider.calculateCoordinates(view);
- dragTo(floats[X], floats[Y]);
- }
-
- public void dragTo(float x, float y) {
- mFloats[X] = x;
- mFloats[Y] = y;
- injectMotionEvent(obtainMoveEvent(mStartTime, uptimeMillis(), mFloats));
- }
-
- public void dragTo(CoordinatesProvider provider, View view, long duration) {
- float[] floats = provider.calculateCoordinates(view);
- dragTo(floats[X], floats[Y], duration);
- }
-
- public void dragTo(float x, float y, long duration) {
- float x0 = mFloats[X];
- float y0 = mFloats[Y];
- float dx = x - x0;
- float dy = y - y0;
- int steps = Math.max(1, Math.round(duration / 10f));
- for (int i = 1; i <= steps; i++) {
- float progress = (float) i / steps;
- mFloats[X] = x0 + dx * progress;
- mFloats[Y] = y0 + dy * progress;
- injectMotionEvent(obtainMoveEvent(mStartTime, mCurrentTime + 10L, mFloats));
- }
- }
-
- public void dragBy(float dx, float dy) {
- dragTo(mFloats[X] + dx, mFloats[Y] + dy);
- }
-
- public void dragBy(float dx, float dy, long duration) {
- dragTo(mFloats[X] + dx, mFloats[Y] + dy, duration);
- }
-
- public void finishDrag() {
- injectMotionEvent(obtainUpEvent(mStartTime, uptimeMillis(), mFloats));
- }
-
- private static MotionEvent obtainDownEvent(long time, float[] coord) {
- return MotionEvent.obtain(time, time,
- MotionEvent.ACTION_DOWN, coord[X], coord[Y], 0);
- }
-
- private static MotionEvent obtainMoveEvent(long startTime, long time, float[] coord) {
- return MotionEvent.obtain(startTime, time,
- MotionEvent.ACTION_MOVE, coord[X], coord[Y], 0);
- }
-
- private static MotionEvent obtainUpEvent(long startTime, long time, float[] coord) {
- return MotionEvent.obtain(startTime, time,
- MotionEvent.ACTION_UP, coord[X], coord[Y], 0);
- }
-
- private void injectMotionEvent(MotionEvent event) {
- try {
- long eventTime = event.getEventTime();
- long now = uptimeMillis();
- if (eventTime - now > 0) {
- SystemClock.sleep(eventTime - now);
- }
- mInstrumentation.sendPointerSync(event);
- mCurrentTime = eventTime;
- } finally {
- event.recycle();
- }
- }
-}
diff --git a/swiperefreshlayout/src/androidTest/java/androidx/swiperefreshlayout/widget/SwipeRefreshLayoutInHorizontallyScrollingParentTest.java b/swiperefreshlayout/src/androidTest/java/androidx/swiperefreshlayout/widget/SwipeRefreshLayoutInHorizontallyScrollingParentTest.java
index f9c514b..665fbb4 100644
--- a/swiperefreshlayout/src/androidTest/java/androidx/swiperefreshlayout/widget/SwipeRefreshLayoutInHorizontallyScrollingParentTest.java
+++ b/swiperefreshlayout/src/androidTest/java/androidx/swiperefreshlayout/widget/SwipeRefreshLayoutInHorizontallyScrollingParentTest.java
@@ -33,6 +33,7 @@
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
import androidx.testutils.PollingCheck;
+import androidx.testutils.SwipeInjector;
import org.junit.Before;
import org.junit.Rule;
diff --git a/testutils/src/main/java/androidx/testutils/SimpleGestureGenerator.kt b/testutils/src/main/java/androidx/testutils/SimpleGestureGenerator.kt
index 72e7cf3..da76ecc 100644
--- a/testutils/src/main/java/androidx/testutils/SimpleGestureGenerator.kt
+++ b/testutils/src/main/java/androidx/testutils/SimpleGestureGenerator.kt
@@ -58,23 +58,41 @@
}
fun MotionEventData.toMotionEvent(downTime: Long): MotionEvent = MotionEvent.obtain(
- downTime,
- this.eventTimeDelta + downTime,
- this.action,
- this.x,
- this.y,
- this.metaState)
+ downTime,
+ this.eventTimeDelta + downTime,
+ this.action,
+ this.x,
+ this.y,
+ this.metaState
+)
/**
- * Constructs a [FlingData] from a [Context].
+ * Constructs a [FlingData] from a [Context] and [velocityPixelsPerSecond].
+ *
+ * [velocityPixelsPerSecond] must between [ViewConfiguration.getScaledMinimumFlingVelocity] * 1.1
+ * and [ViewConfiguration.getScaledMaximumFlingVelocity] * .9, inclusive. Losses of precision do
+ * not allow the simulated fling to be super precise.
*/
-fun generateFlingData(context: Context): FlingData {
+@JvmOverloads
+fun generateFlingData(context: Context, velocityPixelsPerSecond: Float? = null): FlingData {
val configuration = ViewConfiguration.get(context)
val touchSlop = configuration.scaledTouchSlop
val minimumVelocity = configuration.scaledMinimumFlingVelocity
val maximumVelocity = configuration.scaledMaximumFlingVelocity
- val targetPixelsPerMilli = ((maximumVelocity + minimumVelocity) / 2) / 1000f
+ val targetPixelsPerMilli =
+ if (velocityPixelsPerSecond != null) {
+ if (velocityPixelsPerSecond < minimumVelocity * 1.1 - .001f ||
+ velocityPixelsPerSecond > maximumVelocity * .9 + .001f) {
+ throw IllegalArgumentException("velocityPixelsPerSecond must be between " +
+ "ViewConfiguration.scaledMinimumFlingVelocity * 1.1 and " +
+ "ViewConfiguration.scaledMinimumFlingVelocity * .9, inclusive")
+ }
+ velocityPixelsPerSecond / 1000f
+ } else {
+ ((maximumVelocity + minimumVelocity) / 2) / 1000f
+ }
+
val targetDistancePixels = touchSlop * 2
val targetMillisPassed = floor(targetDistancePixels / targetPixelsPerMilli).toInt()
@@ -89,7 +107,7 @@
* Returns [value] rounded up to the closest [interval] * N, where N is a Integer.
*/
private fun ceilToInterval(value: Int, interval: Int): Int =
- ceil(value.toFloat() / interval).toInt() * interval
+ ceil(value.toFloat() / interval).toInt() * interval
/**
* Generates a [List] of [MotionEventData] starting from ([originX], [originY]) that will cause a
@@ -100,7 +118,7 @@
originY: Float,
fingerDirection: Direction
):
- List<MotionEventData> {
+ List<MotionEventData> {
// Ceiling the time and distance to match up with motion event intervals.
val time: Int = ceilToInterval(this.time, MOTION_EVENT_INTERVAL_MILLIS)
@@ -127,8 +145,8 @@
motionEventData.add(MotionEventData(0, MotionEvent.ACTION_DOWN, originX, originY, 0))
for (i in 1..(numberOfInnerEvents)) {
val timeDelta = i * MOTION_EVENT_INTERVAL_MILLIS
- val x = i * dxIncrement
- val y = i * dyIncrement
+ val x = originX + (i * dxIncrement)
+ val y = originY + (i * dyIncrement)
motionEventData.add(MotionEventData(timeDelta, MotionEvent.ACTION_MOVE, x, y, 0))
}
motionEventData.add(MotionEventData(time, MotionEvent.ACTION_MOVE, toX, toY, 0))
@@ -161,12 +179,17 @@
* @see [generateFlingMotionEventData]
* @see [dispatchTouchEvents]
*/
+@JvmOverloads
fun View.simulateFling(
downTime: Long,
originX: Float,
originY: Float,
- direction: Direction
+ direction: Direction,
+ velocityPixelsPerSecond: Float? = null
) {
- dispatchTouchEvents(downTime,
- generateFlingData(context).generateFlingMotionEventData(originX, originY, direction))
+ dispatchTouchEvents(
+ downTime,
+ generateFlingData(context, velocityPixelsPerSecond)
+ .generateFlingMotionEventData(originX, originY, direction)
+ )
}
diff --git a/testutils/src/main/java/androidx/testutils/SwipeInjector.java b/testutils/src/main/java/androidx/testutils/SwipeInjector.java
new file mode 100644
index 0000000..9c63a7d
--- /dev/null
+++ b/testutils/src/main/java/androidx/testutils/SwipeInjector.java
@@ -0,0 +1,234 @@
+/*
+ * 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.testutils;
+
+import static android.os.SystemClock.uptimeMillis;
+
+import android.annotation.SuppressLint;
+import android.app.Instrumentation;
+import android.os.SystemClock;
+import android.view.MotionEvent;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.test.espresso.action.CoordinatesProvider;
+
+/**
+ * Injects motion events for custom gestures using Instrumentation. Use this to "draw" the gestures,
+ * in a similar way as a Path is defined. Create an instance of this class, start a drag, move it
+ * around and then finish it. For example, this injects a "Swipe right then left" gesture from
+ * TalkBack:
+ *
+ * <pre>
+ * View view = findViewById(R.id.view_to_swipe_on);
+ *
+ * SwipeInjector swiper = new SwipeInjector(InstrumentationRegistry.getInstrumentation());
+ * swiper.startDrag(GeneralLocation.LEFT, view); // Start at the left side of the view
+ * swiper.dragTo(GeneralLocation.RIGHT, view, 200); // Swipe to the right in 200 ms
+ * swiper.dragTo(GeneralLocation.LEFT, view, 200); // Swipe to the left in 200 ms
+ * swiper.finishDrag(); // Finish the gesture
+ * </pre>
+ *
+ * @see androidx.test.espresso.action.GeneralLocation GeneralLocation
+ * @see TranslatedCoordinatesProvider
+ */
+public class SwipeInjector {
+
+ private static final int X = 0;
+ private static final int Y = 1;
+
+ private Instrumentation mInstrumentation;
+
+ private long mStartTime = 0;
+ private long mCurrentTime = 0;
+ private float[] mFloats = new float[2];
+
+ /**
+ * Creates an injector.
+ */
+ public SwipeInjector(@NonNull Instrumentation instrumentation) {
+ mInstrumentation = instrumentation;
+ }
+
+ /**
+ * Start a drag on the coordinate provided for the given view. For example,
+ * {@code startDrag(GeneralLocation.CENTER, mView)}.
+ *
+ * @param provider provider of the coordinate of the pointer down event, like
+ * {@link androidx.test.espresso.action.GeneralLocation#CENTER GeneralLocation.CENTER}
+ * @param view the view passed to the
+ * {@link CoordinatesProvider#calculateCoordinates(View) coordinates provider}
+ */
+ @SuppressLint("LambdaLast")
+ public void startDrag(@NonNull CoordinatesProvider provider, @NonNull View view) {
+ float[] floats = provider.calculateCoordinates(view);
+ startDrag(floats[X], floats[Y]);
+ }
+
+ /**
+ * Start a drag on the given coordinate. For example, {@code startDrag(100, 200)}. Note that the
+ * coordinates are absolute coordinates for the screen.
+ *
+ * @param x the x coordinate of the pointer down event
+ * @param y the y coordinate of the pointer down event
+ */
+ public void startDrag(float x, float y) {
+ mStartTime = uptimeMillis();
+ mFloats[X] = x;
+ mFloats[Y] = y;
+ injectMotionEvent(obtainDownEvent(mStartTime, mFloats));
+ }
+
+ /**
+ * Extend the drag with a single motion event to the coordinates provided for the given view.
+ * For example, {@code dragTo(GeneralLocation.LEFT, mView)}. Call one of the {@code startDrag}
+ * methods before calling any of the {@code dragTo} methods.
+ *
+ * @param provider provider of the coordinate of the pointer move event, like
+ * {@link androidx.test.espresso.action.GeneralLocation#CENTER GeneralLocation.CENTER}
+ * @param view the view passed to the
+ * {@link CoordinatesProvider#calculateCoordinates(View) coordinates provider}
+ */
+ @SuppressLint("LambdaLast")
+ public void dragTo(@NonNull CoordinatesProvider provider, @NonNull View view) {
+ float[] floats = provider.calculateCoordinates(view);
+ dragTo(floats[X], floats[Y]);
+ }
+
+ /**
+ * Extend the drag with a single motion event to the given coordinate. For example,
+ * {@code dragTo(0, 10)}. Call one of the {@code startDrag} methods before calling any of the
+ * {@code dragTo} methods. Note that the coordinates are absolute coordinates for the screen.
+ *
+ * @param x the x coordinate of the pointer move event
+ * @param y the y coordinate of the pointer move event
+ */
+ public void dragTo(float x, float y) {
+ mFloats[X] = x;
+ mFloats[Y] = y;
+ injectMotionEvent(obtainMoveEvent(mStartTime, uptimeMillis(), mFloats));
+ }
+
+ /**
+ * Extend the drag with a range of motion events to the coordinate provided for the given view.
+ * An event will be injected every 10ms, interpolating linearly to the destination. For example,
+ * {@code dragTo(GeneralLocation.LEFT, mView, 300)}. Call one of the {@code startDrag} methods
+ * before calling any of the {@code dragTo} methods.
+ *
+ * @param provider provider of the final coordinate of this part of the drag, like
+ * {@link androidx.test.espresso.action.GeneralLocation#CENTER GeneralLocation.CENTER}
+ * @param view the view passed to the
+ * {@link CoordinatesProvider#calculateCoordinates(View) coordinates provider}
+ * @param duration the time in milliseconds this part of the drag should take. Actual time will
+ * be different if not a multiple of 10.
+ */
+ @SuppressLint("LambdaLast")
+ public void dragTo(@NonNull CoordinatesProvider provider, @NonNull View view, long duration) {
+ float[] floats = provider.calculateCoordinates(view);
+ dragTo(floats[X], floats[Y], duration);
+ }
+
+ /**
+ * Extend the drag with a range of motion events to the given coordinate. An event will be
+ * injected every 10ms, interpolating linearly to the destination. For example,
+ * {@code dragTo(10, 0, 350)}. Call one of the {@code startDrag} methods before calling any of
+ * the {@code dragTo} methods. Note that the coordinates are absolute coordinates for the
+ * screen.
+ *
+ * @param x the final x coordinate of this part of the drag
+ * @param y the final y coordinate of this part of the drag
+ * @param duration the time in milliseconds this part of the drag should take. Actual time will
+ * be different if not a multiple of 10.
+ */
+ public void dragTo(float x, float y, long duration) {
+ float x0 = mFloats[X];
+ float y0 = mFloats[Y];
+ float dx = x - x0;
+ float dy = y - y0;
+ int steps = Math.max(1, Math.round(duration / 10f));
+ for (int i = 1; i <= steps; i++) {
+ float progress = (float) i / steps;
+ mFloats[X] = x0 + dx * progress;
+ mFloats[Y] = y0 + dy * progress;
+ injectMotionEvent(obtainMoveEvent(mStartTime, mCurrentTime + 10L, mFloats));
+ }
+ }
+
+ /**
+ * Extend the drag with a single motion event covering the given delta. For example,
+ * {@code dragBy(10, -20)}. Call one of the {@code startDrag} methods before calling any of
+ * the {@code dragBy} methods.
+ *
+ * @param dx the distance in x direction
+ * @param dy the distance in y direction
+ */
+ public void dragBy(float dx, float dy) {
+ dragTo(mFloats[X] + dx, mFloats[Y] + dy);
+ }
+
+ /**
+ * Extend the drag with a range of motion events covering the given delta. An event will be
+ * injected every 10ms, interpolating linearly to the destination. For example,
+ * {@code dragBy(-50, 0, 100)}. Call one of the {@code startDrag} methods before calling any of
+ * the {@code dragBy} methods.
+ *
+ * @param dx the distance in x direction
+ * @param dy the distance in y direction
+ * @param duration the time in milliseconds this part of the drag should take. Actual time will
+ * be different if not a multiple of 10.
+ */
+ public void dragBy(float dx, float dy, long duration) {
+ dragTo(mFloats[X] + dx, mFloats[Y] + dy, duration);
+ }
+
+ /**
+ * Finish the drag with a pointer up event. A new drag can be started after this with one of the
+ * {@code startDrag} methods.
+ */
+ public void finishDrag() {
+ injectMotionEvent(obtainUpEvent(mStartTime, uptimeMillis(), mFloats));
+ }
+
+ private static MotionEvent obtainDownEvent(long time, float[] coord) {
+ return MotionEvent.obtain(time, time,
+ MotionEvent.ACTION_DOWN, coord[X], coord[Y], 0);
+ }
+
+ private static MotionEvent obtainMoveEvent(long startTime, long time, float[] coord) {
+ return MotionEvent.obtain(startTime, time,
+ MotionEvent.ACTION_MOVE, coord[X], coord[Y], 0);
+ }
+
+ private static MotionEvent obtainUpEvent(long startTime, long time, float[] coord) {
+ return MotionEvent.obtain(startTime, time,
+ MotionEvent.ACTION_UP, coord[X], coord[Y], 0);
+ }
+
+ private void injectMotionEvent(MotionEvent event) {
+ try {
+ long eventTime = event.getEventTime();
+ long now = uptimeMillis();
+ if (eventTime - now > 0) {
+ SystemClock.sleep(eventTime - now);
+ }
+ mInstrumentation.sendPointerSync(event);
+ mCurrentTime = eventTime;
+ } finally {
+ event.recycle();
+ }
+ }
+}
diff --git a/swiperefreshlayout/src/androidTest/java/androidx/swiperefreshlayout/widget/TranslatedCoordinatesProvider.java b/testutils/src/main/java/androidx/testutils/TranslatedCoordinatesProvider.java
similarity index 65%
rename from swiperefreshlayout/src/androidTest/java/androidx/swiperefreshlayout/widget/TranslatedCoordinatesProvider.java
rename to testutils/src/main/java/androidx/testutils/TranslatedCoordinatesProvider.java
index 4cdd93b..a0eaebd 100644
--- a/swiperefreshlayout/src/androidTest/java/androidx/swiperefreshlayout/widget/TranslatedCoordinatesProvider.java
+++ b/testutils/src/main/java/androidx/testutils/TranslatedCoordinatesProvider.java
@@ -14,18 +14,32 @@
* limitations under the License.
*/
-package androidx.swiperefreshlayout.widget;
+package androidx.testutils;
+import android.annotation.SuppressLint;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.test.espresso.action.CoordinatesProvider;
+/**
+ * Translates a {@link CoordinatesProvider} by the given x and y distances. The distances are given
+ * in pixels. Common providers to start with can be found in
+ * {@link androidx.test.espresso.action.GeneralLocation GeneralLocation}.
+ */
public class TranslatedCoordinatesProvider implements CoordinatesProvider {
private CoordinatesProvider mProvider;
private float mDx;
private float mDy;
+ /**
+ * Creates an instance of {@link TranslatedCoordinatesProvider}
+ *
+ * @param coordinatesProvider the {@link CoordinatesProvider} to translate
+ * @param dx the distance in x direction
+ * @param dy the distance in y direction
+ */
+ @SuppressLint("LambdaLast")
public TranslatedCoordinatesProvider(@NonNull CoordinatesProvider coordinatesProvider, float dx,
float dy) {
mProvider = coordinatesProvider;
@@ -33,8 +47,9 @@
mDy = dy;
}
+ @NonNull
@Override
- public float[] calculateCoordinates(View view) {
+ public float[] calculateCoordinates(@NonNull View view) {
float[] coords = mProvider.calculateCoordinates(view);
coords[0] += mDx;
coords[1] += mDy;
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/build.gradle b/textclassifier/build.gradle
index d2cd121..92508eb 100644
--- a/textclassifier/build.gradle
+++ b/textclassifier/build.gradle
@@ -13,7 +13,7 @@
api("androidx.annotation:annotation:1.1.0")
implementation("androidx.collection:collection:1.0.0")
// TODO: change to 1.1.0-alpha04 after release
- api("androidx.core:core:1.1.0-rc01")
+ api("androidx.core:core:1.1.0")
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
androidTestImplementation(ANDROIDX_TEST_CORE)
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/tv-provider/api/1.1.0-alpha01.ignore b/tv-provider/api/1.1.0-alpha01.ignore
deleted file mode 100644
index 7690c60..0000000
--- a/tv-provider/api/1.1.0-alpha01.ignore
+++ /dev/null
@@ -1,7 +0,0 @@
-// Baseline format: 1.0
-HiddenSuperclass: androidx.tvprovider.media.tv.PreviewProgram:
- Public class androidx.tvprovider.media.tv.PreviewProgram stripped of unavailable superclass androidx.tvprovider.media.tv.BasePreviewProgram
-HiddenSuperclass: androidx.tvprovider.media.tv.Program:
- Public class androidx.tvprovider.media.tv.Program stripped of unavailable superclass androidx.tvprovider.media.tv.BaseProgram
-HiddenSuperclass: androidx.tvprovider.media.tv.WatchNextProgram:
- Public class androidx.tvprovider.media.tv.WatchNextProgram stripped of unavailable superclass androidx.tvprovider.media.tv.BasePreviewProgram
diff --git a/tv-provider/api/restricted_1.1.0-alpha01.ignore b/tv-provider/api/restricted_1.1.0-alpha01.ignore
deleted file mode 100644
index 10c7976..0000000
--- a/tv-provider/api/restricted_1.1.0-alpha01.ignore
+++ /dev/null
@@ -1,15 +0,0 @@
-// Baseline format: 1.0
-RemovedClass: androidx.tvprovider.media.tv.BasePreviewProgram.AspectRatio:
- Removed class androidx.tvprovider.media.tv.BasePreviewProgram.AspectRatio
-RemovedClass: androidx.tvprovider.media.tv.BasePreviewProgram.Availability:
- Removed class androidx.tvprovider.media.tv.BasePreviewProgram.Availability
-RemovedClass: androidx.tvprovider.media.tv.BasePreviewProgram.Builder:
- Removed class androidx.tvprovider.media.tv.BasePreviewProgram.Builder
-RemovedClass: androidx.tvprovider.media.tv.BasePreviewProgram.InteractionType:
- Removed class androidx.tvprovider.media.tv.BasePreviewProgram.InteractionType
-RemovedClass: androidx.tvprovider.media.tv.BasePreviewProgram.TvSeriesItemType:
- Removed class androidx.tvprovider.media.tv.BasePreviewProgram.TvSeriesItemType
-RemovedClass: androidx.tvprovider.media.tv.BasePreviewProgram.Type:
- Removed class androidx.tvprovider.media.tv.BasePreviewProgram.Type
-RemovedClass: androidx.tvprovider.media.tv.BaseProgram.Builder:
- Removed class androidx.tvprovider.media.tv.BaseProgram.Builder
diff --git a/tv-provider/build.gradle b/tv-provider/build.gradle
index b4cbc11..902b0d5 100644
--- a/tv-provider/build.gradle
+++ b/tv-provider/build.gradle
@@ -10,7 +10,7 @@
dependencies {
api("androidx.annotation:annotation:1.1.0")
- api("androidx.core:core:1.1.0-rc01")
+ api("androidx.core:core:1.1.0")
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
androidTestImplementation(ANDROIDX_TEST_CORE)
diff --git a/ui/integration-tests/benchmark/build.gradle b/ui/integration-tests/benchmark/build.gradle
index 4d8845b..ab94dc5 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"))
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/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..bc6446b 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,8 +18,10 @@
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.disposeComposition
+import androidx.ui.test.AndroidTestCase
import androidx.ui.test.ComposeTestCase
import androidx.ui.test.TestCase
import androidx.ui.test.ToggleableTestCase
@@ -30,11 +32,11 @@
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 +68,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 +92,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 +221,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()
}
}
@@ -129,31 +242,19 @@
activity: Activity,
testCase: T
) 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.runToFirstDraw()
testCase.recomposeSyncAssertNoChanges()
measureRepeated {
runWithTimingDisabled {
- toggleState()
+ testCase.toggleState()
testCase.recomposeSyncAssertHadChanges()
testCase.requestLayout()
}
testCase.measure()
}
+ activity.disposeComposition()
}
}
@@ -164,32 +265,20 @@
activity: Activity,
testCase: T
) 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.runToFirstDraw()
testCase.recomposeSyncAssertNoChanges()
measureRepeated {
runWithTimingDisabled {
- toggleState()
+ testCase.toggleState()
testCase.recomposeSyncAssertHadChanges()
testCase.requestLayout()
testCase.measure()
}
testCase.layout()
}
+ activity.disposeComposition()
}
}
@@ -200,26 +289,13 @@
activity: Activity,
testCase: T
) 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.runToFirstDraw()
testCase.recomposeSyncAssertNoChanges()
measureRepeated {
runWithTimingDisabled {
- toggleState()
+ testCase.toggleState()
testCase.recomposeSyncAssertHadChanges()
testCase.requestLayout()
testCase.measure()
@@ -231,5 +307,6 @@
testCase.finishDraw()
}
}
+ activity.disposeComposition()
}
-}
\ No newline at end of file
+}
diff --git a/ui/integration-tests/demos/build.gradle b/ui/integration-tests/demos/build.gradle
index ca3b08d..c73e7dc 100644
--- a/ui/integration-tests/demos/build.gradle
+++ b/ui/integration-tests/demos/build.gradle
@@ -14,6 +14,7 @@
implementation(project(":ui:ui-framework:integration-tests:ui-framework-demos"))
implementation(project(":ui:ui-layout:integration-tests:ui-layout-demos"))
implementation(project(":ui:ui-material:integration-tests:ui-material-demos"))
+ implementation(project(":ui:ui-foundation:integration-tests:ui-foundation-demos"))
implementation(project(":ui:ui-text:integration-tests:ui-text-demos"))
implementation(KOTLIN_COMPOSE_STDLIB)
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..7004525 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
@@ -41,6 +41,8 @@
private val renderNode = RenderNode("test")
private var canvas: Canvas? = null
+ var view: ViewGroup
+
init {
val displayMetrics = DisplayMetrics()
activity.windowManager.defaultDisplay.getMetrics(displayMetrics)
@@ -49,11 +51,21 @@
screenWithSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.AT_MOST)
screenHeightSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.AT_MOST)
+ view = activity.findViewById(android.R.id.content)
}
- lateinit var view: ViewGroup
+ abstract fun setupContent(activity: Activity)
- abstract fun runSetup()
+ /**
+ * 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,6 +120,10 @@
}
}
+abstract class AndroidTestCase(
+ activity: Activity
+) : TestCase(activity)
+
abstract class ComposeTestCase(
activity: Activity
) : TestCase(activity) {
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..fa19488 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
@@ -31,6 +31,7 @@
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
@@ -45,18 +46,20 @@
private val states = mutableListOf<State<Boolean>>()
- override fun runSetup() {
+ override fun setupContent(activity: Activity) {
compositionContext = activity.setContent {
MaterialTheme {
- Column {
- repeat(amountOfCheckboxes) {
- FlexRow {
- inflexible {
- Text(text = "Check Me!")
- }
- expanded(1f) {
- Align(alignment = Alignment.CenterRight) {
- CheckboxWithState()
+ Surface {
+ Column {
+ repeat(amountOfCheckboxes) {
+ FlexRow {
+ inflexible {
+ Text(text = "Check Me!")
+ }
+ expanded(1f) {
+ Align(alignment = Alignment.CenterRight) {
+ CheckboxWithState()
+ }
}
}
}
@@ -65,12 +68,6 @@
}
}!!
FrameManager.nextFrame()
-
- view = activity.findViewById(android.R.id.content)
-
- measure()
- layout()
- drawSlow()
}
override fun toggleState() {
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..d33c624 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
@@ -46,7 +46,7 @@
private val model = RectanglesInColumnTestCaseColorModel(Color.Black)
- override fun runSetup() {
+ override fun setupContent(activity: Activity) {
compositionContext = activity.setContent {
MaterialTheme {
Column {
@@ -61,12 +61,6 @@
}
}!!
FrameManager.nextFrame()
-
- view = activity.findViewById(android.R.id.content)
-
- measure()
- layout()
- drawSlow()
}
override fun toggleState() {
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..526c984 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
@@ -29,6 +29,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,23 +46,19 @@
private val states = mutableListOf<State<Color>>()
- override fun runSetup() {
+ override fun setupContent(activity: Activity) {
compositionContext = activity.setContent {
MaterialTheme {
- Column {
- repeat(amountOfRectangles) {
- ColoredRectWithModel()
+ Surface {
+ Column {
+ repeat(amountOfRectangles) {
+ ColoredRectWithModel()
+ }
}
}
}
}!!
FrameManager.nextFrame()
-
- view = activity.findViewById(android.R.id.content)
-
- measure()
- layout()
- drawSlow()
}
override fun toggleState() {
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..1d93e85 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,6 +22,7 @@
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
@@ -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 setupContent(activity: Activity) {
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()
}
fun toggleState() {
diff --git a/ui/settings.gradle b/ui/settings.gradle
index c46fc60..75431ad 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")
@@ -39,6 +40,7 @@
includeProject(":ui:ui-core", "ui-core")
includeProject(":ui:ui-foundation", "ui-foundation")
includeProject(":ui:ui-foundation:integration-tests:samples", "ui-foundation/integration-tests/samples")
+includeProject(":ui:ui-foundation:integration-tests:ui-foundation-demos", "ui-foundation/integration-tests/foundation-demos")
includeProject(":ui:ui-framework", "ui-framework")
includeProject(":ui:ui-framework:integration-tests:ui-framework-demos", "ui-framework/integration-tests/framework-demos")
includeProject(":ui:ui-framework:integration-tests:samples", "ui-framework/integration-tests/samples")
diff --git a/ui/ui-animation/integration-tests/animation-demos/build.gradle b/ui/ui-animation/integration-tests/animation-demos/build.gradle
index f4a54b0..beaca6b 100644
--- a/ui/ui-animation/integration-tests/animation-demos/build.gradle
+++ b/ui/ui-animation/integration-tests/animation-demos/build.gradle
@@ -29,7 +29,7 @@
}
androidx {
- name = "Crane Animation Composables"
+ name = "Compose Animation Composables"
publish = Publish.SNAPSHOT_AND_RELEASE
mavenVersion = LibraryVersions.UI
mavenGroup = LibraryGroups.UI
diff --git a/ui/ui-core/api/1.0.0-alpha01.txt b/ui/ui-core/api/1.0.0-alpha01.txt
index 88194bc..d0525d8 100644
--- a/ui/ui-core/api/1.0.0-alpha01.txt
+++ b/ui/ui-core/api/1.0.0-alpha01.txt
@@ -1669,6 +1669,15 @@
}
+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 {
diff --git a/ui/ui-core/api/current.txt b/ui/ui-core/api/current.txt
index 88194bc..d0525d8 100644
--- a/ui/ui-core/api/current.txt
+++ b/ui/ui-core/api/current.txt
@@ -1669,6 +1669,15 @@
}
+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 {
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..d0525d8 100644
--- a/ui/ui-core/api/restricted_1.0.0-alpha01.txt
+++ b/ui/ui-core/api/restricted_1.0.0-alpha01.txt
@@ -1669,6 +1669,15 @@
}
+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 {
diff --git a/ui/ui-core/api/restricted_current.txt b/ui/ui-core/api/restricted_current.txt
index 88194bc..d0525d8 100644
--- a/ui/ui-core/api/restricted_current.txt
+++ b/ui/ui-core/api/restricted_current.txt
@@ -1669,6 +1669,15 @@
}
+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 {
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/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/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-foundation/api/1.0.0-alpha01.txt b/ui/ui-foundation/api/1.0.0-alpha01.txt
index 26ee3a8..5edc08e 100644
--- a/ui/ui-foundation/api/1.0.0-alpha01.txt
+++ b/ui/ui-foundation/api/1.0.0-alpha01.txt
@@ -17,6 +17,11 @@
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);
diff --git a/ui/ui-foundation/api/current.txt b/ui/ui-foundation/api/current.txt
index 26ee3a8..5edc08e 100644
--- a/ui/ui-foundation/api/current.txt
+++ b/ui/ui-foundation/api/current.txt
@@ -17,6 +17,11 @@
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);
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..5edc08e 100644
--- a/ui/ui-foundation/api/restricted_1.0.0-alpha01.txt
+++ b/ui/ui-foundation/api/restricted_1.0.0-alpha01.txt
@@ -17,6 +17,11 @@
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);
diff --git a/ui/ui-foundation/api/restricted_current.txt b/ui/ui-foundation/api/restricted_current.txt
index 26ee3a8..5edc08e 100644
--- a/ui/ui-foundation/api/restricted_current.txt
+++ b/ui/ui-foundation/api/restricted_current.txt
@@ -17,6 +17,11 @@
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);
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/build.gradle b/ui/ui-foundation/integration-tests/foundation-demos/build.gradle
new file mode 100644
index 0000000..c46ed89
--- /dev/null
+++ b/ui/ui-foundation/integration-tests/foundation-demos/build.gradle
@@ -0,0 +1,66 @@
+/*
+ * 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 org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+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("AndroidXUiPlugin")
+ id("org.jetbrains.kotlin.android")
+}
+
+dependencies {
+ kotlinPlugin project(path: ":compose:compose-compiler", configuration: "embeddablePlugin")
+
+ implementation(KOTLIN_COMPOSE_COROUTINES)
+ implementation(KOTLIN_COMPOSE_STDLIB)
+
+ implementation "androidx.activity:activity:1.0.0-alpha01"
+ implementation "androidx.annotation:annotation:1.1.0"
+
+ implementation project(":compose:compose-runtime")
+ implementation project(":ui:ui-core")
+ implementation project(":ui:ui-foundation")
+ implementation project(":ui:ui-framework")
+ implementation project(":ui:ui-animation")
+ implementation project(":ui:ui-layout")
+ implementation project(":ui:ui-foundation:integration-tests:samples")
+ implementation project(":ui:ui-text")
+ implementation project(':ui:ui-android-view-non-ir')
+}
+
+android {
+ tasks.withType(KotlinCompile).configureEach {
+ kotlinOptions {
+ useIR = true
+ }
+ }
+}
+
+androidx {
+ name = "Compose Foundation Demos"
+ publish = Publish.NONE
+ mavenVersion = LibraryVersions.UI
+ mavenGroup = LibraryGroups.UI
+ inceptionYear = "2019"
+ description = "This is a project for Foundation demos."
+}
diff --git a/benchmark/src/androidTest/AndroidManifest.xml b/ui/ui-foundation/integration-tests/foundation-demos/src/main/AndroidManifest.xml
similarity index 60%
copy from benchmark/src/androidTest/AndroidManifest.xml
copy to ui/ui-foundation/integration-tests/foundation-demos/src/main/AndroidManifest.xml
index f5ec776..5cb729b 100644
--- a/benchmark/src/androidTest/AndroidManifest.xml
+++ b/ui/ui-foundation/integration-tests/foundation-demos/src/main/AndroidManifest.xml
@@ -1,4 +1,3 @@
-<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2019 The Android Open Source Project
@@ -14,13 +13,20 @@
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">
+ package="androidx.ui.foundation.demos">
- <application
- android:name="androidx.benchmark.ArgumentInjectingApplication">
- <activity android:name="android.app.Activity"/>
+ <application>
+
+ <activity android:name=".AnimatedDraggableActivity"
+ android:configChanges="orientation|screenSize"
+ android:label="Foundation/AnimatedDraggable">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="androidx.ui.demos.SAMPLE_CODE" />
+ </intent-filter>
+ </activity>
</application>
-</manifest>
\ No newline at end of file
+</manifest>
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
new file mode 100644
index 0000000..d33545a
--- /dev/null
+++ b/ui/ui-foundation/integration-tests/foundation-demos/src/main/java/androidx/ui/foundation/demos/AnimatedDraggableActivity.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.ui.foundation.demos
+
+import android.app.Activity
+import android.os.Bundle
+import androidx.compose.composer
+import androidx.ui.core.setContent
+import androidx.ui.foundation.samples.AnimatedDraggableSample
+import androidx.ui.layout.Wrap
+
+class AnimatedDraggableActivity : Activity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContent {
+ Wrap {
+ AnimatedDraggableSample()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ui/ui-foundation/integration-tests/samples/build.gradle b/ui/ui-foundation/integration-tests/samples/build.gradle
index 446e4b2..beb8562 100644
--- a/ui/ui-foundation/integration-tests/samples/build.gradle
+++ b/ui/ui-foundation/integration-tests/samples/build.gradle
@@ -34,6 +34,7 @@
implementation project(":compose:compose-runtime")
implementation project(":ui:ui-core")
+ implementation project(":ui:ui-animation")
implementation project(":ui:ui-foundation")
implementation project(":ui:ui-framework")
implementation project(":ui:ui-layout")
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
new file mode 100644
index 0000000..3714185
--- /dev/null
+++ b/ui/ui-foundation/integration-tests/samples/src/main/java/androidx/ui/foundation/samples/AnimatedDraggableSamples.kt
@@ -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.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/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/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..0bfd0af
--- /dev/null
+++ b/ui/ui-foundation/src/androidTest/java/androidx/ui/foundation/DialogUiTest.kt
@@ -0,0 +1,172 @@
+/*
+ * 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.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 { label.equals(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 { label.equals(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/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..8ccc5b6
--- /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 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 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/gestures/AnimatedDraggable.kt b/ui/ui-foundation/src/main/java/androidx/ui/foundation/gestures/AnimatedDraggable.kt
index a429d5b..39e47e1 100644
--- 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
@@ -46,7 +46,7 @@
*
* If you need only animations without gesture support, consider using [animatedFloat] instead.
*
- * //TODO: Add sample
+ * @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
diff --git a/ui/ui-framework/api/1.0.0-alpha01.txt b/ui/ui-framework/api/1.0.0-alpha01.txt
index 7e4b05c..c7820fa 100644
--- a/ui/ui-framework/api/1.0.0-alpha01.txt
+++ b/ui/ui-framework/api/1.0.0-alpha01.txt
@@ -34,7 +34,7 @@
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 = {});
+ 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 = {}, androidx.ui.core.VisualTransformation? visualTransformation = null);
}
public final class IntrinsicMeasurementsReceiver implements androidx.ui.core.DensityReceiver {
@@ -92,6 +92,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 +107,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();
@@ -174,12 +186,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);
@@ -222,6 +252,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 +296,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 {
@@ -278,6 +313,10 @@
method public static void SelectionContainer(androidx.ui.core.selection.Selection? selection, kotlin.jvm.functions.Function1<? super androidx.ui.core.selection.Selection,kotlin.Unit> onSelectionChange, androidx.ui.core.selection.SelectionMode mode = SelectionMode.Vertical, kotlin.jvm.functions.Function0<kotlin.Unit> children);
}
+ public final class SelectionHandlesKt {
+ ctor public SelectionHandlesKt();
+ }
+
public final class SelectionKt {
ctor public SelectionKt();
}
diff --git a/ui/ui-framework/api/current.txt b/ui/ui-framework/api/current.txt
index 7e4b05c..c7820fa 100644
--- a/ui/ui-framework/api/current.txt
+++ b/ui/ui-framework/api/current.txt
@@ -34,7 +34,7 @@
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 = {});
+ 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 = {}, androidx.ui.core.VisualTransformation? visualTransformation = null);
}
public final class IntrinsicMeasurementsReceiver implements androidx.ui.core.DensityReceiver {
@@ -92,6 +92,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 +107,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();
@@ -174,12 +186,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);
@@ -222,6 +252,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 +296,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 {
@@ -278,6 +313,10 @@
method public static void SelectionContainer(androidx.ui.core.selection.Selection? selection, kotlin.jvm.functions.Function1<? super androidx.ui.core.selection.Selection,kotlin.Unit> onSelectionChange, androidx.ui.core.selection.SelectionMode mode = SelectionMode.Vertical, kotlin.jvm.functions.Function0<kotlin.Unit> children);
}
+ public final class SelectionHandlesKt {
+ ctor public SelectionHandlesKt();
+ }
+
public final class SelectionKt {
ctor public SelectionKt();
}
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 7e4b05c..c7820fa 100644
--- a/ui/ui-framework/api/restricted_1.0.0-alpha01.txt
+++ b/ui/ui-framework/api/restricted_1.0.0-alpha01.txt
@@ -34,7 +34,7 @@
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 = {});
+ 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 = {}, androidx.ui.core.VisualTransformation? visualTransformation = null);
}
public final class IntrinsicMeasurementsReceiver implements androidx.ui.core.DensityReceiver {
@@ -92,6 +92,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 +107,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();
@@ -174,12 +186,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);
@@ -222,6 +252,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 +296,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 {
@@ -278,6 +313,10 @@
method public static void SelectionContainer(androidx.ui.core.selection.Selection? selection, kotlin.jvm.functions.Function1<? super androidx.ui.core.selection.Selection,kotlin.Unit> onSelectionChange, androidx.ui.core.selection.SelectionMode mode = SelectionMode.Vertical, kotlin.jvm.functions.Function0<kotlin.Unit> children);
}
+ public final class SelectionHandlesKt {
+ ctor public SelectionHandlesKt();
+ }
+
public final class SelectionKt {
ctor public SelectionKt();
}
diff --git a/ui/ui-framework/api/restricted_current.txt b/ui/ui-framework/api/restricted_current.txt
index 7e4b05c..c7820fa 100644
--- a/ui/ui-framework/api/restricted_current.txt
+++ b/ui/ui-framework/api/restricted_current.txt
@@ -34,7 +34,7 @@
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 = {});
+ 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 = {}, androidx.ui.core.VisualTransformation? visualTransformation = null);
}
public final class IntrinsicMeasurementsReceiver implements androidx.ui.core.DensityReceiver {
@@ -92,6 +92,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 +107,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();
@@ -174,12 +186,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);
@@ -222,6 +252,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 +296,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 {
@@ -278,6 +313,10 @@
method public static void SelectionContainer(androidx.ui.core.selection.Selection? selection, kotlin.jvm.functions.Function1<? super androidx.ui.core.selection.Selection,kotlin.Unit> onSelectionChange, androidx.ui.core.selection.SelectionMode mode = SelectionMode.Vertical, kotlin.jvm.functions.Function0<kotlin.Unit> children);
}
+ public final class SelectionHandlesKt {
+ ctor public SelectionHandlesKt();
+ }
+
public final class SelectionKt {
ctor public SelectionKt();
}
diff --git a/ui/ui-framework/build.gradle b/ui/ui-framework/build.gradle
index 6d07e4c..2bf6a35 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
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..933fee0 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,14 @@
<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.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/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/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..dc3feee 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,26 @@
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.PressReleasedGestureDetector
+import androidx.ui.core.round
+import androidx.ui.core.toRect
/**
* Demo app created to study some complex interactions of multiple DragGestureDetectors.
@@ -50,11 +52,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
) {
@@ -138,6 +140,10 @@
height: Dp
) {
+ val pressedColor = Color(0x1f000000)
+ val itemColor = Color(0xFFFFFFFF.toInt())
+
+ val color = +state { itemColor }
val pressed = +state { false }
val onStart: (PxPosition) -> Unit = {
@@ -148,29 +154,43 @@
pressed.value = false
}
- val resolvedColor =
- if (pressed.value) {
- Color(0x1f000000)
- } else {
- Color(0xFFFFFFFF.toInt())
- }
+ val onRelease = {
+ color.value = color.value.next()
+ pressed.value = false
+ }
+
+ val onLongPress = { _: PxPosition ->
+ color.value = color.value.prev()
+ pressed.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 (pressed.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)
- ) {}
+ PressReleasedGestureDetector(onRelease, false) {
+ LongPressGestureDetector(onLongPress) {
+ Layout(children) { _, constraints ->
+ layout(
+ constraints.maxWidth,
+ height.toIntPx().coerceIn(constraints.minHeight, constraints.maxHeight)
+ ) {}
+ }
+ }
}
}
}
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..e49a48e 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
@@ -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/main/java/androidx/ui/core/InputField.kt b/ui/ui-framework/src/main/java/androidx/ui/core/InputField.kt
index a2bb749..5da2696 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/InputField.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/InputField.kt
@@ -31,7 +31,6 @@
import androidx.ui.input.EditorState
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
@@ -100,7 +99,12 @@
onValueChange: (EditorState) -> 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 +115,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) {
+ InputFieldDelegate.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
@@ -123,10 +130,11 @@
// States
val hasFocus = +state { false }
+ val coords = +state<LayoutCoordinates?> { null }
processor.onNewState(value, textInputService)
TextInputEventObserver(
- onPress = { InputFieldDelegate.onPress(textInputService) },
+ onPress = { },
onFocus = {
hasFocus.value = true
InputFieldDelegate.onFocus(
@@ -137,6 +145,18 @@
imeAction,
onValueChange,
onImeActionPerformed)
+ coords.value?.let { coords ->
+ textInputService?.let { textInputService ->
+ InputFieldDelegate.notifyFocusedRect(
+ value,
+ textPainter,
+ coords,
+ textInputService,
+ hasFocus.value,
+ offsetMap
+ )
+ }
+ }
},
onBlur = {
hasFocus.value = false
@@ -146,7 +166,16 @@
onValueChange)
},
onDragAt = { InputFieldDelegate.onDragAt(it) },
- onRelease = { InputFieldDelegate.onRelease(it, textPainter, processor, onValueChange) }
+ onRelease = {
+ InputFieldDelegate.onRelease(
+ it,
+ textPainter,
+ processor,
+ offsetMap,
+ onValueChange,
+ textInputService,
+ hasFocus.value)
+ }
) {
Layout(
children = @Composable {
@@ -154,18 +183,21 @@
if (textInputService != null) {
// TODO(nona): notify focused rect in onPreDraw equivalent callback for
// supporting multiline text.
+ coords.value = it
InputFieldDelegate.notifyFocusedRect(
value,
textPainter,
it,
textInputService,
- hasFocus.value
+ hasFocus.value,
+ offsetMap
)
}
}
Draw { canvas, _ -> InputFieldDelegate.draw(
canvas,
value,
+ offsetMap,
textPainter,
hasFocus.value,
editorStyle) }
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/InputFieldDelegate.kt b/ui/ui-framework/src/main/java/androidx/ui/core/InputFieldDelegate.kt
index 3732906..1c70f23 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/InputFieldDelegate.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/InputFieldDelegate.kt
@@ -17,7 +17,6 @@
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
@@ -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.
@@ -81,33 +81,33 @@
fun draw(
canvas: Canvas,
value: EditorState,
+ 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)
}
/**
@@ -121,18 +121,19 @@
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, 0f, 0f)
+ Rect(0f, 0f, 1.0f, textPainter.preferredLineHeight)
}
val globalLT = layoutCoordinates.localToRoot(PxPosition(bbox.left.px, bbox.top.px))
@@ -163,16 +164,6 @@
}
/**
- * 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.
@@ -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: (EditorState) -> 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,7 +214,7 @@
* @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(
@@ -246,5 +250,21 @@
onEditCommand(listOf(FinishComposingTextEditOp()), editProcessor, onValueChange)
textInputService?.stopInput()
}
+
+ /**
+ * Helper function of applying visual transformation method to the EditorState.
+ *
+ * @param value An editor state
+ * @param visualTransformation A visual transformation
+ */
+ @JvmStatic
+ fun applyVisualFilter(
+ value: EditorState,
+ 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/Text.kt b/ui/ui-framework/src/main/java/androidx/ui/core/Text.kt
index 524f483..7e1680cb 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
@@ -31,7 +31,6 @@
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
@@ -228,9 +227,9 @@
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 +285,12 @@
onSelectionChange = { internalSelection.value = it },
textPainter = textPainter
)
-
if (!textSelectionProcessor.isSelected) return null
// 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!!
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..45f178a 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.
@@ -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()
}
}
@@ -102,8 +107,11 @@
.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()
}
}
@@ -121,8 +129,11 @@
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,
+ coroutineContext: CoroutineContext,
@Children 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/LongPressGestureDetector.kt b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/LongPressGestureDetector.kt
new file mode 100644
index 0000000..a3f894c
--- /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 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/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 c238d48..a3bd5e5 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
@@ -22,22 +22,13 @@
import androidx.compose.memo
import androidx.compose.unaryPlus
import androidx.ui.core.Constraints
-import androidx.ui.core.Draw
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
-import androidx.ui.core.toRect
-import androidx.ui.graphics.Color
-import androidx.ui.painting.Paint
-
-private val HANDLE_WIDTH = 100.px
-private val HANDLE_HEIGHT = 100.px
/**
* Selection Widget.
@@ -89,7 +80,7 @@
dragObserver = manager.handleDragObserver(dragStartHandle = true)
) {
Layout(
- children = { SelectionHandle() },
+ children = { LeftPointingSelectionHandle() },
layoutBlock = { _, constraints ->
layout(constraints.minWidth, constraints.minHeight) {}
})
@@ -101,7 +92,7 @@
dragObserver = manager.handleDragObserver(dragStartHandle = false)
) {
Layout(
- children = { SelectionHandle() },
+ children = { RightPointingSelectionHandle() },
layoutBlock = { _, constraints ->
layout(constraints.minWidth, constraints.minHeight) {}
})
@@ -136,31 +127,16 @@
) {
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 - HANDLE_HEIGHT)
- end.place(endOffset.x, endOffset.y - HANDLE_HEIGHT)
+ start.place(startOffset.x - HANDLE_WIDTH, startOffset.y)
+ end.place(endOffset.x, endOffset.y)
}
}
}))
}
}
-
-@Composable
-internal fun SelectionHandle() {
- val paint = +memo { Paint() }
- paint.color = Color(0xAAD94633.toInt())
- Draw { canvas, parentSize ->
- canvas.drawRect(parentSize.toRect(), paint)
- }
-}
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/selection/SelectionHandles.kt b/ui/ui-framework/src/main/java/androidx/ui/core/selection/SelectionHandles.kt
new file mode 100644
index 0000000..6c8d9f2
--- /dev/null
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/selection/SelectionHandles.kt
@@ -0,0 +1,86 @@
+/*
+ * 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.selection
+
+import androidx.compose.Composable
+import androidx.compose.composer
+import androidx.compose.memo
+import androidx.compose.unaryPlus
+import androidx.ui.core.Draw
+import androidx.ui.core.px
+import androidx.ui.engine.geometry.Rect
+import androidx.ui.graphics.Color
+import androidx.ui.painting.Paint
+import androidx.ui.painting.Path
+
+internal val HANDLE_WIDTH = 80.px
+internal val HANDLE_HEIGHT = 80.px
+private val HANDLE_COLOR = Color(0xFF2B28F5.toInt())
+
+@Composable
+internal fun LeftPointingSelectionHandle() {
+ val paint = +memo { Paint() }
+ paint.color = HANDLE_COLOR
+ Draw { canvas, _ ->
+ var path = Path()
+ path.addRect(
+ Rect(
+ top = 0f,
+ bottom = 0.5f * HANDLE_HEIGHT.value,
+ left = 0.5f * HANDLE_WIDTH.value,
+ right = HANDLE_WIDTH.value
+ )
+ )
+ path.addOval(
+ Rect(
+ top = 0f,
+ bottom = HANDLE_HEIGHT.value,
+ left = 0f,
+ right = HANDLE_WIDTH.value
+ )
+ )
+
+ canvas.drawPath(path, paint)
+ }
+}
+
+@Composable
+internal fun RightPointingSelectionHandle() {
+ val paint = +memo { Paint() }
+ paint.color = HANDLE_COLOR
+ Draw { canvas, _ ->
+ var path = Path()
+ path.addRect(
+ Rect(
+ top = 0f,
+ bottom = 0.5f * HANDLE_HEIGHT.value,
+ left = 0f,
+ right = 0.5f * HANDLE_WIDTH.value
+ )
+ )
+ path.addOval(
+ Rect(
+ top = 0f,
+ bottom = HANDLE_HEIGHT.value,
+ left = 0f,
+ right = HANDLE_WIDTH.value
+ )
+ )
+
+ canvas.drawPath(path, paint)
+ }
+}
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 53c9ef5..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 {
/**
@@ -65,6 +64,14 @@
private var dragTotalDistance = PxPosition.Origin
/**
+ * A flag to check if the selection start or end handle is being dragged.
+ * If this value is true, then onPress will not select any text.
+ * This value will be set to true when either handle is being dragged, and be reset to false
+ * when the dragging is stopped.
+ */
+ private var draggingHandle = false
+
+ /**
* Allow a Text composable to "register" itself with the manager
*/
override fun subscribe(handler: TextSelectionHandler): Any {
@@ -80,6 +87,7 @@
}
fun onPress(position: PxPosition) {
+ if (draggingHandle) return
var result: Selection? = null
for (handler in handlers) {
result += handler.getSelection(
@@ -90,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 {
@@ -112,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
@@ -128,6 +140,7 @@
// Zero out the total distance that being dragged.
dragTotalDistance = PxPosition.Origin
+ draggingHandle = true
}
override fun onDrag(dragDistance: PxPosition): PxPosition {
@@ -140,7 +153,7 @@
} else {
containerLayoutCoordinates.childToLocal(
selection!!.startLayoutCoordinates!!,
- getCoordinatesForCharacter(selection!!.startOffset)
+ getAdjustedCoordinates(selection!!.startCoordinates)
)
}
@@ -148,7 +161,7 @@
if (dragStartHandle) {
containerLayoutCoordinates.childToLocal(
selection!!.endLayoutCoordinates!!,
- getCoordinatesForCharacter(selection!!.endOffset)
+ getAdjustedCoordinates(selection!!.endCoordinates)
)
} else {
dragBeginPosition + dragTotalDistance
@@ -163,6 +176,11 @@
onSelectionChange(result)
return dragDistance
}
+
+ override fun onStop(velocity: PxPosition) {
+ super.onStop(velocity)
+ draggingHandle = false
+ }
}
}
}
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/test/java/androidx/ui/core/InputFieldDelegateTest.kt b/ui/ui-framework/src/test/java/androidx/ui/core/InputFieldDelegateTest.kt
index 595f498..f934c88 100644
--- a/ui/ui-framework/src/test/java/androidx/ui/core/InputFieldDelegateTest.kt
+++ b/ui/ui-framework/src/test/java/androidx/ui/core/InputFieldDelegateTest.kt
@@ -59,6 +59,37 @@
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()
@@ -80,11 +111,13 @@
textPainter = painter,
value = EditorState(text = "Hello, World", selection = selection),
editorStyle = EditorStyle(selectionColor = selectionColor),
- hasFocus = true)
+ hasFocus = true,
+ offsetMap = identityOffsetMap
+ )
verify(painter, times(1)).paintBackground(
- eq(selection.start), eq(selection.end), eq(selectionColor), eq(canvas), any())
- verify(painter, times(1)).paint(eq(canvas), any())
+ eq(selection.start), eq(selection.end), eq(selectionColor), eq(canvas))
+ verify(painter, times(1)).paint(eq(canvas))
verify(painter, never()).paintCursor(any(), any())
}
@@ -98,11 +131,13 @@
textPainter = painter,
value = EditorState(text = "Hello, World", selection = cursor),
editorStyle = EditorStyle(),
- hasFocus = true)
+ hasFocus = true,
+ offsetMap = identityOffsetMap
+ )
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())
+ verify(painter, times(1)).paint(eq(canvas))
+ verify(painter, never()).paintBackground(any(), any(), any(), any())
}
@Test
@@ -114,11 +149,13 @@
textPainter = painter,
value = EditorState(text = "Hello, World", selection = cursor),
editorStyle = EditorStyle(),
- hasFocus = false)
+ hasFocus = false,
+ offsetMap = identityOffsetMap
+ )
verify(painter, never()).paintCursor(any(), any())
- verify(painter, times(1)).paint(eq(canvas), any())
- verify(painter, never()).paintBackground(any(), any(), any(), any(), any())
+ verify(painter, times(1)).paint(eq(canvas))
+ verify(painter, never()).paintBackground(any(), any(), any(), any())
}
@Test
@@ -134,11 +171,13 @@
value = EditorState(text = "Hello, World", selection = cursor,
composition = composition),
editorStyle = EditorStyle(compositionColor = compositionColor),
- hasFocus = true)
+ hasFocus = true,
+ offsetMap = identityOffsetMap
+ )
verify(painter, times(1)).paintBackground(
- eq(composition.start), eq(composition.end), eq(compositionColor), eq(canvas), any())
- verify(painter, times(1)).paint(eq(canvas), any())
+ 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())
}
@@ -160,18 +199,45 @@
val offset = 10
val dummyEditorState = EditorState(text = "Hello, World", selection = TextRange(1, 1))
- whenever(painter.getPositionForOffset(position.toOffset())).thenReturn(offset)
+ whenever(painter.getOffsetForPosition(position)).thenReturn(offset)
val captor = argumentCaptor<List<EditOperation>>()
whenever(processor.onEditCommands(captor.capture())).thenReturn(dummyEditorState)
- InputFieldDelegate.onRelease(position, painter, processor, onValueChange)
+ InputFieldDelegate.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)
+ InputFieldDelegate.onRelease(
+ position,
+ painter,
+ processor,
+ identityOffsetMap,
+ onValueChange,
+ textInputService,
+ false)
+
+ verify(onValueChange, never()).invoke(any())
+ verify(textInputService).showSoftwareKeyboard()
}
@Test
@@ -186,22 +252,17 @@
composition = TextRange(1, 3)
),
editorStyle = EditorStyle(compositionColor = Color.Red),
- hasFocus = true
- )
+ hasFocus = true,
+ offsetMap = identityOffsetMap
+ )
inOrder(painter) {
- verify(painter).paintBackground(eq(1), eq(3), eq(Color.Red), eq(canvas), any())
+ verify(painter).paintBackground(eq(1), eq(3), eq(Color.Red), eq(canvas))
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,
@@ -241,7 +302,9 @@
painter,
layoutCoordinates,
textInputService,
- true /* hasFocus */)
+ true /* hasFocus */,
+ identityOffsetMap
+ )
verify(textInputService).notifyFocusedRect(any())
}
@@ -253,7 +316,9 @@
painter,
layoutCoordinates,
textInputService,
- false /* hasFocus */)
+ false /* hasFocus */,
+ identityOffsetMap
+ )
verify(textInputService, never()).notifyFocusedRect(any())
}
@@ -269,14 +334,16 @@
painter,
layoutCoordinates,
textInputService,
- true /* hasFocus */)
+ true /* hasFocus */,
+ identityOffsetMap
+ )
verify(textInputService).notifyFocusedRect(any())
}
@Test
fun notify_rect_empty() {
- val dummyRect = Rect(0f, 1f, 2f, 3f)
- whenever(painter.getBoundingBox(any())).thenReturn(dummyRect)
+ 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))
@@ -285,8 +352,11 @@
painter,
layoutCoordinates,
textInputService,
- true /* hasFocus */)
- verify(textInputService).notifyFocusedRect(any())
+ true, /* hasFocus */
+ identityOffsetMap)
+ val captor = argumentCaptor<Rect>()
+ verify(textInputService).notifyFocusedRect(captor.capture())
+ assertEquals(dummyHeight, captor.firstValue.height)
}
@Test
@@ -311,4 +381,93 @@
verify(painter, times(1)).layout(constraints)
}
+
+ @Test
+ fun check_draw_uses_offset_map() {
+ val selection = TextRange(1, 3)
+ val selectionColor = Color.Blue
+
+ InputFieldDelegate.draw(
+ canvas = canvas,
+ textPainter = painter,
+ value = EditorState(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 = EditorState(text = "Hello, World", selection = TextRange(1, 3))
+ whenever(painter.getBoundingBox(any())).thenReturn(dummyRect)
+ whenever(layoutCoordinates.localToRoot(any())).thenReturn(dummyPoint)
+
+ InputFieldDelegate.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 = EditorState(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)
+
+ InputFieldDelegate.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) = InputFieldDelegate.applyVisualFilter(
+ EditorState(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/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/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..2af0b7e 100644
--- a/ui/ui-layout/build.gradle
+++ b/ui/ui-layout/build.gradle
@@ -27,6 +27,7 @@
id("com.android.library")
id("AndroidXUiPlugin")
id("org.jetbrains.kotlin.android")
+ id("androidx.benchmark")
}
dependencies {
@@ -44,7 +45,7 @@
testImplementation(ANDROIDX_TEST_RUNNER)
testImplementation(JUNIT)
- androidTestImplementation project(":benchmark")
+ androidTestImplementation project(":benchmark:benchmark-junit4")
androidTestImplementation project(":ui:ui-platform")
androidTestImplementation(ANDROIDX_TEST_RULES)
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/java/androidx/ui/layout/test/ScrollerPerformance.kt b/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/ScrollerPerformance.kt
index 3efac80..652694d 100644
--- 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
@@ -17,8 +17,8 @@
package androidx.ui.layout.test
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.Composable
import androidx.compose.CompositionContext
import androidx.compose.FrameManager
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/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-material/api/1.0.0-alpha01.txt b/ui/ui-material/api/1.0.0-alpha01.txt
index 17ead5a..eba63b6 100644
--- a/ui/ui-material/api/1.0.0-alpha01.txt
+++ b/ui/ui-material/api/1.0.0-alpha01.txt
@@ -9,7 +9,7 @@
}), kotlin.jvm.functions.Function0<kotlin.Unit>? navigationIcon = (kotlin.jvm.functions.Function0<? extends kotlin.Unit>)null, kotlin.jvm.functions.Function0<kotlin.Unit>? floatingActionButton = (kotlin.jvm.functions.Function0<? extends kotlin.Unit>)null, androidx.ui.material.BottomAppBar.FabPosition fabPosition = Center, java.util.List<? extends T>? contextualActions = null, kotlin.jvm.functions.Function1<? super T,kotlin.Unit> action = {});
method public static <T> void TopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title = {}, androidx.ui.graphics.Color color = +themeColor({
primary
-}), kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon = {}, java.util.List<? extends T>? contextualActions = null, kotlin.jvm.functions.Function1<? super T,kotlin.Unit> action = {});
+}), kotlin.jvm.functions.Function0<kotlin.Unit>? navigationIcon = (kotlin.jvm.functions.Function0<? extends kotlin.Unit>)null, java.util.List<? extends T>? contextualActions = null, kotlin.jvm.functions.Function1<? super T,kotlin.Unit> action = {});
}
public final class BottomAppBar {
@@ -211,7 +211,26 @@
public final class TabKt {
ctor public TabKt();
method public static void Tab(String? text = null, androidx.ui.painting.Image? icon = null, boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onSelected);
- method public static <T> void TabRow(java.util.List<? extends T> items, int selectedIndex, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,kotlin.Unit> tab);
+ method public static <T> void TabRow(java.util.List<? extends T> items, int selectedIndex, kotlin.jvm.functions.Function1<? super java.util.List<androidx.ui.material.TabRow.TabPosition>,kotlin.Unit> indicatorContainer = { tabPositions -> TabRow.IndicatorContainer(tabPositions, selectedIndex, {
+ TabRow.Indicator()
+}) }, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,kotlin.Unit> tab);
+ }
+
+ public final class TabRow {
+ method public void Indicator();
+ method public void IndicatorContainer(java.util.List<androidx.ui.material.TabRow.TabPosition> tabPositions, int selectedIndex, kotlin.jvm.functions.Function0<kotlin.Unit> indicator);
+ field public static final androidx.ui.material.TabRow! INSTANCE;
+ }
+
+ public static final class TabRow.TabPosition {
+ ctor public TabRow.TabPosition(androidx.ui.core.Dp xOffset, androidx.ui.core.Dp width, androidx.ui.core.Dp height);
+ method public androidx.ui.core.Dp component1();
+ method public androidx.ui.core.Dp component2();
+ method public androidx.ui.core.Dp component3();
+ method public androidx.ui.material.TabRow.TabPosition copy(androidx.ui.core.Dp xOffset, androidx.ui.core.Dp width, androidx.ui.core.Dp height);
+ method public androidx.ui.core.Dp getHeight();
+ method public androidx.ui.core.Dp getWidth();
+ method public androidx.ui.core.Dp getXOffset();
}
public final class TextKt {
diff --git a/ui/ui-material/api/current.txt b/ui/ui-material/api/current.txt
index 17ead5a..eba63b6 100644
--- a/ui/ui-material/api/current.txt
+++ b/ui/ui-material/api/current.txt
@@ -9,7 +9,7 @@
}), kotlin.jvm.functions.Function0<kotlin.Unit>? navigationIcon = (kotlin.jvm.functions.Function0<? extends kotlin.Unit>)null, kotlin.jvm.functions.Function0<kotlin.Unit>? floatingActionButton = (kotlin.jvm.functions.Function0<? extends kotlin.Unit>)null, androidx.ui.material.BottomAppBar.FabPosition fabPosition = Center, java.util.List<? extends T>? contextualActions = null, kotlin.jvm.functions.Function1<? super T,kotlin.Unit> action = {});
method public static <T> void TopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title = {}, androidx.ui.graphics.Color color = +themeColor({
primary
-}), kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon = {}, java.util.List<? extends T>? contextualActions = null, kotlin.jvm.functions.Function1<? super T,kotlin.Unit> action = {});
+}), kotlin.jvm.functions.Function0<kotlin.Unit>? navigationIcon = (kotlin.jvm.functions.Function0<? extends kotlin.Unit>)null, java.util.List<? extends T>? contextualActions = null, kotlin.jvm.functions.Function1<? super T,kotlin.Unit> action = {});
}
public final class BottomAppBar {
@@ -211,7 +211,26 @@
public final class TabKt {
ctor public TabKt();
method public static void Tab(String? text = null, androidx.ui.painting.Image? icon = null, boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onSelected);
- method public static <T> void TabRow(java.util.List<? extends T> items, int selectedIndex, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,kotlin.Unit> tab);
+ method public static <T> void TabRow(java.util.List<? extends T> items, int selectedIndex, kotlin.jvm.functions.Function1<? super java.util.List<androidx.ui.material.TabRow.TabPosition>,kotlin.Unit> indicatorContainer = { tabPositions -> TabRow.IndicatorContainer(tabPositions, selectedIndex, {
+ TabRow.Indicator()
+}) }, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,kotlin.Unit> tab);
+ }
+
+ public final class TabRow {
+ method public void Indicator();
+ method public void IndicatorContainer(java.util.List<androidx.ui.material.TabRow.TabPosition> tabPositions, int selectedIndex, kotlin.jvm.functions.Function0<kotlin.Unit> indicator);
+ field public static final androidx.ui.material.TabRow! INSTANCE;
+ }
+
+ public static final class TabRow.TabPosition {
+ ctor public TabRow.TabPosition(androidx.ui.core.Dp xOffset, androidx.ui.core.Dp width, androidx.ui.core.Dp height);
+ method public androidx.ui.core.Dp component1();
+ method public androidx.ui.core.Dp component2();
+ method public androidx.ui.core.Dp component3();
+ method public androidx.ui.material.TabRow.TabPosition copy(androidx.ui.core.Dp xOffset, androidx.ui.core.Dp width, androidx.ui.core.Dp height);
+ method public androidx.ui.core.Dp getHeight();
+ method public androidx.ui.core.Dp getWidth();
+ method public androidx.ui.core.Dp getXOffset();
}
public final class TextKt {
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 17ead5a..eba63b6 100644
--- a/ui/ui-material/api/restricted_1.0.0-alpha01.txt
+++ b/ui/ui-material/api/restricted_1.0.0-alpha01.txt
@@ -9,7 +9,7 @@
}), kotlin.jvm.functions.Function0<kotlin.Unit>? navigationIcon = (kotlin.jvm.functions.Function0<? extends kotlin.Unit>)null, kotlin.jvm.functions.Function0<kotlin.Unit>? floatingActionButton = (kotlin.jvm.functions.Function0<? extends kotlin.Unit>)null, androidx.ui.material.BottomAppBar.FabPosition fabPosition = Center, java.util.List<? extends T>? contextualActions = null, kotlin.jvm.functions.Function1<? super T,kotlin.Unit> action = {});
method public static <T> void TopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title = {}, androidx.ui.graphics.Color color = +themeColor({
primary
-}), kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon = {}, java.util.List<? extends T>? contextualActions = null, kotlin.jvm.functions.Function1<? super T,kotlin.Unit> action = {});
+}), kotlin.jvm.functions.Function0<kotlin.Unit>? navigationIcon = (kotlin.jvm.functions.Function0<? extends kotlin.Unit>)null, java.util.List<? extends T>? contextualActions = null, kotlin.jvm.functions.Function1<? super T,kotlin.Unit> action = {});
}
public final class BottomAppBar {
@@ -211,7 +211,26 @@
public final class TabKt {
ctor public TabKt();
method public static void Tab(String? text = null, androidx.ui.painting.Image? icon = null, boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onSelected);
- method public static <T> void TabRow(java.util.List<? extends T> items, int selectedIndex, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,kotlin.Unit> tab);
+ method public static <T> void TabRow(java.util.List<? extends T> items, int selectedIndex, kotlin.jvm.functions.Function1<? super java.util.List<androidx.ui.material.TabRow.TabPosition>,kotlin.Unit> indicatorContainer = { tabPositions -> TabRow.IndicatorContainer(tabPositions, selectedIndex, {
+ TabRow.Indicator()
+}) }, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,kotlin.Unit> tab);
+ }
+
+ public final class TabRow {
+ method public void Indicator();
+ method public void IndicatorContainer(java.util.List<androidx.ui.material.TabRow.TabPosition> tabPositions, int selectedIndex, kotlin.jvm.functions.Function0<kotlin.Unit> indicator);
+ field public static final androidx.ui.material.TabRow! INSTANCE;
+ }
+
+ public static final class TabRow.TabPosition {
+ ctor public TabRow.TabPosition(androidx.ui.core.Dp xOffset, androidx.ui.core.Dp width, androidx.ui.core.Dp height);
+ method public androidx.ui.core.Dp component1();
+ method public androidx.ui.core.Dp component2();
+ method public androidx.ui.core.Dp component3();
+ method public androidx.ui.material.TabRow.TabPosition copy(androidx.ui.core.Dp xOffset, androidx.ui.core.Dp width, androidx.ui.core.Dp height);
+ method public androidx.ui.core.Dp getHeight();
+ method public androidx.ui.core.Dp getWidth();
+ method public androidx.ui.core.Dp getXOffset();
}
public final class TextKt {
diff --git a/ui/ui-material/api/restricted_current.txt b/ui/ui-material/api/restricted_current.txt
index 17ead5a..eba63b6 100644
--- a/ui/ui-material/api/restricted_current.txt
+++ b/ui/ui-material/api/restricted_current.txt
@@ -9,7 +9,7 @@
}), kotlin.jvm.functions.Function0<kotlin.Unit>? navigationIcon = (kotlin.jvm.functions.Function0<? extends kotlin.Unit>)null, kotlin.jvm.functions.Function0<kotlin.Unit>? floatingActionButton = (kotlin.jvm.functions.Function0<? extends kotlin.Unit>)null, androidx.ui.material.BottomAppBar.FabPosition fabPosition = Center, java.util.List<? extends T>? contextualActions = null, kotlin.jvm.functions.Function1<? super T,kotlin.Unit> action = {});
method public static <T> void TopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title = {}, androidx.ui.graphics.Color color = +themeColor({
primary
-}), kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon = {}, java.util.List<? extends T>? contextualActions = null, kotlin.jvm.functions.Function1<? super T,kotlin.Unit> action = {});
+}), kotlin.jvm.functions.Function0<kotlin.Unit>? navigationIcon = (kotlin.jvm.functions.Function0<? extends kotlin.Unit>)null, java.util.List<? extends T>? contextualActions = null, kotlin.jvm.functions.Function1<? super T,kotlin.Unit> action = {});
}
public final class BottomAppBar {
@@ -211,7 +211,26 @@
public final class TabKt {
ctor public TabKt();
method public static void Tab(String? text = null, androidx.ui.painting.Image? icon = null, boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onSelected);
- method public static <T> void TabRow(java.util.List<? extends T> items, int selectedIndex, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,kotlin.Unit> tab);
+ method public static <T> void TabRow(java.util.List<? extends T> items, int selectedIndex, kotlin.jvm.functions.Function1<? super java.util.List<androidx.ui.material.TabRow.TabPosition>,kotlin.Unit> indicatorContainer = { tabPositions -> TabRow.IndicatorContainer(tabPositions, selectedIndex, {
+ TabRow.Indicator()
+}) }, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,kotlin.Unit> tab);
+ }
+
+ public final class TabRow {
+ method public void Indicator();
+ method public void IndicatorContainer(java.util.List<androidx.ui.material.TabRow.TabPosition> tabPositions, int selectedIndex, kotlin.jvm.functions.Function0<kotlin.Unit> indicator);
+ field public static final androidx.ui.material.TabRow! INSTANCE;
+ }
+
+ public static final class TabRow.TabPosition {
+ ctor public TabRow.TabPosition(androidx.ui.core.Dp xOffset, androidx.ui.core.Dp width, androidx.ui.core.Dp height);
+ method public androidx.ui.core.Dp component1();
+ method public androidx.ui.core.Dp component2();
+ method public androidx.ui.core.Dp component3();
+ method public androidx.ui.material.TabRow.TabPosition copy(androidx.ui.core.Dp xOffset, androidx.ui.core.Dp width, androidx.ui.core.Dp height);
+ method public androidx.ui.core.Dp getHeight();
+ method public androidx.ui.core.Dp getWidth();
+ method public androidx.ui.core.Dp getXOffset();
}
public final class TextKt {
diff --git a/ui/ui-material/integration-tests/material-demos/build.gradle b/ui/ui-material/integration-tests/material-demos/build.gradle
index 01f612f..8c60318 100644
--- a/ui/ui-material/integration-tests/material-demos/build.gradle
+++ b/ui/ui-material/integration-tests/material-demos/build.gradle
@@ -16,10 +16,12 @@
kotlinPlugin project(path: ":compose:compose-compiler", configuration: "embeddablePlugin")
implementation(KOTLIN_COMPOSE_COROUTINES)
+ implementation(KOTLIN_COMPOSE_REFLECT)
implementation(KOTLIN_COMPOSE_STDLIB)
implementation "androidx.activity:activity:1.0.0-alpha01"
implementation "androidx.annotation:annotation:1.1.0"
+ implementation "androidx.preference:preference:1.1.0-rc01"
implementation project(":compose:compose-runtime")
implementation project(":ui:ui-core")
@@ -41,10 +43,10 @@
}
androidx {
- name = "Crane Material Composables"
- publish = Publish.SNAPSHOT_AND_RELEASE
+ name = "Compose Material Demos"
+ publish = Publish.NONE
mavenVersion = LibraryVersions.UI
mavenGroup = LibraryGroups.UI
inceptionYear = "2019"
- description = "This is a temporary project for Material composables."
+ description = "This is a project for Material demos."
}
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/AndroidManifest.xml b/ui/ui-material/integration-tests/material-demos/src/main/AndroidManifest.xml
index c0b0e7d..d7dac44 100644
--- a/ui/ui-material/integration-tests/material-demos/src/main/AndroidManifest.xml
+++ b/ui/ui-material/integration-tests/material-demos/src/main/AndroidManifest.xml
@@ -146,5 +146,10 @@
<category android:name="androidx.ui.demos.SAMPLE_CODE" />
</intent-filter>
</activity>
+
+ <activity android:name=".MaterialSettingsActivity"
+ android:label="Material Theme Settings"
+ android:theme="@style/Theme.AppCompat.Light.DarkActionBar">
+ </activity>
</application>
</manifest>
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/ic_launcher-web.png b/ui/ui-material/integration-tests/material-demos/src/main/ic_launcher-web.png
deleted file mode 100644
index 88e5f3b..0000000
--- a/ui/ui-material/integration-tests/material-demos/src/main/ic_launcher-web.png
+++ /dev/null
Binary files differ
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/AppBarActivity.kt b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/AppBarActivity.kt
index eb9d41b..486ff6f 100644
--- a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/AppBarActivity.kt
+++ b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/AppBarActivity.kt
@@ -16,62 +16,46 @@
package androidx.ui.material.demos
-import android.app.Activity
-import android.os.Bundle
import androidx.compose.Composable
import androidx.compose.composer
import androidx.compose.unaryPlus
import androidx.ui.core.Text
-import androidx.ui.core.dp
-import androidx.ui.core.setContent
import androidx.ui.layout.Column
-import androidx.ui.layout.FlexColumn
-import androidx.ui.layout.HeightSpacer
-import androidx.ui.material.MaterialTheme
+import androidx.ui.layout.MainAxisAlignment
import androidx.ui.material.samples.SimpleBottomAppBarCenterFab
import androidx.ui.material.samples.SimpleBottomAppBarEndFab
import androidx.ui.material.samples.SimpleBottomAppBarNoFab
import androidx.ui.material.samples.SimpleTopAppBar
+import androidx.ui.material.samples.SimpleTopAppBarNavIcon
import androidx.ui.material.themeTextStyle
import androidx.ui.painting.imageFromResource
-class AppBarActivity : Activity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContent {
- MaterialTheme {
- val favouriteImage = { imageFromResource(resources, R.drawable.ic_favorite) }
- val navigationImage = { imageFromResource(resources, R.drawable.ic_menu) }
- FlexColumn {
- expanded(1f) {
- Column {
- SpacedText("TopAppBar")
- HeightSpacer(height = 28.dp)
- SimpleTopAppBar(favouriteImage, navigationImage)
- }
- Column {
- SpacedText("BottomAppBar - No FAB")
- HeightSpacer(height = 28.dp)
- SimpleBottomAppBarNoFab(favouriteImage, navigationImage)
- }
- Column {
- SpacedText("BottomAppBar - Center FAB")
- SimpleBottomAppBarCenterFab(favouriteImage, navigationImage)
- }
- Column {
- SpacedText("BottomAppBar - End FAB")
- SimpleBottomAppBarEndFab(favouriteImage)
- }
- }
- }
- }
+class AppBarActivity : MaterialDemoActivity() {
+
+ @Composable
+ override fun materialContent() {
+ val favouriteImage = { imageFromResource(resources, R.drawable.ic_favorite) }
+ val navigationImage = { imageFromResource(resources, R.drawable.ic_menu) }
+ Column(mainAxisAlignment = MainAxisAlignment.SpaceBetween) {
+ DemoText("TopAppBar")
+ SimpleTopAppBar(favouriteImage)
+
+ DemoText("TopAppBar - With navigation icon")
+ SimpleTopAppBarNavIcon(favouriteImage, navigationImage)
+
+ DemoText("BottomAppBar - No FAB")
+ SimpleBottomAppBarNoFab(favouriteImage, navigationImage)
+
+ DemoText("BottomAppBar - Center FAB")
+ SimpleBottomAppBarCenterFab(favouriteImage, navigationImage)
+
+ DemoText("BottomAppBar - End FAB")
+ SimpleBottomAppBarEndFab(favouriteImage)
}
}
@Composable
- private fun SpacedText(text: String) {
- HeightSpacer(height = 12.dp)
+ private fun DemoText(text: String) {
Text(text, style = +themeTextStyle { h6 })
- HeightSpacer(height = 12.dp)
}
}
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/BottomDrawerActivity.kt b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/BottomDrawerActivity.kt
index 1c1147b..e69dade 100644
--- a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/BottomDrawerActivity.kt
+++ b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/BottomDrawerActivity.kt
@@ -16,21 +16,14 @@
package androidx.ui.material.demos
-import android.app.Activity
-import android.os.Bundle
+import androidx.compose.Composable
import androidx.compose.composer
-import androidx.ui.core.setContent
-import androidx.ui.material.MaterialTheme
import androidx.ui.material.samples.BottomDrawerSample
-class BottomDrawerActivity : Activity() {
+class BottomDrawerActivity : MaterialDemoActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContent {
- MaterialTheme {
- BottomDrawerSample()
- }
- }
+ @Composable
+ override fun materialContent() {
+ BottomDrawerSample()
}
}
\ No newline at end of file
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/ButtonActivity.kt b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/ButtonActivity.kt
index b6ea1bb..e6ad89c 100644
--- a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/ButtonActivity.kt
+++ b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/ButtonActivity.kt
@@ -32,15 +32,61 @@
package androidx.ui.material.demos
-import android.app.Activity
-import android.os.Bundle
+import android.util.Log
+import androidx.compose.Composable
import androidx.compose.composer
-import androidx.ui.core.setContent
+import androidx.compose.unaryPlus
+import androidx.ui.core.Text
+import androidx.ui.core.dp
+import androidx.ui.foundation.shape.border.Border
+import androidx.ui.graphics.Color
+import androidx.ui.layout.Center
+import androidx.ui.layout.Column
+import androidx.ui.layout.MainAxisAlignment
+import androidx.ui.layout.Padding
+import androidx.ui.material.Button
+import androidx.ui.material.TransparentButton
+import androidx.ui.material.themeColor
+import androidx.ui.material.themeTextStyle
-class ButtonActivity : Activity() {
+class ButtonActivity : MaterialDemoActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContent { ButtonDemo() }
+ @Composable
+ override fun materialContent() {
+ val onClick: () -> Unit = { Log.e("ButtonDemo", "onClick") }
+ Center {
+ Column(mainAxisAlignment = MainAxisAlignment.SpaceEvenly) {
+ Button(onClick = onClick, text = "LONG TEXT")
+ Button(onClick = onClick, text = "SH")
+ TransparentButton(onClick = onClick, text = "NO BACKGROUND")
+ Button(
+ onClick = onClick,
+ color = +themeColor { secondary },
+ text = "SECONDARY COLOR"
+ )
+
+ TransparentButton(
+ onClick = onClick,
+ border = Border(Color(0xFF888888.toInt()), 1.dp),
+ text = "OUTLINED"
+ )
+
+ val customColor = Color(0xFFFFFF00.toInt())
+ Button(
+ onClick = onClick,
+ text = "CUSTOM STYLE",
+ textStyle = +themeTextStyle { body2.copy(color = customColor) })
+ Button(onClick = onClick) {
+ Padding(padding = 16.dp) {
+ Text(text = "CUSTOM BUTTON!")
+ }
+ }
+
+ // TODO(Andrey): Disabled button has wrong bg and text color for now.
+ // Need to figure out where will we store their styling. Not a part of
+ // MaterialColors right now and specs are not clear about this.
+ Button(text = "DISABLED. TODO")
+ }
+ }
}
}
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/ButtonDemo.kt b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/ButtonDemo.kt
deleted file mode 100644
index 35279f4..0000000
--- a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/ButtonDemo.kt
+++ /dev/null
@@ -1,76 +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.material.demos
-
-import android.util.Log
-import androidx.compose.Composable
-import androidx.compose.composer
-import androidx.compose.unaryPlus
-import androidx.ui.core.Text
-import androidx.ui.core.dp
-import androidx.ui.graphics.Color
-import androidx.ui.foundation.shape.border.Border
-import androidx.ui.layout.Center
-import androidx.ui.layout.Column
-import androidx.ui.layout.MainAxisAlignment
-import androidx.ui.layout.Padding
-import androidx.ui.material.Button
-import androidx.ui.material.MaterialTheme
-import androidx.ui.material.TransparentButton
-import androidx.ui.material.themeColor
-import androidx.ui.material.themeTextStyle
-
-@Composable
-fun ButtonDemo() {
- MaterialTheme {
- val onClick: () -> Unit = { Log.e("ButtonDemo", "onClick") }
- Center {
- Column(mainAxisAlignment = MainAxisAlignment.SpaceEvenly) {
- Button(onClick = onClick, text = "LONG TEXT")
- Button(onClick = onClick, text = "SH")
- TransparentButton(onClick = onClick, text = "NO BACKGROUND")
- Button(
- onClick = onClick,
- color = +themeColor { secondary },
- text = "SECONDARY COLOR"
- )
-
- TransparentButton(
- onClick = onClick,
- border = Border(Color(0xFF888888.toInt()), 1.dp),
- text = "OUTLINED"
- )
-
- val customColor = Color(0xFFFFFF00.toInt())
- Button(
- onClick = onClick,
- text = "CUSTOM STYLE",
- textStyle = +themeTextStyle { body2.copy(color = customColor) })
- Button(onClick = onClick) {
- Padding(padding = 16.dp) {
- Text(text = "CUSTOM BUTTON!")
- }
- }
-
- // TODO(Andrey): Disabled button has wrong bg and text color for now.
- // Need to figure out where will we store their styling. Not a part of
- // MaterialColors right now and specs are not clear about this.
- Button(text = "DISABLED. TODO")
- }
- }
- }
-}
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/CustomShapeActivity.kt b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/CustomShapeActivity.kt
index 4b76cca..7d49679 100644
--- a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/CustomShapeActivity.kt
+++ b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/CustomShapeActivity.kt
@@ -16,13 +16,11 @@
package androidx.ui.material.demos
-import android.app.Activity
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import androidx.compose.Composable
import androidx.compose.composer
import androidx.ui.core.dp
-import androidx.ui.core.setContent
import androidx.ui.foundation.shape.border.Border
import androidx.ui.foundation.shape.GenericShape
import androidx.ui.graphics.Color
@@ -32,12 +30,16 @@
import androidx.ui.material.Button
import androidx.ui.material.MaterialTheme
-class CustomShapeActivity : Activity() {
+class CustomShapeActivity : MaterialDemoActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
window.setBackgroundDrawable(ColorDrawable(android.graphics.Color.WHITE))
- setContent { CustomShapeDemo() }
+ }
+
+ @Composable
+ override fun materialContent() {
+ CustomShapeDemo()
}
}
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/DividersSpacersActivity.kt b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/DividersSpacersActivity.kt
index 5289f89..fba5126 100644
--- a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/DividersSpacersActivity.kt
+++ b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/DividersSpacersActivity.kt
@@ -16,20 +16,81 @@
package androidx.ui.material.demos
-import android.app.Activity
-import android.os.Bundle
+import androidx.compose.Composable
import androidx.compose.composer
-import androidx.ui.core.setContent
-import androidx.ui.material.MaterialTheme
+import androidx.compose.unaryPlus
+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.Container
+import androidx.ui.layout.CrossAxisAlignment
+import androidx.ui.layout.EdgeInsets
+import androidx.ui.layout.HeightSpacer
+import androidx.ui.layout.Row
+import androidx.ui.layout.WidthSpacer
+import androidx.ui.material.Divider
+import androidx.ui.material.themeTextStyle
-class DividersSpacersActivity : Activity() {
+class DividersSpacersActivity : MaterialDemoActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContent {
- MaterialTheme {
- DividersDemo()
+ @Composable
+ override fun materialContent() {
+ DividersDemo()
+ }
+
+ @Composable
+ fun DividersDemo() {
+ val items = listOf(
+ "Lorem ipsum dolor sit amet.",
+ "Morbi ac purus eget quam dapibus cursus.",
+ "Integer viverra libero eget.",
+ "Mauris tristique arcu nec aliquam.",
+ "Vivamus euismod augue eget maximus."
+ )
+ val color = Color(0xFFE91E63.toInt())
+ val dividerColor = Color(0xFFC6C6C6.toInt())
+ val blackColor = Color.Black
+ Column {
+ Column {
+ items.forEachIndexed { index, text ->
+ Item(text = text, color = color)
+ if (index != items.lastIndex) {
+ Divider(color = dividerColor, indent = ItemSize)
+ }
+ }
+ }
+ HeightSpacer(height = 30.dp)
+ Divider(height = 2.dp, color = blackColor)
+ HeightSpacer(height = 10.dp)
+ Column {
+ items.forEach { text ->
+ Item(text = text)
+ Divider(color = dividerColor, height = 0.5.dp)
+ }
}
}
}
+
+ @Composable
+ fun Item(text: String, color: Color? = null) {
+ val avatarSize = ItemSize - ItemPadding * 2
+ val textStyle = +themeTextStyle { body1 }
+ Container(height = ItemSize, padding = EdgeInsets(ItemPadding)) {
+ Row(crossAxisAlignment = CrossAxisAlignment.Center) {
+ if (color != null) {
+ ColoredRect(
+ width = avatarSize,
+ height = avatarSize,
+ color = color)
+ WidthSpacer(width = ItemPadding)
+ }
+ Text(text = text, style = textStyle)
+ }
+ }
+ }
+
+ private val ItemSize = 55.dp
+ private val ItemPadding = 7.5.dp
}
\ No newline at end of file
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/DividersSpacersDemo.kt b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/DividersSpacersDemo.kt
deleted file mode 100644
index f1d4432..0000000
--- a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/DividersSpacersDemo.kt
+++ /dev/null
@@ -1,88 +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.material.demos
-
-import androidx.compose.Composable
-import androidx.compose.unaryPlus
-import androidx.compose.composer
-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.Container
-import androidx.ui.layout.CrossAxisAlignment
-import androidx.ui.layout.EdgeInsets
-import androidx.ui.layout.HeightSpacer
-import androidx.ui.layout.Row
-import androidx.ui.layout.WidthSpacer
-import androidx.ui.material.Divider
-import androidx.ui.material.themeTextStyle
-
-@Composable
-fun DividersDemo() {
- val items = listOf(
- "Lorem ipsum dolor sit amet.",
- "Morbi ac purus eget quam dapibus cursus.",
- "Integer viverra libero eget.",
- "Mauris tristique arcu nec aliquam.",
- "Vivamus euismod augue eget maximus."
- )
- val color = Color(0xFFE91E63.toInt())
- val dividerColor = Color(0xFFC6C6C6.toInt())
- val blackColor = Color(0xFF000000.toInt())
- Column {
- Column {
- items.forEachIndexed { index, text ->
- Item(text = text, color = color)
- if (index != items.lastIndex) {
- Divider(color = dividerColor, indent = ItemSize)
- }
- }
- }
- HeightSpacer(height = 30.dp)
- Divider(height = 2.dp, color = blackColor)
- HeightSpacer(height = 10.dp)
- Column {
- items.forEach { text ->
- Item(text = text)
- Divider(color = dividerColor, height = 0.5.dp)
- }
- }
- }
-}
-
-@Composable
-fun Item(text: String, color: Color? = null) {
- val avatarSize = ItemSize - ItemPadding * 2
- val textStyle = +themeTextStyle { body1 }
- Container(height = ItemSize, padding = EdgeInsets(ItemPadding)) {
- Row(crossAxisAlignment = CrossAxisAlignment.Center) {
- if (color != null) {
- ColoredRect(
- width = avatarSize,
- height = avatarSize,
- color = color)
- WidthSpacer(width = ItemPadding)
- }
- Text(text = text, style = textStyle)
- }
- }
-}
-
-private val ItemSize = 55.dp
-private val ItemPadding = 7.5.dp
\ No newline at end of file
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/FloatingActionButtonActivity.kt b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/FloatingActionButtonActivity.kt
index dd2eac9..6b632f9 100644
--- a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/FloatingActionButtonActivity.kt
+++ b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/FloatingActionButtonActivity.kt
@@ -16,17 +16,27 @@
package androidx.ui.material.demos
-import android.app.Activity
-import android.os.Bundle
+import android.util.Log
+import androidx.compose.Composable
import androidx.ui.painting.imageFromResource
import androidx.compose.composer
-import androidx.ui.core.setContent
+import androidx.ui.layout.Center
+import androidx.ui.layout.Column
+import androidx.ui.layout.MainAxisAlignment
+import androidx.ui.material.FloatingActionButton
-class FloatingActionButtonActivity : Activity() {
+class FloatingActionButtonActivity : MaterialDemoActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
+ @Composable
+ override fun materialContent() {
val icon = imageFromResource(resources, R.drawable.ic_favorite)
- setContent { FloatingActionButtonDemo(icon = icon) }
+ Center {
+ val onClick: () -> Unit = { Log.e("FABDemo", "onClick") }
+ Column(mainAxisAlignment = MainAxisAlignment.SpaceEvenly) {
+ FloatingActionButton(icon = icon, onClick = onClick)
+ FloatingActionButton(text = "EXTENDED", onClick = onClick)
+ FloatingActionButton(icon = icon, text = "ADD TO FAVS", onClick = onClick)
+ }
+ }
}
}
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/FloatingActionButtonDemo.kt b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/FloatingActionButtonDemo.kt
deleted file mode 100644
index 6405923..0000000
--- a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/FloatingActionButtonDemo.kt
+++ /dev/null
@@ -1,41 +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.material.demos
-
-import android.util.Log
-import androidx.ui.layout.Center
-import androidx.ui.layout.Column
-import androidx.ui.layout.MainAxisAlignment
-import androidx.ui.material.FloatingActionButton
-import androidx.ui.painting.Image
-import androidx.compose.Composable
-import androidx.compose.composer
-import androidx.ui.material.MaterialTheme
-
-@Composable
-fun FloatingActionButtonDemo(icon: Image) {
- MaterialTheme {
- Center {
- val onClick: () -> Unit = { Log.e("FABDemo", "onClick") }
- Column(mainAxisAlignment = MainAxisAlignment.SpaceEvenly) {
- FloatingActionButton(icon = icon, onClick = onClick)
- FloatingActionButton(text = "EXTENDED", onClick = onClick)
- FloatingActionButton(icon = icon, text = "ADD TO FAVS", onClick = onClick)
- }
- }
- }
-}
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/MaterialDemoActivity.kt b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/MaterialDemoActivity.kt
new file mode 100644
index 0000000..9d0da33
--- /dev/null
+++ b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/MaterialDemoActivity.kt
@@ -0,0 +1,479 @@
+/*
+ * 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.demos
+
+import android.app.Activity
+import android.content.Intent
+import android.content.SharedPreferences
+import android.os.Bundle
+import android.view.Menu
+import android.view.MenuItem
+import androidx.appcompat.app.AppCompatActivity
+import androidx.compose.Composable
+import androidx.compose.FrameManager
+import androidx.compose.Model
+import androidx.compose.composer
+import androidx.preference.EditTextPreference
+import androidx.preference.PreferenceFragmentCompat
+import androidx.preference.PreferenceManager.getDefaultSharedPreferences
+import androidx.ui.core.setContent
+import androidx.ui.graphics.Color
+import androidx.ui.material.MaterialColors
+import androidx.ui.material.MaterialTheme
+import kotlin.random.Random
+import kotlin.reflect.full.memberProperties
+import kotlin.reflect.full.primaryConstructor
+
+@Model
+class CurrentMaterialColors {
+ var colors = MaterialColors()
+}
+
+/**
+ * Base [Activity] for material demos. Handles generating and editing the top level [MaterialTheme].
+ * Subclasses should override [materialContent] to emit their specific demos.
+ */
+abstract class MaterialDemoActivity : Activity() {
+
+ private val currentColors = CurrentMaterialColors()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ // Ensure we are in a frame, as this is only normally initialized after the setContent call
+ FrameManager.ensureStarted()
+ currentColors.colors = getColorsFromSharedPreferences()
+ setContent {
+ MaterialTheme(currentColors.colors) {
+ materialContent()
+ }
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+ // Update colors in case we changed something in settings activity
+ currentColors.colors = getColorsFromSharedPreferences()
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu?): Boolean {
+ menu?.add(Menu.NONE, SETTINGS, Menu.NONE, "Theme settings")
+ menu?.add(Menu.NONE, SHUFFLE, Menu.NONE, "Shuffle colors")
+ menu?.add(Menu.NONE, INVERT, Menu.NONE, "Invert color mapping")
+ menu?.add(Menu.NONE, RESET, Menu.NONE, "Reset theme to default")
+ return true
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ when (item.itemId) {
+ SETTINGS -> startActivity(Intent(this, MaterialSettingsActivity::class.java))
+ SHUFFLE -> {
+ val colors = generateMaterialColors()
+ colors.saveColors()
+ currentColors.colors = colors
+ }
+ INVERT -> {
+ // Flip all colors
+ val newPrimary = currentColors.colors.onPrimary
+ val newOnPrimary = currentColors.colors.primary
+ val newSecondary = currentColors.colors.onSecondary
+ val newOnSecondary = currentColors.colors.secondary
+ val colors = currentColors.colors.copy(
+ primary = newPrimary,
+ onPrimary = newOnPrimary,
+ secondary = newSecondary,
+ onSecondary = newOnSecondary
+ )
+ colors.saveColors()
+ currentColors.colors = colors
+ }
+ RESET -> {
+ val sharedPreferences = getDefaultSharedPreferences(this)
+ sharedPreferences.edit().clear().apply()
+ currentColors.colors = getColorsFromSharedPreferences()
+ }
+ }
+ return true
+ }
+
+ /**
+ * Returns [MaterialColors] from the values saved to [SharedPreferences]. If a given color is
+ * not present in the [SharedPreferences], its default value as defined in [MaterialColors]
+ * will be returned.
+ */
+ private fun getColorsFromSharedPreferences(): MaterialColors {
+ val sharedPreferences = getDefaultSharedPreferences(this)
+ val constructor = MaterialColors::class.primaryConstructor!!
+ val parametersToSet = constructor.parameters.mapNotNull { parameter ->
+ val savedValue = sharedPreferences.getString(parameter.name, "")
+ if (savedValue.isNullOrBlank()) {
+ null
+ } else {
+ val parsedColor = Color(java.lang.Long.parseLong(savedValue, 16).toInt())
+ parameter to parsedColor
+ }
+ }.toMap()
+ return MaterialColors::class.primaryConstructor!!.callBy(parametersToSet)
+ }
+
+ /**
+ * Persists the current [MaterialColors] to [SharedPreferences].
+ */
+ private fun MaterialColors.saveColors() {
+ forEachColorProperty { name, color ->
+ getDefaultSharedPreferences(this@MaterialDemoActivity)
+ .edit()
+ .putString(name, Integer.toHexString(color.toArgb()))
+ .apply()
+ }
+ }
+
+ /**
+ * Generates random colors for [MaterialColors.primary], [MaterialColors.onPrimary],
+ * [MaterialColors.secondary] and [MaterialColors.onSecondary] as dark-on-light or light-on-dark
+ * pairs.
+ */
+ private fun generateMaterialColors(): MaterialColors {
+ val (primary, onPrimary) = generateColorPair()
+ val (secondary, onSecondary) = generateColorPair()
+ return MaterialColors(
+ primary = primary,
+ onPrimary = onPrimary,
+ secondary = secondary,
+ onSecondary = onSecondary
+ )
+ }
+
+ /**
+ * Generate a random dark and light color from the palette, and returns either a dark-on-light
+ * or light-on-dark color pair.
+ */
+ private fun generateColorPair(): Pair<Color, Color> {
+ val darkColor = Color(DARK_PALETTE_COLORS.random().toInt())
+ val lightColor = Color(LIGHT_PALETTE_COLORS.random().toInt())
+ val isMainColorLight = Random.nextBoolean()
+ return if (isMainColorLight) {
+ (lightColor to darkColor)
+ } else {
+ (darkColor to lightColor)
+ }
+ }
+
+ /**
+ * Override this function to return the composable hierarchy that should be displayed inside the
+ * customized [MaterialTheme].
+ */
+ @Composable
+ abstract fun materialContent()
+
+ companion object {
+ private const val SETTINGS = 1
+ private const val SHUFFLE = 2
+ private const val INVERT = 3
+ private const val RESET = 4
+
+ // Colors taken from https://material.io/design/color -> 2014 Material Design color palettes
+
+ private val LIGHT_PALETTE_COLORS = listOf(
+ 0xFFEF5350,
+ 0xFFF44336,
+ 0xFFE53935,
+ 0xFFD32F2F,
+ 0xFFC62828,
+ 0xFFB71C1C,
+ 0xFFFF5252,
+ 0xFFFF1744,
+ 0xFFD50000,
+ 0xFFEC407A,
+ 0xFFE91E63,
+ 0xFFD81B60,
+ 0xFFC2185B,
+ 0xFFAD1457,
+ 0xFF880E4F,
+ 0xFFFF4081,
+ 0xFFF50057,
+ 0xFFC51162,
+ 0xFFBA68C8,
+ 0xFFAB47BC,
+ 0xFF9C27B0,
+ 0xFF8E24AA,
+ 0xFF7B1FA2,
+ 0xFF6A1B9A,
+ 0xFF4A148C,
+ 0xFFE040FB,
+ 0xFFD500F9,
+ 0xFFAA00FF,
+ 0xFF9575CD,
+ 0xFF7E57C2,
+ 0xFF673AB7,
+ 0xFF5E35B1,
+ 0xFF512DA8,
+ 0xFF4527A0,
+ 0xFF311B92,
+ 0xFF7C4DFF,
+ 0xFF651FFF,
+ 0xFF6200EA,
+ 0xFF7986CB,
+ 0xFF5C6BC0,
+ 0xFF3F51B5,
+ 0xFF3949AB,
+ 0xFF303F9F,
+ 0xFF283593,
+ 0xFF1A237E,
+ 0xFF536DFE,
+ 0xFF3D5AFE,
+ 0xFF304FFE,
+ 0xFF1E88E5,
+ 0xFF1976D2,
+ 0xFF1565C0,
+ 0xFF0D47A1,
+ 0xFF448AFF,
+ 0xFF2979FF,
+ 0xFF2962FF,
+ 0xFF0288D1,
+ 0xFF0277BD,
+ 0xFF01579B,
+ 0xFF0091EA,
+ 0xFF0097A7,
+ 0xFF00838F,
+ 0xFF006064,
+ 0xFF009688,
+ 0xFF00897B,
+ 0xFF00796B,
+ 0xFF00695C,
+ 0xFF004D40,
+ 0xFF43A047,
+ 0xFF388E3C,
+ 0xFF2E7D32,
+ 0xFF1B5E20,
+ 0xFF558B2F,
+ 0xFF33691E,
+ 0xFF827717,
+ 0xFFE65100,
+ 0xFFF4511E,
+ 0xFFE64A19,
+ 0xFFD84315,
+ 0xFFBF360C,
+ 0xFFFF3D00,
+ 0xFFDD2C00,
+ 0xFFA1887F,
+ 0xFF8D6E63,
+ 0xFF795548,
+ 0xFF6D4C41,
+ 0xFF5D4037,
+ 0xFF4E342E,
+ 0xFF3E2723,
+ 0xFF757575,
+ 0xFF616161,
+ 0xFF424242,
+ 0xFF212121,
+ 0xFF78909C,
+ 0xFF607D8B,
+ 0xFF546E7A,
+ 0xFF455A64,
+ 0xFF37474F,
+ 0xFF263238
+ )
+
+ private val DARK_PALETTE_COLORS = listOf(
+ 0xFFFFCDD2,
+ 0xFFEF9A9A,
+ 0xFFE57373,
+ 0xFFFF8A80,
+ 0xFFF8BBD0,
+ 0xFFF48FB1,
+ 0xFFF06292,
+ 0xFFFF80AB,
+ 0xFFE1BEE7,
+ 0xFFCE93D8,
+ 0xFFEA80FC,
+ 0xFFD1C4E9,
+ 0xFFB39DDB,
+ 0xFFB388FF,
+ 0xFFC5CAE9,
+ 0xFF9FA8DA,
+ 0xFF8C9EFF,
+ 0xFFBBDEFB,
+ 0xFF90CAF9,
+ 0xFF64B5F6,
+ 0xFF42A5F5,
+ 0xFF2196F3,
+ 0xFF82B1FF,
+ 0xFFB3E5FC,
+ 0xFF81D4FA,
+ 0xFF4FC3F7,
+ 0xFF29B6F6,
+ 0xFF03A9F4,
+ 0xFF039BE5,
+ 0xFF80D8FF,
+ 0xFF40C4FF,
+ 0xFF00B0FF,
+ 0xFFB2EBF2,
+ 0xFF80DEEA,
+ 0xFF4DD0E1,
+ 0xFF26C6DA,
+ 0xFF00BCD4,
+ 0xFF00ACC1,
+ 0xFF84FFFF,
+ 0xFF18FFFF,
+ 0xFF00E5FF,
+ 0xFF00B8D4,
+ 0xFFB2DFDB,
+ 0xFF80CBC4,
+ 0xFF4DB6AC,
+ 0xFF26A69A,
+ 0xFFA7FFEB,
+ 0xFF64FFDA,
+ 0xFF1DE9B6,
+ 0xFF00BFA5,
+ 0xFFC8E6C9,
+ 0xFFA5D6A7,
+ 0xFF81C784,
+ 0xFF66BB6A,
+ 0xFF4CAF50,
+ 0xFFB9F6CA,
+ 0xFF69F0AE,
+ 0xFF00E676,
+ 0xFF00C853,
+ 0xFFDCEDC8,
+ 0xFFC5E1A5,
+ 0xFFAED581,
+ 0xFF9CCC65,
+ 0xFF8BC34A,
+ 0xFF7CB342,
+ 0xFF689F38,
+ 0xFFCCFF90,
+ 0xFFB2FF59,
+ 0xFF76FF03,
+ 0xFF64DD17,
+ 0xFFF0F4C3,
+ 0xFFE6EE9C,
+ 0xFFDCE775,
+ 0xFFD4E157,
+ 0xFFCDDC39,
+ 0xFFC0CA33,
+ 0xFFAFB42B,
+ 0xFF9E9D24,
+ 0xFFF4FF81,
+ 0xFFEEFF41,
+ 0xFFC6FF00,
+ 0xFFAEEA00,
+ 0xFFFFF9C4,
+ 0xFFFFF59D,
+ 0xFFFFF176,
+ 0xFFFFEE58,
+ 0xFFFFEB3B,
+ 0xFFFDD835,
+ 0xFFFBC02D,
+ 0xFFF9A825,
+ 0xFFF57F17,
+ 0xFFFFFF8D,
+ 0xFFFFFF00,
+ 0xFFFFEA00,
+ 0xFFFFD600,
+ 0xFFFFECB3,
+ 0xFFFFE082,
+ 0xFFFFD54F,
+ 0xFFFFCA28,
+ 0xFFFFC107,
+ 0xFFFFB300,
+ 0xFFFFA000,
+ 0xFFFF8F00,
+ 0xFFFF6F00,
+ 0xFFFFE57F,
+ 0xFFFFD740,
+ 0xFFFFC400,
+ 0xFFFFAB00,
+ 0xFFFFE0B2,
+ 0xFFFFCC80,
+ 0xFFFFB74D,
+ 0xFFFFA726,
+ 0xFFFF9800,
+ 0xFFFB8C00,
+ 0xFFF57C00,
+ 0xFFEF6C00,
+ 0xFFFFD180,
+ 0xFFFFAB40,
+ 0xFFFF9100,
+ 0xFFFF6D00,
+ 0xFFFFCCBC,
+ 0xFFFFAB91,
+ 0xFFFF8A65,
+ 0xFFFF7043,
+ 0xFFFF5722,
+ 0xFFFF9E80,
+ 0xFFFF6E40,
+ 0xFFD7CCC8,
+ 0xFFBCAAA4,
+ 0xFFF5F5F5,
+ 0xFFEEEEEE,
+ 0xFFE0E0E0,
+ 0xFFBDBDBD,
+ 0xFF9E9E9E,
+ 0xFFCFD8DC,
+ 0xFFB0BEC5,
+ 0xFF90A4AE,
+ 0xFFFFFFFF
+ )
+ }
+}
+
+/**
+ * Shell [AppCompatActivity] around [SettingsFragment], as we need a FragmentActivity subclass
+ * to host the [SettingsFragment].
+ */
+class MaterialSettingsActivity : AppCompatActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ supportFragmentManager
+ .beginTransaction()
+ .replace(android.R.id.content, SettingsFragment())
+ .commit()
+ }
+
+ class SettingsFragment : PreferenceFragmentCompat() {
+ override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
+ val context = preferenceManager.context
+ val screen = preferenceManager.createPreferenceScreen(context)
+ // Create new MaterialColors to resolve defaults
+ MaterialColors().forEachColorProperty { name, color ->
+ val preference = EditTextPreference(context)
+ preference.key = name
+ preference.title = name
+ // set the default value to be the default for MaterialColors
+ preference.setDefaultValue(Integer.toHexString(color.toArgb()))
+ preference.summaryProvider = EditTextPreference.SimpleSummaryProvider.getInstance()
+ screen.addPreference(preference)
+ }
+ preferenceScreen = screen
+ }
+ }
+}
+
+/**
+ * Iterates over each color present in a given [MaterialColors].
+ *
+ * @param action the action to take on each property, where [name] is the name of the property,
+ * such as 'primary' for [MaterialColors.primary], and [color] is the resolved [Color] of the
+ * property.
+ */
+private fun MaterialColors.forEachColorProperty(action: (name: String, color: Color) -> Unit) {
+ MaterialColors::class.memberProperties.forEach { property ->
+ val name = property.name
+ val color = property.get(this) as Color
+ action(name, color)
+ }
+}
\ No newline at end of file
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/ModalDrawerActivity.kt b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/ModalDrawerActivity.kt
index 1938d75..8df119a 100644
--- a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/ModalDrawerActivity.kt
+++ b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/ModalDrawerActivity.kt
@@ -16,21 +16,14 @@
package androidx.ui.material.demos
-import android.app.Activity
-import android.os.Bundle
+import androidx.compose.Composable
import androidx.compose.composer
-import androidx.ui.core.setContent
-import androidx.ui.material.MaterialTheme
import androidx.ui.material.samples.ModalDrawerSample
-class ModalDrawerActivity : Activity() {
+class ModalDrawerActivity : MaterialDemoActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContent {
- MaterialTheme {
- ModalDrawerSample()
- }
- }
+ @Composable
+ override fun materialContent() {
+ ModalDrawerSample()
}
}
\ No newline at end of file
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/ProgressIndicatorActivity.kt b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/ProgressIndicatorActivity.kt
index 4ed72d1..d3cdaaa 100644
--- a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/ProgressIndicatorActivity.kt
+++ b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/ProgressIndicatorActivity.kt
@@ -16,8 +16,6 @@
package androidx.ui.material.demos
-import android.app.Activity
-import android.os.Bundle
import android.os.Handler
import androidx.ui.layout.FlexColumn
import androidx.ui.layout.MainAxisAlignment.SpaceEvenly
@@ -31,17 +29,12 @@
import androidx.compose.onActive
import androidx.compose.onDispose
import androidx.compose.unaryPlus
-import androidx.ui.core.setContent
-import androidx.ui.material.MaterialTheme
-class ProgressIndicatorActivity : Activity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContent {
- MaterialTheme {
- ProgressIndicatorDemo()
- }
- }
+class ProgressIndicatorActivity : MaterialDemoActivity() {
+
+ @Composable
+ override fun materialContent() {
+ ProgressIndicatorDemo()
}
}
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 514813f..a0eaee6 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
@@ -16,19 +16,165 @@
package androidx.ui.material.demos
-import android.app.Activity
-import android.os.Bundle
+import androidx.compose.Composable
import androidx.compose.composer
-import androidx.ui.core.setContent
-import androidx.ui.material.MaterialTheme
+import androidx.compose.memo
+import androidx.compose.state
+import androidx.compose.unaryPlus
+import androidx.ui.core.Text
+import androidx.ui.core.dp
+import androidx.ui.foundation.selection.ToggleableState
+import androidx.ui.graphics.Color
+import androidx.ui.layout.Column
+import androidx.ui.layout.CrossAxisAlignment
+import androidx.ui.layout.EdgeInsets
+import androidx.ui.layout.FlexSize
+import androidx.ui.layout.MainAxisAlignment
+import androidx.ui.layout.Padding
+import androidx.ui.layout.Row
+import androidx.ui.material.Checkbox
+import androidx.ui.material.RadioButton
+import androidx.ui.material.RadioGroup
+import androidx.ui.material.Switch
+import androidx.ui.material.TriStateCheckbox
+import androidx.ui.material.surface.Surface
+import androidx.ui.material.themeTextStyle
-open class SelectionControlsActivity : Activity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContent {
- MaterialTheme {
- SelectionsControlsDemo()
+class SelectionControlsActivity : MaterialDemoActivity() {
+ private val customColor = Color(0xFFFF5722.toInt())
+ private val customColor2 = Color(0xFFE91E63.toInt())
+ private val customColor3 = Color(0xFF607D8B.toInt())
+
+ @Composable
+ override fun materialContent() {
+ val headerStyle = +themeTextStyle { h6 }
+ val padding = EdgeInsets(10.dp)
+
+ Surface(color = Color.White) {
+ Padding(padding = padding) {
+ Column(crossAxisAlignment = CrossAxisAlignment.Start) {
+ Text(text = "Checkbox", style = headerStyle)
+ Padding(padding = padding) {
+ CheckboxDemo()
+ }
+ Text(text = "Switch", style = headerStyle)
+ Padding(padding = padding) {
+ SwitchDemo()
+ }
+ Text(text = "RadioButton", style = headerStyle)
+ Padding(padding = padding) {
+ RadioButtonDemo()
+ }
+ Text(text = "Radio group :: Default usage", style = headerStyle)
+ Padding(padding = padding) {
+ DefaultRadioGroup()
+ }
+ Text(text = "Radio group :: Custom usage", style = headerStyle)
+ Padding(padding = padding) {
+ CustomRadioGroup()
+ }
+ }
}
}
}
+
+ @Composable
+ fun DefaultRadioGroup() {
+ val radioOptions = listOf("Calls", "Missed", "Friends")
+ val (selectedOption, onOptionSelected) = +state { radioOptions[0] }
+ RadioGroup(
+ options = radioOptions,
+ selectedOption = selectedOption,
+ onSelectedChange = onOptionSelected,
+ radioColor = customColor2
+ )
+ }
+
+ @Composable
+ fun CustomRadioGroup() {
+ val radioOptions = listOf("Disagree", "Neutral", "Agree")
+ val (selectedOption, onOptionSelected) = +state { radioOptions[0] }
+ val textStyle = +themeTextStyle { subtitle1 }
+
+ RadioGroup {
+ Row(mainAxisSize = FlexSize.Min) {
+ radioOptions.forEach { text ->
+ val selected = text == selectedOption
+ RadioGroupItem(
+ selected = selected,
+ onSelect = { onOptionSelected(text) }) {
+ Padding(padding = 10.dp) {
+ Column {
+ RadioButton(
+ selected = selected,
+ onSelect = { onOptionSelected(text) })
+ Text(text = text, style = textStyle)
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ @Composable
+ fun CheckboxDemo() {
+ Column(crossAxisAlignment = CrossAxisAlignment.Start) {
+ val (state, onStateChange) = +state { true }
+ val (state2, onStateChange2) = +state { true }
+ val (state3, onStateChange3) = +state { true }
+ val parentState = +memo(state, state2, state3) {
+ if (state && state2 && state3) ToggleableState.Checked
+ else if (!state && !state2 && !state3) ToggleableState.Unchecked
+ else ToggleableState.Indeterminate
+ }
+ val onParentClick = {
+ val s = parentState != ToggleableState.Checked
+ onStateChange(s)
+ onStateChange2(s)
+ onStateChange3(s)
+ }
+ Row {
+ TriStateCheckbox(value = parentState, onClick = onParentClick)
+ Text(text = "This is parent TriStateCheckbox", style = +themeTextStyle { body1 })
+ }
+ Padding(left = 10.dp) {
+ Column(crossAxisAlignment = CrossAxisAlignment.Start) {
+ Checkbox(state, onStateChange, customColor)
+ Checkbox(state2, onStateChange2, customColor2)
+ Checkbox(state3, onStateChange3, customColor3)
+ }
+ }
+ }
+ }
+
+ @Composable
+ fun SwitchDemo() {
+ Row(
+ mainAxisAlignment = MainAxisAlignment.SpaceAround,
+ mainAxisSize = FlexSize.Min
+ ) {
+ val (checked, onChecked) = +state { false }
+ val (checked2, onChecked2) = +state { false }
+ val (checked3, onChecked3) = +state { true }
+ val (checked4, onChecked4) = +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)
+ }
+ }
+
+ @Composable
+ fun RadioButtonDemo() {
+ Row(
+ mainAxisAlignment = MainAxisAlignment.SpaceAround,
+ mainAxisSize = FlexSize.Min
+ ) {
+ RadioButton(selected = true, onSelect = null)
+ RadioButton(selected = false, onSelect = null)
+ RadioButton(selected = true, color = customColor, onSelect = null)
+ RadioButton(selected = false, color = customColor, onSelect = null)
+ }
+ }
}
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/SelectionsControlsDemo.kt b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/SelectionsControlsDemo.kt
deleted file mode 100644
index 38ceab73..0000000
--- a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/SelectionsControlsDemo.kt
+++ /dev/null
@@ -1,181 +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.material.demos
-
-import androidx.compose.Composable
-import androidx.compose.composer
-import androidx.compose.memo
-import androidx.compose.state
-import androidx.compose.unaryPlus
-import androidx.ui.core.Text
-import androidx.ui.core.dp
-import androidx.ui.foundation.selection.ToggleableState
-import androidx.ui.foundation.selection.ToggleableState.Checked
-import androidx.ui.foundation.selection.ToggleableState.Unchecked
-import androidx.ui.graphics.Color
-import androidx.ui.layout.Column
-import androidx.ui.layout.CrossAxisAlignment
-import androidx.ui.layout.EdgeInsets
-import androidx.ui.layout.FlexSize
-import androidx.ui.layout.MainAxisAlignment
-import androidx.ui.layout.Padding
-import androidx.ui.layout.Row
-import androidx.ui.material.Checkbox
-import androidx.ui.material.RadioButton
-import androidx.ui.material.RadioGroup
-import androidx.ui.material.Switch
-import androidx.ui.material.surface.Surface
-import androidx.ui.material.themeTextStyle
-import androidx.ui.material.TriStateCheckbox
-
-private val customColor = Color(0xFFFF5722.toInt())
-private val customColor2 = Color(0xFFE91E63.toInt())
-private val customColor3 = Color(0xFF607D8B.toInt())
-
-@Composable
-fun SelectionsControlsDemo() {
-
- val headerStyle = +themeTextStyle { h6 }
- val padding = EdgeInsets(10.dp)
-
- Surface {
- Padding(padding = padding) {
- Column(crossAxisAlignment = CrossAxisAlignment.Start) {
- Text(text = "Checkbox", style = headerStyle)
- Padding(padding = padding) {
- CheckboxDemo()
- }
- Text(text = "Switch", style = headerStyle)
- Padding(padding = padding) {
- SwitchDemo()
- }
- Text(text = "RadioButton", style = headerStyle)
- Padding(padding = padding) {
- RadioButtonDemo()
- }
- Text(text = "Radio group :: Default usage", style = headerStyle)
- Padding(padding = padding) {
- DefaultRadioGroup()
- }
- Text(text = "Radio group :: Custom usage", style = headerStyle)
- Padding(padding = padding) {
- CustomRadioGroup()
- }
- }
- }
- }
-}
-
-@Composable
-fun DefaultRadioGroup() {
- val radioOptions = listOf("Calls", "Missed", "Friends")
- val (selectedOption, onOptionSelected) = +state { radioOptions[0] }
- RadioGroup(
- options = radioOptions,
- selectedOption = selectedOption,
- onSelectedChange = onOptionSelected,
- radioColor = customColor2
- )
-}
-
-@Composable
-fun CustomRadioGroup() {
- val radioOptions = listOf("Disagree", "Neutral", "Agree")
- val (selectedOption, onOptionSelected) = +state { radioOptions[0] }
- val textStyle = +themeTextStyle { subtitle1 }
-
- RadioGroup {
- Row(mainAxisSize = FlexSize.Min) {
- radioOptions.forEach { text ->
- val selected = text == selectedOption
- RadioGroupItem(
- selected = selected,
- onSelect = { onOptionSelected(text) }) {
- Padding(padding = 10.dp) {
- Column {
- RadioButton(
- selected = selected,
- onSelect = { onOptionSelected(text) })
- Text(text = text, style = textStyle)
- }
- }
- }
- }
- }
- }
-}
-
-@Composable
-fun CheckboxDemo() {
- Column(crossAxisAlignment = CrossAxisAlignment.Start) {
- val (state, onStateChange) = +state { true }
- val (state2, onStateChange2) = +state { true }
- val (state3, onStateChange3) = +state { true }
- val parentState = +memo(state, state2, state3) {
- if (state && state2 && state3) ToggleableState.Checked
- else if (!state && !state2 && !state3) ToggleableState.Unchecked
- else ToggleableState.Indeterminate
- }
- val onParentClick = {
- val s = parentState != Checked
- onStateChange(s)
- onStateChange2(s)
- onStateChange3(s)
- }
- Row {
- TriStateCheckbox(value = parentState, onClick = onParentClick)
- Text(text = "This is parent TriStateCheckbox", style = +themeTextStyle { body1 })
- }
- Padding(left = 10.dp) {
- Column(crossAxisAlignment = CrossAxisAlignment.Start) {
- Checkbox(state, onStateChange, customColor)
- Checkbox(state2, onStateChange2, customColor2)
- Checkbox(state3, onStateChange3, customColor3)
- }
- }
- }
-}
-
-@Composable
-fun SwitchDemo() {
- Row(
- mainAxisAlignment = MainAxisAlignment.SpaceAround,
- mainAxisSize = FlexSize.Min
- ) {
- val (checked, onChecked) = +state { false }
- val (checked2, onChecked2) = +state { false }
- val (checked3, onChecked3) = +state { true }
- val (checked4, onChecked4) = +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)
- }
-}
-
-@Composable
-fun RadioButtonDemo() {
- Row(
- mainAxisAlignment = MainAxisAlignment.SpaceAround,
- mainAxisSize = FlexSize.Min
- ) {
- RadioButton(selected = true, onSelect = null)
- RadioButton(selected = false, onSelect = null)
- RadioButton(selected = true, color = customColor, onSelect = null)
- RadioButton(selected = false, color = customColor, onSelect = null)
- }
-}
\ No newline at end of file
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/StaticDrawerActivity.kt b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/StaticDrawerActivity.kt
index 8ccc222..e8c827c 100644
--- a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/StaticDrawerActivity.kt
+++ b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/StaticDrawerActivity.kt
@@ -16,21 +16,14 @@
package androidx.ui.material.demos
-import android.app.Activity
-import android.os.Bundle
+import androidx.compose.Composable
import androidx.compose.composer
-import androidx.ui.core.setContent
-import androidx.ui.material.MaterialTheme
import androidx.ui.material.samples.StaticDrawerSample
-class StaticDrawerActivity : Activity() {
+class StaticDrawerActivity : MaterialDemoActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContent {
- MaterialTheme {
- StaticDrawerSample()
- }
- }
+ @Composable
+ override fun materialContent() {
+ StaticDrawerSample()
}
}
\ No newline at end of file
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/TabActivity.kt b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/TabActivity.kt
index 85d1e64..0a22493 100644
--- a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/TabActivity.kt
+++ b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/TabActivity.kt
@@ -16,37 +16,48 @@
package androidx.ui.material.demos
-import android.app.Activity
-import android.os.Bundle
+import androidx.compose.Composable
import androidx.compose.composer
-import androidx.ui.core.setContent
+import androidx.compose.state
+import androidx.compose.unaryPlus
+import androidx.ui.graphics.Color
+import androidx.ui.layout.Center
import androidx.ui.layout.FlexColumn
-import androidx.ui.material.MaterialTheme
-import androidx.ui.material.samples.CustomTabs
+import androidx.ui.material.Button
+import androidx.ui.material.samples.FancyIndicatorContainerTabs
+import androidx.ui.material.samples.FancyIndicatorTabs
+import androidx.ui.material.samples.FancyTabs
import androidx.ui.material.samples.IconTabs
import androidx.ui.material.samples.TextAndIconTabs
import androidx.ui.material.samples.TextTabs
import androidx.ui.painting.imageFromResource
-class TabActivity : Activity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContent {
- MaterialTheme {
- val favouriteImage = imageFromResource(resources, R.drawable.ic_favorite)
- FlexColumn {
- expanded(flex = 1f) {
- TextTabs()
- }
- expanded(flex = 1f) {
- IconTabs(favouriteImage)
- }
- expanded(flex = 1f) {
- TextAndIconTabs(favouriteImage)
- }
- expanded(flex = 1f) {
- CustomTabs()
- }
+class TabActivity : MaterialDemoActivity() {
+
+ @Composable
+ override fun materialContent() {
+ val favouriteImage = imageFromResource(resources, R.drawable.ic_favorite)
+ FlexColumn {
+ val showingSimple = +state { true }
+ val buttonText = "Show ${if (showingSimple.value) "custom" else "simple"} tabs"
+
+ expanded(flex = 1f) {
+ if (showingSimple.value) {
+ TextTabs()
+ IconTabs(favouriteImage)
+ TextAndIconTabs(favouriteImage)
+ } else {
+ FancyTabs()
+ FancyIndicatorTabs()
+ FancyIndicatorContainerTabs()
+ }
+ }
+
+ expanded(flex = 1f) {
+ Center {
+ Button(color = Color.Cyan, text = buttonText, onClick = {
+ showingSimple.value = !showingSimple.value
+ })
}
}
}
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/TextActivity.kt b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/TextActivity.kt
index 35a660e..afde748 100644
--- a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/TextActivity.kt
+++ b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/TextActivity.kt
@@ -16,26 +16,20 @@
package androidx.ui.material.demos
-import android.app.Activity
-import android.os.Bundle
+import androidx.compose.Composable
import androidx.ui.core.Text
import androidx.ui.material.themeTextStyle
import androidx.ui.graphics.Color
import androidx.compose.composer
import androidx.compose.unaryPlus
-import androidx.ui.core.setContent
-import androidx.ui.material.MaterialTheme
-open class TextActivity : Activity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContent {
- MaterialTheme {
- val textColor = Color(0xFFFF0000.toInt())
- Text(
- text = "Hello",
- style = +themeTextStyle { h1.copy(color = textColor) })
- }
- }
+open class TextActivity : MaterialDemoActivity() {
+
+ @Composable
+ override fun materialContent() {
+ val textColor = Color(0xFFFF0000.toInt())
+ Text(
+ text = "Hello",
+ style = +themeTextStyle { h1.copy(color = textColor) })
}
}
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 37f2905..e80124f 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
@@ -19,7 +19,6 @@
import androidx.compose.Children
import androidx.compose.Composable
import androidx.compose.composer
-import androidx.ui.core.CurrentTextStyleProvider
import androidx.ui.core.sp
import androidx.ui.text.font.FontWeight
import androidx.ui.text.font.FontFamily
@@ -41,7 +40,7 @@
val colors = MaterialColors(
primary = rallyGreen,
surface = Color(0xFF26282F.toInt()),
- onSurface = Color(0xFFFFFFFF.toInt())
+ onSurface = Color.White
)
val typography = MaterialTypography(
h1 = TextStyle(fontFamily = FontFamily("RobotoCondensed"),
@@ -86,10 +85,6 @@
)
MaterialTheme(colors = colors, typography = typography) {
- // TODO: remove this when surface auto-sets the text color
- val value = TextStyle(color = Color(0xFFFFFFFF.toInt()))
- CurrentTextStyleProvider(value = value) {
- children()
- }
+ children()
}
}
\ No newline at end of file
diff --git a/ui/ui-material/integration-tests/samples/build.gradle b/ui/ui-material/integration-tests/samples/build.gradle
index 8dec260..81fe717 100644
--- a/ui/ui-material/integration-tests/samples/build.gradle
+++ b/ui/ui-material/integration-tests/samples/build.gradle
@@ -33,6 +33,7 @@
implementation project(":annotation:annotation-sampled")
implementation project(":compose:compose-runtime")
+ implementation project(":ui:ui-animation")
implementation project(":ui:ui-core")
implementation project(":ui:ui-foundation")
implementation project(":ui:ui-framework")
diff --git a/ui/ui-material/integration-tests/samples/src/main/java/androidx/ui/material/samples/AppBarSamples.kt b/ui/ui-material/integration-tests/samples/src/main/java/androidx/ui/material/samples/AppBarSamples.kt
index 369ca53..8074eb7 100644
--- a/ui/ui-material/integration-tests/samples/src/main/java/androidx/ui/material/samples/AppBarSamples.kt
+++ b/ui/ui-material/integration-tests/samples/src/main/java/androidx/ui/material/samples/AppBarSamples.kt
@@ -28,9 +28,24 @@
import androidx.ui.painting.Image
@Suppress("UNUSED_VARIABLE")
+@Composable
+fun SimpleTopAppBar(getMyActionImage: () -> Image) {
+ val someActionImage: Image = getMyActionImage()
+ val contextualActions = listOf("Action 1" to someActionImage, "action 2" to someActionImage)
+
+ TopAppBar(
+ title = { Text("Simple TopAppBar") },
+ contextualActions = contextualActions
+ ) { actionData ->
+ val (actionTitle, actionImage) = actionData
+ AppBarIcon(actionImage) { /* doSomething()*/ }
+ }
+}
+
+@Suppress("UNUSED_VARIABLE")
@Sampled
@Composable
-fun SimpleTopAppBar(getMyActionImage: () -> Image, getMyNavigationImage: () -> Image) {
+fun SimpleTopAppBarNavIcon(getMyActionImage: () -> Image, getMyNavigationImage: () -> Image) {
val someActionImage: Image = getMyActionImage()
val someNavigationImage: Image = getMyNavigationImage()
diff --git a/ui/ui-material/integration-tests/samples/src/main/java/androidx/ui/material/samples/TabSamples.kt b/ui/ui-material/integration-tests/samples/src/main/java/androidx/ui/material/samples/TabSamples.kt
index 5982b39..058f62f 100644
--- a/ui/ui-material/integration-tests/samples/src/main/java/androidx/ui/material/samples/TabSamples.kt
+++ b/ui/ui-material/integration-tests/samples/src/main/java/androidx/ui/material/samples/TabSamples.kt
@@ -16,17 +16,26 @@
package androidx.ui.material.samples
+import androidx.animation.ColorPropKey
+import androidx.animation.DpPropKey
+import androidx.animation.transitionDefinition
import androidx.annotation.Sampled
import androidx.compose.Composable
import androidx.compose.composer
+import androidx.compose.memo
import androidx.compose.state
import androidx.compose.unaryPlus
+import androidx.ui.animation.Transition
import androidx.ui.core.Text
import androidx.ui.core.dp
-import androidx.ui.core.sp
import androidx.ui.foundation.ColoredRect
import androidx.ui.foundation.selection.MutuallyExclusiveSetItem
+import androidx.ui.foundation.shape.border.Border
+import androidx.ui.foundation.shape.border.DrawBorder
+import androidx.ui.foundation.shape.corner.CornerSizes
+import androidx.ui.foundation.shape.corner.RoundedCornerShape
import androidx.ui.graphics.Color
+import androidx.ui.layout.Alignment
import androidx.ui.layout.Center
import androidx.ui.layout.Column
import androidx.ui.layout.Container
@@ -35,8 +44,8 @@
import androidx.ui.layout.Padding
import androidx.ui.material.Tab
import androidx.ui.material.TabRow
+import androidx.ui.material.themeTextStyle
import androidx.ui.painting.Image
-import androidx.ui.text.TextStyle
@Sampled
@Composable
@@ -53,7 +62,7 @@
Center {
Text(
text = "Text tab ${state.value + 1} selected",
- style = TextStyle(fontSize = 10.sp)
+ style = +themeTextStyle { body1 }
)
}
}
@@ -74,7 +83,7 @@
Center {
Text(
text = "Icon tab ${state.value + 1} selected",
- style = TextStyle(fontSize = 10.sp)
+ style = +themeTextStyle { body1 }
)
}
}
@@ -99,7 +108,7 @@
Center {
Text(
text = "Text and icon tab ${state.value + 1} selected",
- style = TextStyle(fontSize = 10.sp)
+ style = +themeTextStyle { body1 }
)
}
}
@@ -108,7 +117,7 @@
@Sampled
@Composable
-fun CustomTabs() {
+fun FancyTabs() {
val state = +state { 0 }
val titles = listOf("TAB 1", "TAB 2", "TAB 3")
FlexColumn {
@@ -124,8 +133,73 @@
flexible(flex = 1f) {
Center {
Text(
- text = "Custom tab ${state.value + 1} selected",
- style = TextStyle(fontSize = 10.sp)
+ text = "Fancy tab ${state.value + 1} selected",
+ style = +themeTextStyle { body1 }
+ )
+ }
+ }
+ }
+}
+
+@Sampled
+@Composable
+fun FancyIndicatorTabs() {
+ val state = +state { 0 }
+ val titles = listOf("TAB 1", "TAB 2", "TAB 3")
+
+ // Reuse the default transition, and provide our custom indicator as its child
+ val indicatorContainer = @Composable { tabPositions: List<TabRow.TabPosition> ->
+ TabRow.IndicatorContainer(tabPositions = tabPositions, selectedIndex = state.value) {
+ FancyIndicator(Color.White)
+ }
+ }
+
+ FlexColumn {
+ inflexible {
+ TabRow(
+ items = titles,
+ selectedIndex = state.value,
+ indicatorContainer = indicatorContainer
+ ) { index, text ->
+ Tab(text = text, selected = state.value == index) { state.value = index }
+ }
+ }
+ flexible(flex = 1f) {
+ Center {
+ Text(
+ text = "Fancy indicator tab ${state.value + 1} selected",
+ style = +themeTextStyle { body1 }
+ )
+ }
+ }
+ }
+}
+
+@Sampled
+@Composable
+fun FancyIndicatorContainerTabs() {
+ val state = +state { 0 }
+ val titles = listOf("TAB 1", "TAB 2", "TAB 3")
+
+ val indicatorContainer = @Composable { tabPositions: List<TabRow.TabPosition> ->
+ FancyIndicatorContainer(tabPositions = tabPositions, selectedIndex = state.value)
+ }
+
+ FlexColumn {
+ inflexible {
+ TabRow(
+ items = titles,
+ selectedIndex = state.value,
+ indicatorContainer = indicatorContainer
+ ) { index, text ->
+ Tab(text = text, selected = state.value == index) { state.value = index }
+ }
+ }
+ flexible(flex = 1f) {
+ Center {
+ Text(
+ text = "Fancy transition tab ${state.value + 1} selected",
+ style = +themeTextStyle { body1 }
)
}
}
@@ -142,7 +216,77 @@
val color = if (selected) Color.Red else Color.Gray
ColoredRect(height = 10.dp, width = 10.dp, color = color)
Padding(5.dp) {
- Text(text = title, style = TextStyle(fontSize = 10.sp))
+ Text(text = title, style = +themeTextStyle { body1 })
+ }
+ }
+ }
+ }
+}
+
+@Sampled
+@Composable
+fun FancyIndicator(color: Color) {
+ // Draws a rounded rectangular with border around the Tab, with a 5.dp padding from the edges
+ // Color is passed in as a parameter [color]
+ Padding(5.dp) {
+ Container(expanded = true) {
+ DrawBorder(RoundedCornerShape(CornerSizes(5.dp)), Border(color, 2.dp))
+ }
+ }
+}
+
+@Sampled
+@Composable
+fun FancyIndicatorContainer(tabPositions: List<TabRow.TabPosition>, selectedIndex: Int) {
+ val indicatorStart = +memo { DpPropKey() }
+ val indicatorEnd = +memo { DpPropKey() }
+ val indicatorColor = +memo { ColorPropKey() }
+
+ val colors = listOf(Color.Yellow, Color.Red, Color.Green)
+ val transitionDefinition =
+ +memo {
+ transitionDefinition {
+ tabPositions.forEachIndexed { index, position ->
+ state(index) {
+ this[indicatorStart] = position.xOffset
+ this[indicatorEnd] = position.xOffset + position.width
+ this[indicatorColor] = colors[index]
+ }
+ }
+ repeat(tabPositions.size) { from ->
+ repeat(tabPositions.size) { to ->
+ if (from != to) {
+ transition(fromState = from, toState = to) {
+ // Handle directionality here, if we are moving to the right, we
+ // want the right side of the indicator to move faster, if we are
+ // moving to the left, we want the left side to move faster.
+ val startStiffness = if (from < to) 50f else 1000f
+ val endStiffness = if (from < to) 1000f else 50f
+ indicatorStart using physics {
+ dampingRatio = 1f
+ stiffness = startStiffness
+ }
+ indicatorEnd using physics {
+ dampingRatio = 1f
+ stiffness = endStiffness
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Fill up the entire TabRow with this container, and place children at the left so we can use
+ // Padding to set the 'offset'
+ Container(expanded = true, alignment = Alignment.BottomLeft) {
+ Transition(transitionDefinition, selectedIndex) { state ->
+ val offset = state[indicatorStart]
+ val width = state[indicatorEnd] - state[indicatorStart]
+ Padding(left = offset) {
+ Container(width = width) {
+ // Pass the current color to the indicator
+ FancyIndicator(state[indicatorColor])
}
}
}
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 edec03a..de0c637 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
@@ -76,7 +76,7 @@
}
@Test
- fun topAppBar_defaultPositioning() {
+ fun topAppBar_default_positioning() {
var appBarCoords: LayoutCoordinates? = null
var navigationIconCoords: LayoutCoordinates? = null
var titleCoords: LayoutCoordinates? = null
@@ -113,15 +113,14 @@
}
withDensity(composeTestRule.density) {
- // Navigation icon should be at the beginning
+ // Navigation icon should be 16.dp from the start
val navigationIconPositionX = navigationIconCoords!!.localToGlobal(PxPosition.Origin).x
val navigationIconExpectedPositionX = 16.dp.toIntPx().toPx()
Truth.assertThat(navigationIconPositionX).isEqualTo(navigationIconExpectedPositionX)
- // Title should be next
+ // Title should be 72.dp from the start
val titlePositionX = titleCoords!!.localToGlobal(PxPosition.Origin).x
- val titleExpectedPositionX =
- navigationIconPositionX + navigationIconCoords!!.size.width + 32.dp.toIntPx()
+ val titleExpectedPositionX = 72.dp.toIntPx().toPx()
Truth.assertThat(titlePositionX).isEqualTo(titleExpectedPositionX)
// Action should be placed at the end
@@ -133,6 +132,49 @@
}
@Test
+ fun topAppBar_noNavigationIcon_positioning() {
+ var appBarCoords: LayoutCoordinates? = null
+ var titleCoords: LayoutCoordinates? = null
+ var actionCoords: LayoutCoordinates? = null
+ composeTestRule.setMaterialContent {
+ Container {
+ OnChildPositioned(onPositioned = { coords ->
+ appBarCoords = coords
+ }) {
+ TopAppBar(
+ title = {
+ OnChildPositioned(onPositioned = { coords ->
+ titleCoords = coords
+ }) {
+ Text("title")
+ }
+ },
+ contextualActions = createImageList(1),
+ action = {
+ OnChildPositioned(onPositioned = { coords ->
+ actionCoords = coords
+ }) { it() }
+ }
+ )
+ }
+ }
+ }
+
+ withDensity(composeTestRule.density) {
+ // Title should now be placed 16.dp from the start, as there is no navigation icon
+ val titlePositionX = titleCoords!!.localToGlobal(PxPosition.Origin).x
+ val titleExpectedPositionX = 16.dp.toIntPx().toPx()
+ Truth.assertThat(titlePositionX).isEqualTo(titleExpectedPositionX)
+
+ // Action should still be placed at the end
+ val actionPositionX = actionCoords!!.localToGlobal(PxPosition.Origin).x
+ val actionExpectedPositionX =
+ appBarCoords!!.size.width - 16.dp.toIntPx() - 24.dp.toIntPx()
+ Truth.assertThat(actionPositionX).isEqualTo(actionExpectedPositionX)
+ }
+ }
+
+ @Test
fun topAppBar_oneAction() {
val tag = "action"
val numberOfActions = 1
@@ -199,6 +241,36 @@
}
@Test
+ fun bottomAppBar_noNavigationIcon_positioning() {
+ var appBarCoords: LayoutCoordinates? = null
+ var actionCoords: LayoutCoordinates? = null
+ composeTestRule.setMaterialContent {
+ Container {
+ OnChildPositioned(onPositioned = { coords ->
+ appBarCoords = coords
+ }) {
+ BottomAppBar(
+ contextualActions = createImageList(1),
+ action = {
+ OnChildPositioned(onPositioned = { coords ->
+ actionCoords = coords
+ }) { it() }
+ }
+ )
+ }
+ }
+ }
+
+ withDensity(composeTestRule.density) {
+ // Action should still be placed at the end, even though there is no navigation icon
+ val actionPositionX = actionCoords!!.localToGlobal(PxPosition.Origin).x
+ val actionExpectedPositionX = appBarCoords!!.size.width.round().toPx() -
+ 16.dp.toIntPx().toPx() - 24.dp.toIntPx().toPx()
+ Truth.assertThat(actionPositionX).isEqualTo(actionExpectedPositionX)
+ }
+ }
+
+ @Test
fun bottomAppBar_noFab_positioning() {
var appBarCoords: LayoutCoordinates? = null
var navigationIconCoords: LayoutCoordinates? = null
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 d6906c6..b4b1c5c 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
@@ -15,9 +15,20 @@
*/
package androidx.ui.material
+import androidx.compose.Composable
import androidx.compose.composer
+import androidx.compose.state
+import androidx.compose.unaryPlus
import androidx.test.filters.LargeTest
+import androidx.ui.core.LayoutCoordinates
+import androidx.ui.core.OnChildPositioned
+import androidx.ui.core.PxPosition
import androidx.ui.core.dp
+import androidx.ui.core.toPx
+import androidx.ui.core.withDensity
+import androidx.ui.foundation.ColoredRect
+import androidx.ui.graphics.Color
+import androidx.ui.layout.Alignment
import androidx.ui.layout.Container
import androidx.ui.material.samples.TextTabs
import androidx.ui.material.surface.Surface
@@ -29,6 +40,7 @@
import androidx.ui.test.createComposeRule
import androidx.ui.test.doClick
import androidx.ui.test.findAll
+import com.google.common.truth.Truth
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -91,6 +103,74 @@
}
@Test
+ fun tabRow_indicatorPosition() {
+ val indicatorHeight = 1.dp
+ var tabRowCoords: LayoutCoordinates? = null
+ var indicatorCoords: LayoutCoordinates? = null
+
+ composeTestRule
+ .setMaterialContent {
+ val state = +state { 0 }
+ val titles = listOf("TAB 1", "TAB 2")
+
+ val indicatorContainer = @Composable { tabPositions: List<TabRow.TabPosition> ->
+ TabRow.IndicatorContainer(tabPositions, state.value) {
+ OnChildPositioned({ indicatorCoords = it }) {
+ ColoredRect(Color.Red, height = indicatorHeight)
+ }
+ }
+ }
+
+ Container(alignment = Alignment.TopCenter) {
+ OnChildPositioned({ tabRowCoords = it }) {
+ TabRow(
+ items = titles,
+ selectedIndex = state.value,
+ indicatorContainer = indicatorContainer
+ ) { index, text ->
+ Tab(text = text, selected = state.value == index) {
+ state.value = index
+ }
+ }
+ }
+ }
+ }
+
+ val tabRowWidth = tabRowCoords!!.size.width
+ val tabRowHeight = tabRowCoords!!.size.height
+
+ // Indicator should be placed in the bottom left of the first tab
+ withDensity(composeTestRule.density) {
+ val indicatorPositionX = indicatorCoords!!.localToGlobal(PxPosition.Origin).x
+ val expectedPositionX = 0.dp.toPx()
+ Truth.assertThat(indicatorPositionX).isEqualTo(expectedPositionX)
+
+ val indicatorPositionY = indicatorCoords!!.localToGlobal(PxPosition.Origin).y
+ val expectedPositionY = tabRowHeight - indicatorHeight.toIntPx().toPx()
+ Truth.assertThat(indicatorPositionY).isEqualTo(expectedPositionY)
+ }
+
+ // Click the second tab
+ findAll { isInMutuallyExclusiveGroup }[1].doClick()
+
+ // TODO: we aren't correctly waiting for recompositions after clicking, so we need to wait
+ // again
+ findAll { isInMutuallyExclusiveGroup }
+
+ // Indicator should now be placed in the bottom left of the second tab, so its x coordinate
+ // should be in the middle of the TabRow
+ withDensity(composeTestRule.density) {
+ val indicatorPositionX = indicatorCoords!!.localToGlobal(PxPosition.Origin).x
+ val expectedPositionX = tabRowWidth / 2
+ Truth.assertThat(indicatorPositionX).isEqualTo(expectedPositionX)
+
+ val indicatorPositionY = indicatorCoords!!.localToGlobal(PxPosition.Origin).y
+ val expectedPositionY = tabRowHeight - indicatorHeight.toIntPx().toPx()
+ Truth.assertThat(indicatorPositionY).isEqualTo(expectedPositionY)
+ }
+ }
+
+ @Test
fun tabRow_initialTabSelected() {
composeTestRule
.setMaterialContent {
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 c3c4705..84652bd 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
@@ -40,6 +40,7 @@
import androidx.ui.layout.DpConstraints
import androidx.ui.layout.EdgeInsets
import androidx.ui.layout.Stack
+import androidx.ui.layout.Wrap
import androidx.ui.material.BottomAppBar.FabPosition
import androidx.ui.material.BottomAppBar.FabPosition.Center
import androidx.ui.material.BottomAppBar.FabPosition.End
@@ -51,7 +52,7 @@
* A TopAppBar displays information and actions relating to the current screen and is placed at the
* top of the screen.
*
- * @sample androidx.ui.material.samples.SimpleTopAppBar
+ * @sample androidx.ui.material.samples.SimpleTopAppBarNavIcon
*
* @param title The title to be displayed in the center of the TopAppBar
* @param color An optional color for the TopAppBar. By default [MaterialColors.primary] will be
@@ -64,11 +65,13 @@
* called for items in [contextualActions] up to the maximum number of icons that can be displayed.
* @param T the type of item in [contextualActions]
*/
+// TODO: b/137311217 - type inference for nullable lambdas currently doesn't work
+@Suppress("USELESS_CAST")
@Composable
fun <T> TopAppBar(
title: @Composable() () -> Unit = {},
color: Color = +themeColor { primary },
- navigationIcon: @Composable() () -> Unit = {},
+ navigationIcon: @Composable() (() -> Unit)? = null as @Composable() (() -> Unit)?,
contextualActions: List<T>? = null,
action: @Composable() (T) -> Unit = {}
// TODO: support overflow menu here with the remainder of the list
@@ -76,7 +79,12 @@
BaseTopAppBar(
color = color,
startContent = navigationIcon,
- title = title,
+ title = {
+ // Text color comes from the underlying Surface
+ CurrentTextStyleProvider(value = +themeTextStyle { h6 }) {
+ title()
+ }
+ },
endContent = {
if (contextualActions != null) {
AppBarActions(MaxIconsInTopAppBar, contextualActions, action)
@@ -88,27 +96,27 @@
@Composable
private fun BaseTopAppBar(
color: Color = +themeColor { primary },
- startContent: @Composable() () -> Unit,
+ startContent: @Composable() (() -> Unit)?,
title: @Composable() () -> Unit,
endContent: @Composable() () -> Unit
) {
BaseAppBar(color) {
FlexRow(mainAxisAlignment = MainAxisAlignment.SpaceBetween) {
- inflexible {
- // TODO: what should the spacing be when there is no icon provided here?
- startContent()
- // TODO: this accidentally works now because expanded fills up the space, but this
- // is actually adding another item for flex row to measure, meaning that when the
- // expanded block is empty, we are emitting an empty spacer in the middle of the row
- WidthSpacer(width = 32.dp)
- }
- expanded(1f) {
- CurrentTextStyleProvider(value = +themeTextStyle { h6 }) {
- title()
+ // We only want to reserve space here if we have some start content
+ if (startContent != null) {
+ inflexible {
+ Container(width = AppBarTitleStartPadding, alignment = Alignment.CenterLeft) {
+ startContent()
+ }
}
}
+ expanded(1f) {
+ title()
+ }
inflexible {
- endContent()
+ Wrap {
+ endContent()
+ }
}
}
}
@@ -211,6 +219,7 @@
BaseBottomAppBar(
color = color,
startContent = navigationIconComposable,
+ fab = null as @Composable() (() -> Unit)?,
endContent = actions(MaxIconsInBottomAppBarNoFab)
)
return
@@ -220,7 +229,8 @@
End -> BaseBottomAppBar(
color = color,
startContent = actions(MaxIconsInBottomAppBarEndFab),
- fab = { Align(Alignment.CenterRight) { floatingActionButton() } }
+ fab = { Align(Alignment.CenterRight) { floatingActionButton() } },
+ endContent = {}
)
// TODO: support CenterCut
else -> BaseBottomAppBar(
@@ -232,14 +242,12 @@
}
}
-// TODO: b/137311217 - type inference for nullable lambdas currently doesn't work
-@Suppress("USELESS_CAST")
@Composable
private fun BaseBottomAppBar(
color: Color = +themeColor { primary },
- startContent: @Composable() () -> Unit = {},
- fab: (@Composable() () -> Unit)? = null as @Composable() (() -> Unit)?,
- endContent: @Composable() () -> Unit = {}
+ startContent: @Composable() () -> Unit,
+ fab: @Composable() (() -> Unit)?,
+ endContent: @Composable() () -> Unit
) {
val appBar = @Composable { BaseBottomAppBarWithoutFab(color, startContent, endContent) }
if (fab == null) {
@@ -277,15 +285,17 @@
BaseAppBar(color) {
FlexRow(mainAxisAlignment = MainAxisAlignment.SpaceBetween) {
inflexible {
- startContent()
- // TODO: if startContent() doesn't have any layout, then the endContent won't be
- // placed at the end, so we need to trick it with a spacer
- // TODO: if startContent() does have layout, this currently inserts an extra
- // useless spacer which is placed evenly in the middle of the AppBar - this may
- // cause rounding alignment issues.
- WidthSpacer(width = 1.dp)
+ // Using a wrap so that even if startContent() is empty, we will still force
+ // end content to be placed at the end of the row.
+ Wrap {
+ startContent()
+ }
}
- inflexible { endContent() }
+ inflexible {
+ Wrap {
+ endContent()
+ }
+ }
}
}
}
@@ -351,12 +361,10 @@
*/
@Composable
fun AppBarIcon(icon: Image, onClick: () -> Unit) {
- Ripple(bounded = false) {
- Clickable(onClick = onClick) {
- Center {
- Container(width = ActionIconDiameter, height = ActionIconDiameter) {
- SimpleImage(icon)
- }
+ Container(width = ActionIconDiameter, height = ActionIconDiameter) {
+ Ripple(bounded = false) {
+ Clickable(onClick = onClick) {
+ SimpleImage(icon)
}
}
}
@@ -367,6 +375,7 @@
private val AppBarHeight = 56.dp
private val BottomAppBarHeightWithFab = 84.dp
private val AppBarPadding = 16.dp
+private val AppBarTitleStartPadding = 72.dp - AppBarPadding
private const val MaxIconsInTopAppBar = 2
private const val MaxIconsInBottomAppBarCenterFab = 2
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/Checkbox.kt b/ui/ui-material/src/main/java/androidx/ui/material/Checkbox.kt
index 51c1c4b..5437f266 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/Checkbox.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/Checkbox.kt
@@ -137,7 +137,7 @@
@Composable
private fun DrawCheckbox(value: ToggleableState, activeColor: Color) {
- val unselectedColor = (+themeColor { onSurface }).copy(alpha = UncheckedBoxOppacity)
+ val unselectedColor = (+themeColor { onSurface }).copy(alpha = UncheckedBoxOpacity)
val definition = +memo(activeColor, unselectedColor) {
generateTransitionDefinition(activeColor, unselectedColor)
}
@@ -313,5 +313,5 @@
private val StrokeWidth = 2.dp
private val RadiusSize = 2.dp
-private val UncheckedBoxOppacity = 0.6f
-private val CheckStrokeDefaultColor = Color(0xFFFFFFFF.toInt())
\ No newline at end of file
+private val UncheckedBoxOpacity = 0.6f
+private val CheckStrokeDefaultColor = Color.White
\ No newline at end of file
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 a6c1f37..296208b 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
@@ -123,11 +123,11 @@
/**
* The background color appears behind scrollable content.
*/
- val background: Color = Color(0xFFFFFFFF.toInt()),
+ val background: Color = Color.White,
/**
* The surface color is used on surfaces of components, such as cards, sheets and menus.
*/
- val surface: Color = Color(0xFFFFFFFF.toInt()),
+ val surface: Color = Color.White,
/**
* The error color is used to indicate error within components, such as text fields.
*/
@@ -135,23 +135,23 @@
/**
* Color used for text and icons displayed on top of the primary color.
*/
- val onPrimary: Color = Color(0xFFFFFFFF.toInt()),
+ val onPrimary: Color = Color.White,
/**
* Color used for text and icons displayed on top of the secondary color.
*/
- val onSecondary: Color = Color(0xFF000000.toInt()),
+ val onSecondary: Color = Color.Black,
/**
* Color used for text and icons displayed on top of the background color.
*/
- val onBackground: Color = Color(0xFF000000.toInt()),
+ val onBackground: Color = Color.Black,
/**
* Color used for text and icons displayed on top of the surface color.
*/
- val onSurface: Color = Color(0xFF000000.toInt()),
+ val onSurface: Color = Color.Black,
/**
* Color used for text and icons displayed on top of the error color.
*/
- val onError: Color = Color(0xFFFFFFFF.toInt())
+ val onError: Color = Color.White
)
/**
@@ -233,7 +233,7 @@
) { // light bg
materialColors.primary.copy(alpha = 0.12f)
} else { // dark bg
- Color(0xFFFFFFFF.toInt()).copy(alpha = 0.24f)
+ Color.White.copy(alpha = 0.24f)
}
}
)
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/Tab.kt b/ui/ui-material/src/main/java/androidx/ui/material/Tab.kt
index 7f5c452..bd85562 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/Tab.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/Tab.kt
@@ -31,7 +31,6 @@
import androidx.ui.core.Dp
import androidx.ui.core.WithConstraints
import androidx.ui.core.hasBoundedWidth
-import androidx.ui.core.px
import androidx.ui.core.withDensity
import androidx.ui.foundation.ColoredRect
import androidx.ui.foundation.SimpleImage
@@ -44,6 +43,7 @@
import androidx.ui.layout.MainAxisAlignment
import androidx.ui.layout.Padding
import androidx.ui.layout.Stack
+import androidx.ui.material.TabRow.TabPosition
import androidx.ui.material.ripple.Ripple
import androidx.ui.material.surface.Surface
import androidx.ui.painting.Image
@@ -58,26 +58,67 @@
*
* You can also provide your own custom tab, such as:
*
- * @sample androidx.ui.material.samples.CustomTabs
+ * @sample androidx.ui.material.samples.FancyTabs
*
* Where the custom tab itself could look like:
*
* @sample androidx.ui.material.samples.FancyTab
*
+ * As well as customizing the tab, you can also provide a custom [indicatorContainer], to customize
+ * the indicator displayed for a tab. [indicatorContainer] is responsible for positioning an
+ * indicator and for animating its position when [selectedIndex] changes.
+ *
+ * For example, given an indicator that draws a rounded rectangle near the edges of the [Tab]:
+ *
+ * @sample androidx.ui.material.samples.FancyIndicator
+ *
+ * We can reuse [TabRow.IndicatorContainer] and just provide this indicator, as we aren't changing
+ * the transition:
+ *
+ * @sample androidx.ui.material.samples.FancyIndicatorTabs
+ *
+ * You may also want to provide a custom transition, to allow you to dynamically change the
+ * appearance of the indicator as it animates between tabs, such as changing its color or size.
+ * [indicatorContainer] is stacked on top of the entire TabRow, so you just need to provide a custom
+ * container that animates the offset of the indicator from the start of the TabRow and place your
+ * custom indicator inside of it. For example, take the following custom container that animates
+ * position of the indicator, the color of the indicator, and also adds a physics based 'spring'
+ * effect to the indicator in the direction of motion:
+ *
+ * @sample androidx.ui.material.samples.FancyIndicatorContainer
+ *
+ * This container will fill up the entire width of the TabRow, and when a new tab is selected,
+ * the transition will be called with a new value for [selectedIndex], which will animate the
+ * indicator to the position of the new tab.
+ *
+ * We can use this custom container similarly to before:
+ *
+ * @sample androidx.ui.material.samples.FancyIndicatorContainerTabs
+ *
* @param T the type of the item provided that will map to a [Tab]
* @param items the list containing the items used to build this TabRow
* @param selectedIndex the index of the currently selected tab
+ * @param indicatorContainer the container responsible for positioning and animating the position of
+ * the indicator between tabs. By default this will be [TabRow.IndicatorContainer], which animates a
+ * [TabRow.Indicator] between tabs.
* @param tab the [Tab] to be emitted for the given index and element of type [T] in [items]
*
* @throws IllegalStateException when TabRow's parent has [Px.Infinity] width
*/
+
+// TODO: b/137311217 - type inference for nullable lambdas currently doesn't work
+@Suppress("USELESS_CAST")
@Composable
fun <T> TabRow(
items: List<T>,
selectedIndex: Int,
+ indicatorContainer: @Composable() (tabPositions: List<TabPosition>) -> Unit = { tabPositions ->
+ TabRow.IndicatorContainer(tabPositions, selectedIndex) {
+ TabRow.Indicator()
+ }
+ },
tab: @Composable() (Int, T) -> Unit
) {
- val count = items.size
Surface(color = +themeColor { primary }) {
WithConstraints { constraints ->
// TODO : think about Infinite max bounds case
@@ -85,84 +126,133 @@
val totalWidth = +withDensity {
constraints.maxWidth.toDp()
}
- val indicatorWidth = totalWidth / count
- TabIndicatorTransition(count, indicatorWidth, selectedIndex) { indicatorPosition ->
- Stack {
- aligned(Alignment.Center) {
- FlexRow {
- items.forEachIndexed { index, item ->
- expanded(1f) {
- tab(index, item)
- }
+ val height = +withDensity {
+ constraints.maxHeight.toDp()
+ }
+
+ val tabCount = items.size
+ val tabWidth = totalWidth / tabCount
+
+ val tabPositions = +memo(tabCount, tabWidth) {
+ (0 until tabCount).map { index ->
+ val xOffset = (tabWidth * index)
+ TabPosition(xOffset = xOffset, width = tabWidth, height = height)
+ }
+ }
+
+ Stack {
+ aligned(Alignment.Center) {
+ FlexRow {
+ items.forEachIndexed { index, item ->
+ expanded(1f) {
+ tab(index, item)
}
}
}
- aligned(Alignment.BottomCenter) {
- TabRowDivider()
- }
- positioned(leftInset = indicatorPosition, bottomInset = 0.dp) {
- TabIndicator(indicatorWidth)
- }
+ }
+ aligned(Alignment.BottomCenter) {
+ TabRow.Divider()
+ }
+ positioned(0.dp, 0.dp, 0.dp, 0.dp) {
+ indicatorContainer(tabPositions)
}
}
}
}
}
-private val IndicatorPosition = DpPropKey()
+object TabRow {
+ private val IndicatorOffset = DpPropKey()
-/**
- * [Transition] defining how the indicator position animates between tabs, when a new tab is
- * selected.
- */
-@Composable
-private fun TabIndicatorTransition(
- tabCount: Int,
- indicatorWidth: Dp,
- selectedIndex: Int,
- children: @Composable() (indicatorPosition: Dp) -> Unit
-) {
- val transitionDefinition = +memo(tabCount, indicatorWidth) {
- transitionDefinition {
- // TODO: currently the first state set is the 'default' state, so we want to define the
- // state that is initially selected first, so we don't have any initial animations
- // when this is supported by transitionDefinition, we should fix this to just set a
- // default or similar
- state(selectedIndex) {
- this[IndicatorPosition] = indicatorWidth * selectedIndex
- }
- (0 until tabCount).minus(selectedIndex).forEach { tabIndex ->
- state(tabIndex) {
- this[IndicatorPosition] = indicatorWidth * tabIndex
- }
- }
+ /**
+ * Data class that contains information about a tab's position on screen
+ *
+ * @param xOffset how far from the start of the [TabRow] this tab is positioned
+ * @param width the width of this tab
+ * @param height the height of this tab
+ */
+ data class TabPosition(val xOffset: Dp, val width: Dp, val height: Dp)
- transition {
- IndicatorPosition using tween {
- duration = 250
- easing = FastOutSlowInEasing
+ /**
+ * Positions and animates the given [indicator] between tabs when [selectedIndex] changes.
+ */
+ @Composable
+ fun IndicatorContainer(
+ tabPositions: List<TabPosition>,
+ selectedIndex: Int,
+ indicator: @Composable() () -> Unit
+ ) {
+ // TODO: should we animate the width of the indicator as it moves between tabs of different
+ // sizes inside a scrolling container?
+ val currentTabWidth = tabPositions[selectedIndex].width
+
+ Container(expanded = true, alignment = Alignment.BottomLeft) {
+ IndicatorTransition(tabPositions, selectedIndex) { indicatorOffset ->
+ Padding(left = indicatorOffset) {
+ Container(width = currentTabWidth) {
+ indicator()
+ }
}
}
}
}
- Transition(transitionDefinition, selectedIndex) { state ->
- children(state[IndicatorPosition])
+
+ /**
+ * Default indicator, which will be positioned at the bottom of the tab, on top of the divider.
+ *
+ * This is used as the default indicator inside [TabRow].
+ */
+ @Composable
+ fun Indicator() {
+ ColoredRect(color = +themeColor { onPrimary }, height = IndicatorHeight)
}
-}
-@Composable
-private fun TabIndicator(width: Dp) {
- ColoredRect(
- color = +themeColor { onPrimary },
- height = IndicatorHeight,
- width = width
- )
-}
+ /**
+ * [Transition] that animates the indicator offset between a given list of [TabPosition]s.
+ */
+ @Composable
+ internal fun IndicatorTransition(
+ tabPositions: List<TabPosition>,
+ selectedIndex: Int,
+ children: @Composable() (indicatorOffset: Dp) -> Unit
+ ) {
+ val transitionDefinition = +memo(tabPositions) {
+ transitionDefinition {
+ // TODO: currently the first state set is the 'default' state, so we want to define the
+ // state that is initially selected first, so we don't have any initial animations
+ // when this is supported by transitionDefinition, we should fix this to just set a
+ // default or similar
+ state(selectedIndex) {
+ this[IndicatorOffset] = tabPositions[selectedIndex].xOffset
+ }
-@Composable
-private fun TabRowDivider() {
- val onPrimary = +themeColor { onPrimary }
- Divider(color = (onPrimary.copy(alpha = DividerOpacity)))
+ tabPositions.forEachIndexed { index, position ->
+ if (index != selectedIndex) {
+ state(index) {
+ this[IndicatorOffset] = position.xOffset
+ }
+ }
+ }
+
+ transition {
+ IndicatorOffset using tween {
+ duration = 250
+ easing = FastOutSlowInEasing
+ }
+ }
+ }
+ }
+
+ Transition(transitionDefinition, selectedIndex) { state ->
+ children(state[IndicatorOffset])
+ }
+ }
+
+ @Composable
+ internal fun Divider() {
+ val onPrimary = +themeColor { onPrimary }
+ Divider(color = (onPrimary.copy(alpha = DividerOpacity)))
+ }
}
/**
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 6f61e77..139a554 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
@@ -108,8 +108,7 @@
)
val color = theme.colorCallback.invoke(rippleSurface.backgroundColor)
val onAnimationFinished = { effect: RippleEffect ->
- val contains = effects.remove(effect)
- require(contains)
+ effects.remove(effect)
if (currentEffect == effect) {
currentEffect = 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 77a835f..0989de0 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
@@ -163,6 +163,14 @@
KeyboardType.Email ->
outInfo.inputType =
InputType.TYPE_CLASS_TEXT or EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
+ KeyboardType.Password -> {
+ outInfo.inputType =
+ InputType.TYPE_CLASS_TEXT or EditorInfo.TYPE_TEXT_VARIATION_PASSWORD
+ }
+ KeyboardType.NumberPassword -> {
+ outInfo.inputType =
+ InputType.TYPE_CLASS_NUMBER or EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD
+ }
else -> throw IllegalArgumentException("Unknown KeyboardType: $keyboardType")
}
outInfo.imeOptions =
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 81f82ab..575460d 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
@@ -157,6 +157,42 @@
}
@Test
+ fun test_fill_editor_info_password() {
+ textInputService.startInput(
+ EditorState(""),
+ KeyboardType.Password,
+ ImeAction.Unspecified,
+ onEditCommand = {},
+ onImeActionPerformed = {})
+
+ EditorInfo().let { info ->
+ textInputService.createInputConnection(info)
+ assertTrue((InputType.TYPE_CLASS_TEXT and info.inputType) != 0)
+ assertTrue((InputType.TYPE_TEXT_VARIATION_PASSWORD and info.inputType) != 0)
+ assertTrue((EditorInfo.IME_MASK_ACTION and info.imeOptions)
+ == EditorInfo.IME_ACTION_UNSPECIFIED)
+ }
+ }
+
+ @Test
+ fun test_fill_editor_info_number_password() {
+ textInputService.startInput(
+ EditorState(""),
+ KeyboardType.NumberPassword,
+ ImeAction.Unspecified,
+ onEditCommand = {},
+ onImeActionPerformed = {})
+
+ EditorInfo().let { info ->
+ textInputService.createInputConnection(info)
+ assertTrue((InputType.TYPE_CLASS_NUMBER and info.inputType) != 0)
+ assertTrue((InputType.TYPE_NUMBER_VARIATION_PASSWORD and info.inputType) != 0)
+ assertTrue((EditorInfo.IME_MASK_ACTION and info.imeOptions)
+ == EditorInfo.IME_ACTION_UNSPECIFIED)
+ }
+ }
+
+ @Test
fun test_fill_editor_info_action_none() {
textInputService.startInput(
EditorState(""),
diff --git a/ui/ui-text/api/1.0.0-alpha01.txt b/ui/ui-text/api/1.0.0-alpha01.txt
index 0aa0326..59a09a0 100644
--- a/ui/ui-text/api/1.0.0-alpha01.txt
+++ b/ui/ui-text/api/1.0.0-alpha01.txt
@@ -26,6 +26,8 @@
enum_constant public static final androidx.ui.input.KeyboardType Ascii;
enum_constant public static final androidx.ui.input.KeyboardType Email;
enum_constant public static final androidx.ui.input.KeyboardType Number;
+ enum_constant public static final androidx.ui.input.KeyboardType NumberPassword;
+ enum_constant public static final androidx.ui.input.KeyboardType Password;
enum_constant public static final androidx.ui.input.KeyboardType Phone;
enum_constant public static final androidx.ui.input.KeyboardType Text;
enum_constant public static final androidx.ui.input.KeyboardType Uri;
@@ -76,7 +78,6 @@
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();
@@ -87,12 +88,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;
@@ -137,9 +138,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();
@@ -147,10 +148,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/current.txt b/ui/ui-text/api/current.txt
index 0aa0326..59a09a0 100644
--- a/ui/ui-text/api/current.txt
+++ b/ui/ui-text/api/current.txt
@@ -26,6 +26,8 @@
enum_constant public static final androidx.ui.input.KeyboardType Ascii;
enum_constant public static final androidx.ui.input.KeyboardType Email;
enum_constant public static final androidx.ui.input.KeyboardType Number;
+ enum_constant public static final androidx.ui.input.KeyboardType NumberPassword;
+ enum_constant public static final androidx.ui.input.KeyboardType Password;
enum_constant public static final androidx.ui.input.KeyboardType Phone;
enum_constant public static final androidx.ui.input.KeyboardType Text;
enum_constant public static final androidx.ui.input.KeyboardType Uri;
@@ -76,7 +78,6 @@
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();
@@ -87,12 +88,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;
@@ -137,9 +138,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();
@@ -147,10 +148,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 e2f3b68..cb841ce 100644
--- a/ui/ui-text/api/restricted_1.0.0-alpha01.txt
+++ b/ui/ui-text/api/restricted_1.0.0-alpha01.txt
@@ -35,6 +35,8 @@
enum_constant public static final androidx.ui.input.KeyboardType Ascii;
enum_constant public static final androidx.ui.input.KeyboardType Email;
enum_constant public static final androidx.ui.input.KeyboardType Number;
+ enum_constant public static final androidx.ui.input.KeyboardType NumberPassword;
+ enum_constant public static final androidx.ui.input.KeyboardType Password;
enum_constant public static final androidx.ui.input.KeyboardType Phone;
enum_constant public static final androidx.ui.input.KeyboardType Text;
enum_constant public static final androidx.ui.input.KeyboardType Uri;
@@ -90,7 +92,6 @@
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();
@@ -101,12 +102,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;
@@ -151,9 +152,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();
@@ -161,10 +162,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 e2f3b68..cb841ce 100644
--- a/ui/ui-text/api/restricted_current.txt
+++ b/ui/ui-text/api/restricted_current.txt
@@ -35,6 +35,8 @@
enum_constant public static final androidx.ui.input.KeyboardType Ascii;
enum_constant public static final androidx.ui.input.KeyboardType Email;
enum_constant public static final androidx.ui.input.KeyboardType Number;
+ enum_constant public static final androidx.ui.input.KeyboardType NumberPassword;
+ enum_constant public static final androidx.ui.input.KeyboardType Password;
enum_constant public static final androidx.ui.input.KeyboardType Phone;
enum_constant public static final androidx.ui.input.KeyboardType Text;
enum_constant public static final androidx.ui.input.KeyboardType Uri;
@@ -90,7 +92,6 @@
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();
@@ -101,12 +102,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;
@@ -151,9 +152,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();
@@ -161,10 +162,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/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 e377a1a..c71370d 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
@@ -36,7 +36,9 @@
Pair(KeyboardType.Ascii, "Ascii"),
Pair(KeyboardType.Number, "Number"),
Pair(KeyboardType.Email, "Email"),
- Pair(KeyboardType.Phone, "Phone")
+ Pair(KeyboardType.Phone, "Phone"),
+ Pair(KeyboardType.Password, "Password"),
+ Pair(KeyboardType.NumberPassword, "NumberPassword")
)
val IME_ACTIONS = listOf(
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..1f46d90
--- /dev/null
+++ b/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneVariousInputField.kt
@@ -0,0 +1,243 @@
+/*
+ * 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.InputField
+import androidx.ui.core.OffsetMap
+import androidx.ui.core.PasswordVisualTransformation
+import androidx.ui.core.TransformedText
+import androidx.ui.core.VisualTransformation
+import androidx.ui.input.EditorState
+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
+ )
+ }
+ }
+}
+
+@Composable
+fun VariousEditLine(
+ keyboardType: KeyboardType = KeyboardType.Text,
+ imeAction: ImeAction = ImeAction.Unspecified,
+ onValueChange: (EditorState, EditorState) -> EditorState = { _, new -> new },
+ visualTransformation: VisualTransformation
+) {
+ val state = +state { EditorState() }
+ InputField(
+ value = state.value,
+ keyboardType = keyboardType,
+ imeAction = imeAction,
+ visualTransformation = visualTransformation,
+ onValueChange = { state.value = onValueChange(state.value, it) },
+ editorStyle = EditorStyle(textStyle = TextStyle(fontSize = fontSize8))
+ )
+}
diff --git a/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneVariousInputFieldActivity.kt b/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneVariousInputFieldActivity.kt
new file mode 100644
index 0000000..0aae714
--- /dev/null
+++ b/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneVariousInputFieldActivity.kt
@@ -0,0 +1,29 @@
+/*
+ * 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 android.app.Activity
+import android.os.Bundle
+import androidx.compose.composer
+import androidx.ui.core.setContent
+
+class CraneVariousInputFieldActivity : Activity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContent { VariousInputFieldDemo() }
+ }
+}
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..841eb1b 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
@@ -208,7 +209,7 @@
}
@Test
- fun getPositionForOffset_ltr() {
+ fun getOffsetForPosition_ltr() {
withDensity(defaultDensity) {
val text = "abc"
val fontSize = 50.sp
@@ -218,11 +219,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 +231,7 @@
}
@Test
- fun getPositionForOffset_rtl() {
+ fun getOffsetForPosition_rtl() {
withDensity(defaultDensity) {
val text = "\u05D0\u05D1\u05D2"
val fontSize = 50.sp
@@ -241,11 +242,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 +254,7 @@
}
@Test
- fun getPositionForOffset_ltr_multiline() {
+ fun getOffsetForPosition_ltr_multiline() {
withDensity(defaultDensity) {
val firstLine = "abc"
val secondLine = "def"
@@ -267,11 +268,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 +280,7 @@
}
@Test
- fun getPositionForOffset_rtl_multiline() {
+ fun getOffsetForPosition_rtl_multiline() {
withDensity(defaultDensity) {
val firstLine = "\u05D0\u05D1\u05D2"
val secondLine = "\u05D3\u05D4\u05D5"
@@ -293,11 +294,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 +306,7 @@
}
@Test
- fun getPositionForOffset_ltr_width_outOfBounds() {
+ fun getOffsetForPosition_ltr_width_outOfBounds() {
withDensity(defaultDensity) {
val text = "abc"
val fontSize = 50.sp
@@ -315,19 +316,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 +338,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))
}
}
@@ -835,9 +836,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 +857,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 +878,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 +900,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 +922,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 +1310,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 +1336,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 +1364,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 +1934,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..41f1cf3 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,7 +21,9 @@
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
@@ -313,7 +315,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 +344,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)
@@ -489,8 +491,7 @@
start = 0,
end = text.length,
color = defaultSelectionColor,
- canvas = actualCanvas,
- offset = Offset.zero
+ canvas = actualCanvas
)
// Assert.
@@ -558,8 +559,7 @@
start = selectionStart,
end = selectionEnd,
color = defaultSelectionColor,
- canvas = actualCanvas,
- offset = Offset.zero
+ canvas = actualCanvas
)
// Assert
@@ -642,8 +642,7 @@
start = selectionLTRStart,
end = textLTR.length + selectionRTLEnd,
color = defaultSelectionColor,
- canvas = actualCanvas,
- offset = Offset.zero
+ canvas = actualCanvas
)
// Assert
@@ -711,8 +710,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/KeyboardType.kt b/ui/ui-text/src/main/java/androidx/ui/input/KeyboardType.kt
index 1cd2750..ee7f088 100644
--- a/ui/ui-text/src/main/java/androidx/ui/input/KeyboardType.kt
+++ b/ui/ui-text/src/main/java/androidx/ui/input/KeyboardType.kt
@@ -48,5 +48,15 @@
/**
* A keyboard type used to request an IME that is capable of inputting email addresses.
*/
- Email
+ Email,
+
+ /**
+ * A keyboard type used to request an IME that is capable of inputting password
+ */
+ Password,
+
+ /**
+ * A keyboard type used to request an IME that is capable of inputting number password.
+ */
+ NumberPassword
}
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/TextPainter.kt b/ui/ui-text/src/main/java/androidx/ui/text/TextPainter.kt
index e6e5b4d..7880c2d 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
@@ -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,14 @@
}
canvas.clipRect(bounds)
}
- paragraph!!.paint(canvas, offset.dx, offset.dy)
+ paragraph!!.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 +444,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)
// 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.
*
@@ -472,15 +471,50 @@
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 paragraph!!.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 paragraph!!.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 paragraph!!.getPrimaryHorizontal(offset)
+ }
+
+ /** Returns the character offset closest to the given graphical position. */
+ fun getOffsetForPosition(position: PxPosition): Int {
+ assert(!needsLayout)
+ return paragraph!!.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.
*
@@ -493,15 +527,15 @@
}
/**
- * 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 paragraph!!.getWordBoundary(offset)
}
-}
\ No newline at end of file
+}
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..054eb01 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
@@ -203,14 +203,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 {
@@ -267,22 +267,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 {
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..6c07a79 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
@@ -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/versionedparcelable/api/restricted_1.2.0-alpha01.ignore b/versionedparcelable/api/restricted_1.2.0-alpha01.ignore
index 4b76959..e7a06b2 100644
--- a/versionedparcelable/api/restricted_1.2.0-alpha01.ignore
+++ b/versionedparcelable/api/restricted_1.2.0-alpha01.ignore
@@ -1,6 +1,6 @@
// Baseline format: 1.0
ChangedType: androidx.versionedparcelable.VersionedParcel#mParcelizerCache:
- Field androidx.versionedparcelable.VersionedParcel.mParcelizerCache has changed type from androidx.collection.ArrayMap<java.lang.String!,java.lang.Class!> to androidx.collection.ArrayMap<java.lang.String,java.lang.Class<?>>
+ Field androidx.versionedparcelable.VersionedParcel.mParcelizerCache has changed type from androidx.collection.ArrayMap<java.lang.String!,java.lang.Class!> to androidx.collection.ArrayMap<java.lang.String!,java.lang.Class<?>!>
ChangedType: androidx.versionedparcelable.VersionedParcelize#factory():
Method androidx.versionedparcelable.VersionedParcelize.factory has changed return type from Class to Class<?>
diff --git a/viewpager/build.gradle b/viewpager/build.gradle
index 2559de8..f1f7e65 100644
--- a/viewpager/build.gradle
+++ b/viewpager/build.gradle
@@ -17,7 +17,7 @@
dependencies {
api("androidx.annotation:annotation:1.1.0")
- implementation("androidx.core:core:1.1.0-rc01")
+ implementation("androidx.core:core:1.1.0")
api(project(":customview"))
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
diff --git a/viewpager2/api/1.0.0-beta03.txt b/viewpager2/api/1.0.0-beta03.txt
new file mode 100644
index 0000000..e1b6ab9
--- /dev/null
+++ b/viewpager2/api/1.0.0-beta03.txt
@@ -0,0 +1,96 @@
+// Signature format: 3.0
+package androidx.viewpager2.adapter {
+
+ public abstract class FragmentStateAdapter extends androidx.recyclerview.widget.RecyclerView.Adapter<androidx.viewpager2.adapter.FragmentViewHolder> implements androidx.viewpager2.adapter.StatefulAdapter {
+ ctor public FragmentStateAdapter(androidx.fragment.app.FragmentActivity);
+ ctor public FragmentStateAdapter(androidx.fragment.app.Fragment);
+ ctor public FragmentStateAdapter(androidx.fragment.app.FragmentManager, androidx.lifecycle.Lifecycle);
+ method public boolean containsItem(long);
+ method public abstract androidx.fragment.app.Fragment createFragment(int);
+ method public final void onBindViewHolder(androidx.viewpager2.adapter.FragmentViewHolder, int);
+ method public final androidx.viewpager2.adapter.FragmentViewHolder onCreateViewHolder(android.view.ViewGroup, int);
+ method public final boolean onFailedToRecycleView(androidx.viewpager2.adapter.FragmentViewHolder);
+ method public final void onViewAttachedToWindow(androidx.viewpager2.adapter.FragmentViewHolder);
+ method public final void onViewRecycled(androidx.viewpager2.adapter.FragmentViewHolder);
+ method public final void restoreState(android.os.Parcelable);
+ method public final android.os.Parcelable saveState();
+ method public final void setHasStableIds(boolean);
+ }
+
+ public final class FragmentViewHolder extends androidx.recyclerview.widget.RecyclerView.ViewHolder {
+ }
+
+ public interface StatefulAdapter {
+ method public void restoreState(android.os.Parcelable);
+ method public android.os.Parcelable saveState();
+ }
+
+}
+
+package androidx.viewpager2.widget {
+
+ public final class CompositePageTransformer implements androidx.viewpager2.widget.ViewPager2.PageTransformer {
+ ctor public CompositePageTransformer();
+ method public void addTransformer(androidx.viewpager2.widget.ViewPager2.PageTransformer);
+ method public void removeTransformer(androidx.viewpager2.widget.ViewPager2.PageTransformer);
+ method public void transformPage(android.view.View, float);
+ }
+
+ public final class MarginPageTransformer implements androidx.viewpager2.widget.ViewPager2.PageTransformer {
+ ctor public MarginPageTransformer(@Px int);
+ method public void transformPage(android.view.View, float);
+ }
+
+ public final class ViewPager2 extends android.view.ViewGroup {
+ ctor public ViewPager2(android.content.Context);
+ ctor public ViewPager2(android.content.Context, android.util.AttributeSet?);
+ ctor public ViewPager2(android.content.Context, android.util.AttributeSet?, int);
+ ctor @RequiresApi(21) public ViewPager2(android.content.Context, android.util.AttributeSet?, int, int);
+ method public void addItemDecoration(androidx.recyclerview.widget.RecyclerView.ItemDecoration);
+ method public void addItemDecoration(androidx.recyclerview.widget.RecyclerView.ItemDecoration, int);
+ method public boolean beginFakeDrag();
+ method public boolean endFakeDrag();
+ method public boolean fakeDragBy(@Px float);
+ method public androidx.recyclerview.widget.RecyclerView.Adapter? getAdapter();
+ method public int getCurrentItem();
+ method public androidx.recyclerview.widget.RecyclerView.ItemDecoration getItemDecorationAt(int);
+ method public int getItemDecorationCount();
+ method public int getOffscreenPageLimit();
+ method public int getOrientation();
+ method public int getScrollState();
+ method public void invalidateItemDecorations();
+ method public boolean isFakeDragging();
+ method public boolean isUserInputEnabled();
+ method public void registerOnPageChangeCallback(androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback);
+ method public void removeItemDecoration(androidx.recyclerview.widget.RecyclerView.ItemDecoration);
+ method public void removeItemDecorationAt(int);
+ method public void requestTransform();
+ method public void setAdapter(androidx.recyclerview.widget.RecyclerView.Adapter?);
+ method public void setCurrentItem(int);
+ method public void setCurrentItem(int, boolean);
+ method public void setOffscreenPageLimit(int);
+ method public void setOrientation(int);
+ method public void setPageTransformer(androidx.viewpager2.widget.ViewPager2.PageTransformer?);
+ method public void setUserInputEnabled(boolean);
+ method public void unregisterOnPageChangeCallback(androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback);
+ field public static final int OFFSCREEN_PAGE_LIMIT_DEFAULT = -1; // 0xffffffff
+ field public static final int ORIENTATION_HORIZONTAL = 0; // 0x0
+ field public static final int ORIENTATION_VERTICAL = 1; // 0x1
+ field public static final int SCROLL_STATE_DRAGGING = 1; // 0x1
+ field public static final int SCROLL_STATE_IDLE = 0; // 0x0
+ field public static final int SCROLL_STATE_SETTLING = 2; // 0x2
+ }
+
+ public abstract static class ViewPager2.OnPageChangeCallback {
+ ctor public ViewPager2.OnPageChangeCallback();
+ method public void onPageScrollStateChanged(int);
+ method public void onPageScrolled(int, float, @Px int);
+ method public void onPageSelected(int);
+ }
+
+ public static interface ViewPager2.PageTransformer {
+ method public void transformPage(android.view.View, float);
+ }
+
+}
+
diff --git a/benchmark/api/res-1.0.0-alpha03.txt b/viewpager2/api/res-1.0.0-beta03.txt
similarity index 100%
copy from benchmark/api/res-1.0.0-alpha03.txt
copy to viewpager2/api/res-1.0.0-beta03.txt
diff --git a/viewpager2/api/restricted_1.0.0-beta03.txt b/viewpager2/api/restricted_1.0.0-beta03.txt
new file mode 100644
index 0000000..686a4b0
--- /dev/null
+++ b/viewpager2/api/restricted_1.0.0-beta03.txt
@@ -0,0 +1,105 @@
+// Signature format: 3.0
+package androidx.viewpager2.adapter {
+
+ public abstract class FragmentStateAdapter extends androidx.recyclerview.widget.RecyclerView.Adapter<androidx.viewpager2.adapter.FragmentViewHolder> implements androidx.viewpager2.adapter.StatefulAdapter {
+ ctor public FragmentStateAdapter(androidx.fragment.app.FragmentActivity);
+ ctor public FragmentStateAdapter(androidx.fragment.app.Fragment);
+ ctor public FragmentStateAdapter(androidx.fragment.app.FragmentManager, androidx.lifecycle.Lifecycle);
+ method public boolean containsItem(long);
+ method public abstract androidx.fragment.app.Fragment createFragment(int);
+ method public final void onBindViewHolder(androidx.viewpager2.adapter.FragmentViewHolder, int);
+ method public final androidx.viewpager2.adapter.FragmentViewHolder onCreateViewHolder(android.view.ViewGroup, int);
+ method public final boolean onFailedToRecycleView(androidx.viewpager2.adapter.FragmentViewHolder);
+ method public final void onViewAttachedToWindow(androidx.viewpager2.adapter.FragmentViewHolder);
+ method public final void onViewRecycled(androidx.viewpager2.adapter.FragmentViewHolder);
+ method public final void restoreState(android.os.Parcelable);
+ method public final android.os.Parcelable saveState();
+ method public final void setHasStableIds(boolean);
+ }
+
+ public final class FragmentViewHolder extends androidx.recyclerview.widget.RecyclerView.ViewHolder {
+ }
+
+ public interface StatefulAdapter {
+ method public void restoreState(android.os.Parcelable);
+ method public android.os.Parcelable saveState();
+ }
+
+}
+
+package androidx.viewpager2.widget {
+
+ public final class CompositePageTransformer implements androidx.viewpager2.widget.ViewPager2.PageTransformer {
+ ctor public CompositePageTransformer();
+ method public void addTransformer(androidx.viewpager2.widget.ViewPager2.PageTransformer);
+ method public void removeTransformer(androidx.viewpager2.widget.ViewPager2.PageTransformer);
+ method public void transformPage(android.view.View, float);
+ }
+
+ public final class MarginPageTransformer implements androidx.viewpager2.widget.ViewPager2.PageTransformer {
+ ctor public MarginPageTransformer(@Px int);
+ method public void transformPage(android.view.View, float);
+ }
+
+ public final class ViewPager2 extends android.view.ViewGroup {
+ ctor public ViewPager2(android.content.Context);
+ ctor public ViewPager2(android.content.Context, android.util.AttributeSet?);
+ ctor public ViewPager2(android.content.Context, android.util.AttributeSet?, int);
+ ctor @RequiresApi(21) public ViewPager2(android.content.Context, android.util.AttributeSet?, int, int);
+ method public void addItemDecoration(androidx.recyclerview.widget.RecyclerView.ItemDecoration);
+ method public void addItemDecoration(androidx.recyclerview.widget.RecyclerView.ItemDecoration, int);
+ method public boolean beginFakeDrag();
+ method public boolean endFakeDrag();
+ method public boolean fakeDragBy(@Px float);
+ method public androidx.recyclerview.widget.RecyclerView.Adapter? getAdapter();
+ method public int getCurrentItem();
+ method public androidx.recyclerview.widget.RecyclerView.ItemDecoration getItemDecorationAt(int);
+ method public int getItemDecorationCount();
+ method @androidx.viewpager2.widget.ViewPager2.OffscreenPageLimit public int getOffscreenPageLimit();
+ method @androidx.viewpager2.widget.ViewPager2.Orientation public int getOrientation();
+ method @androidx.viewpager2.widget.ViewPager2.ScrollState public int getScrollState();
+ method public void invalidateItemDecorations();
+ method public boolean isFakeDragging();
+ method public boolean isUserInputEnabled();
+ method public void registerOnPageChangeCallback(androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback);
+ method public void removeItemDecoration(androidx.recyclerview.widget.RecyclerView.ItemDecoration);
+ method public void removeItemDecorationAt(int);
+ method public void requestTransform();
+ method public void setAdapter(androidx.recyclerview.widget.RecyclerView.Adapter?);
+ method public void setCurrentItem(int);
+ method public void setCurrentItem(int, boolean);
+ method public void setOffscreenPageLimit(@androidx.viewpager2.widget.ViewPager2.OffscreenPageLimit int);
+ method public void setOrientation(@androidx.viewpager2.widget.ViewPager2.Orientation int);
+ method public void setPageTransformer(androidx.viewpager2.widget.ViewPager2.PageTransformer?);
+ method public void setUserInputEnabled(boolean);
+ method public void unregisterOnPageChangeCallback(androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback);
+ field public static final int OFFSCREEN_PAGE_LIMIT_DEFAULT = -1; // 0xffffffff
+ field public static final int ORIENTATION_HORIZONTAL = 0; // 0x0
+ field public static final int ORIENTATION_VERTICAL = 1; // 0x1
+ field public static final int SCROLL_STATE_DRAGGING = 1; // 0x1
+ field public static final int SCROLL_STATE_IDLE = 0; // 0x0
+ field public static final int SCROLL_STATE_SETTLING = 2; // 0x2
+ }
+
+ @IntDef({androidx.viewpager2.widget.ViewPager2.OFFSCREEN_PAGE_LIMIT_DEFAULT}) @IntRange(from=1) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ViewPager2.OffscreenPageLimit {
+ }
+
+ public abstract static class ViewPager2.OnPageChangeCallback {
+ ctor public ViewPager2.OnPageChangeCallback();
+ method public void onPageScrollStateChanged(@androidx.viewpager2.widget.ViewPager2.ScrollState int);
+ method public void onPageScrolled(int, float, @Px int);
+ method public void onPageSelected(int);
+ }
+
+ @IntDef({androidx.viewpager2.widget.ViewPager2.ORIENTATION_HORIZONTAL, androidx.viewpager2.widget.ViewPager2.ORIENTATION_VERTICAL}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ViewPager2.Orientation {
+ }
+
+ public static interface ViewPager2.PageTransformer {
+ method public void transformPage(android.view.View, float);
+ }
+
+ @IntDef({androidx.viewpager2.widget.ViewPager2.SCROLL_STATE_IDLE, androidx.viewpager2.widget.ViewPager2.SCROLL_STATE_DRAGGING, androidx.viewpager2.widget.ViewPager2.SCROLL_STATE_SETTLING}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ViewPager2.ScrollState {
+ }
+
+}
+
diff --git a/viewpager2/build.gradle b/viewpager2/build.gradle
index 9de6aa7..29ea26d 100644
--- a/viewpager2/build.gradle
+++ b/viewpager2/build.gradle
@@ -27,7 +27,7 @@
dependencies {
api("androidx.annotation:annotation:1.1.0")
- implementation("androidx.core:core:1.1.0-rc01")
+ implementation("androidx.core:core:1.1.0")
api("androidx.fragment:fragment:1.1.0-rc01")
api("androidx.recyclerview:recyclerview:1.1.0-beta01")
implementation("androidx.collection:collection:1.1.0")
diff --git a/viewpager2/integration-tests/testapp/build.gradle b/viewpager2/integration-tests/testapp/build.gradle
index 0452879..00fbbe0 100644
--- a/viewpager2/integration-tests/testapp/build.gradle
+++ b/viewpager2/integration-tests/testapp/build.gradle
@@ -16,7 +16,6 @@
import static androidx.build.dependencies.DependenciesKt.ESPRESSO_CORE
import static androidx.build.dependencies.DependenciesKt.KOTLIN_STDLIB
-import static androidx.build.dependencies.DependenciesKt.MATERIAL
import static androidx.build.dependencies.DependenciesKt.ANDROIDX_TEST_EXT_JUNIT
import static androidx.build.dependencies.DependenciesKt.ANDROIDX_TEST_RULES
@@ -37,10 +36,10 @@
dependencies {
api(KOTLIN_STDLIB)
implementation(project(":viewpager2"))
- implementation(project(":fragment:fragment-ktx"))
- implementation(MATERIAL)
- implementation(project(":coordinatorlayout"))
- implementation(project(":cardview"))
+ implementation("androidx.activity:activity-ktx:1.0.0-rc01")
+ implementation("com.google.android.material:material:1.1.0-alpha08") {
+ exclude group: 'androidx.viewpager2', module: 'viewpager2'
+ }
androidTestImplementation(ANDROIDX_TEST_RULES)
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
diff --git a/viewpager2/integration-tests/testapp/src/main/java/androidx/viewpager2/integration/testapp/CardViewTabLayoutActivity.kt b/viewpager2/integration-tests/testapp/src/main/java/androidx/viewpager2/integration/testapp/CardViewTabLayoutActivity.kt
index f04eaba..b969f25 100644
--- a/viewpager2/integration-tests/testapp/src/main/java/androidx/viewpager2/integration/testapp/CardViewTabLayoutActivity.kt
+++ b/viewpager2/integration-tests/testapp/src/main/java/androidx/viewpager2/integration/testapp/CardViewTabLayoutActivity.kt
@@ -19,6 +19,7 @@
import android.os.Bundle
import androidx.viewpager2.integration.testapp.cards.Card
import com.google.android.material.tabs.TabLayout
+import com.google.android.material.tabs.TabLayoutMediator
class CardViewTabLayoutActivity : CardViewActivity() {
diff --git a/viewpager2/integration-tests/testapp/src/main/java/androidx/viewpager2/integration/testapp/TabLayoutMediator.java b/viewpager2/integration-tests/testapp/src/main/java/androidx/viewpager2/integration/testapp/TabLayoutMediator.java
deleted file mode 100644
index 4a63e42..0000000
--- a/viewpager2/integration-tests/testapp/src/main/java/androidx/viewpager2/integration/testapp/TabLayoutMediator.java
+++ /dev/null
@@ -1,366 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.viewpager2.integration.testapp;
-
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-import static androidx.viewpager2.widget.ViewPager2.SCROLL_STATE_DRAGGING;
-import static androidx.viewpager2.widget.ViewPager2.SCROLL_STATE_IDLE;
-import static androidx.viewpager2.widget.ViewPager2.SCROLL_STATE_SETTLING;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
-import androidx.recyclerview.widget.RecyclerView;
-import androidx.viewpager2.widget.ViewPager2;
-
-import com.google.android.material.tabs.TabLayout;
-
-import java.lang.ref.WeakReference;
-import java.lang.reflect.Method;
-
-/**
- * A mediator to link a TabLayout with a ViewPager2. The mediator will synchronize the ViewPager2's
- * position with the selected tab when a tab is selected, and the TabLayout's scroll position when
- * the user drags the ViewPager2.
- *
- * Establish the link by creating an instance of this class, make sure the ViewPager2 has an adapter
- * and then call {@link #attach()} on it. When creating an instance of this class, you must supply
- * an implementation of {@link OnConfigureTabCallback} in which you set the text of the tab, and/or
- * perform any styling of the tabs that you require.
- *
- * @hide
- */
-@RestrictTo(LIBRARY_GROUP)
-public final class TabLayoutMediator {
- private final @NonNull TabLayout mTabLayout;
- private final @NonNull ViewPager2 mViewPager;
- private final boolean mAutoRefresh;
- private final OnConfigureTabCallback mOnConfigureTabCallback;
- private RecyclerView.Adapter mAdapter;
- private boolean mAttached;
-
- private TabLayoutOnPageChangeCallback mOnPageChangeCallback;
- private TabLayout.OnTabSelectedListener mOnTabSelectedListener;
- private RecyclerView.AdapterDataObserver mPagerAdapterObserver;
-
- /**
- * A callback interface that must be implemented to set the text and styling of newly created
- * tabs.
- */
- public interface OnConfigureTabCallback {
- /**
- * Called to configure the tab for the page at the specified position. Typically calls
- * {@link TabLayout.Tab#setText(CharSequence)}, but any form of styling can be applied.
- *
- * @param tab The Tab which should be configured to represent the title of the item at the
- * given position in the data set.
- * @param position The position of the item within the adapter's data set.
- */
- void onConfigureTab(@NonNull TabLayout.Tab tab, int position);
- }
-
- /**
- * Creates a TabLayoutMediator to synchronize a TabLayout and a ViewPager2 together. It will
- * update the tabs automatically when the data set of the view pager's adapter changes. The link
- * will be established after {@link #attach()} is called.
- *
- * @param tabLayout The tab bar to link
- * @param viewPager The view pager to link
- */
- public TabLayoutMediator(@NonNull TabLayout tabLayout, @NonNull ViewPager2 viewPager,
- @NonNull OnConfigureTabCallback onConfigureTabCallback) {
- this(tabLayout, viewPager, true, onConfigureTabCallback);
- }
-
- /**
- * Creates a TabLayoutMediator to synchronize a TabLayout and a ViewPager2 together. If {@code
- * autoRefresh} is true, it will update the tabs automatically when the data set of the view
- * pager's adapter changes. The link will be established after {@link #attach()} is called.
- *
- * @param tabLayout The tab bar to link
- * @param viewPager The view pager to link
- * @param autoRefresh If {@code true}, will recreate all tabs when the data set of the view
- * pager's adapter changes.
- */
- public TabLayoutMediator(@NonNull TabLayout tabLayout, @NonNull ViewPager2 viewPager,
- boolean autoRefresh, @NonNull OnConfigureTabCallback onConfigureTabCallback) {
- mTabLayout = tabLayout;
- mViewPager = viewPager;
- mAutoRefresh = autoRefresh;
- mOnConfigureTabCallback = onConfigureTabCallback;
- }
-
- /**
- * Link the TabLayout and the ViewPager2 together.
- * @throws IllegalStateException If the mediator is already attached, or the ViewPager2 has no
- * adapter.
- */
- public void attach() {
- if (mAttached) {
- throw new IllegalStateException("TabLayoutMediator is already attached");
- }
- mAdapter = mViewPager.getAdapter();
- if (mAdapter == null) {
- throw new IllegalStateException("TabLayoutMediator attached before ViewPager2 has an "
- + "adapter");
- }
- mAttached = true;
-
- // Add our custom OnPageChangeCallback to the ViewPager
- mOnPageChangeCallback = new TabLayoutOnPageChangeCallback(mTabLayout);
- mViewPager.registerOnPageChangeCallback(mOnPageChangeCallback);
-
- // Now we'll add a tab selected listener to set ViewPager's current item
- mOnTabSelectedListener = new ViewPagerOnTabSelectedListener(mViewPager);
- mTabLayout.addOnTabSelectedListener(mOnTabSelectedListener);
-
- // Now we'll populate ourselves from the pager adapter, adding an observer if
- // autoRefresh is enabled
- if (mAutoRefresh) {
- // Register our observer on the new adapter
- mPagerAdapterObserver = new PagerAdapterObserver();
- mAdapter.registerAdapterDataObserver(mPagerAdapterObserver);
- }
-
- populateTabsFromPagerAdapter();
-
- // Now update the scroll position to match the ViewPager's current item
- mTabLayout.setScrollPosition(mViewPager.getCurrentItem(), 0f, true);
- }
-
- /**
- * Unlink the TabLayout and the ViewPager
- */
- public void detach() {
- mAdapter.unregisterAdapterDataObserver(mPagerAdapterObserver);
- mTabLayout.removeOnTabSelectedListener(mOnTabSelectedListener);
- mViewPager.unregisterOnPageChangeCallback(mOnPageChangeCallback);
- mPagerAdapterObserver = null;
- mOnTabSelectedListener = null;
- mOnPageChangeCallback = null;
- mAttached = false;
- }
-
- @SuppressWarnings("WeakerAccess")
- void populateTabsFromPagerAdapter() {
- mTabLayout.removeAllTabs();
-
- if (mAdapter != null) {
- int adapterCount = mAdapter.getItemCount();
- for (int i = 0; i < adapterCount; i++) {
- TabLayout.Tab tab = mTabLayout.newTab();
- mOnConfigureTabCallback.onConfigureTab(tab, i);
- mTabLayout.addTab(tab, false);
- }
-
- // Make sure we reflect the currently set ViewPager item
- if (adapterCount > 0) {
- int currItem = mViewPager.getCurrentItem();
- if (currItem != mTabLayout.getSelectedTabPosition()) {
- mTabLayout.getTabAt(currItem).select();
- }
- }
- }
- }
-
- /**
- * A {@link ViewPager2.OnPageChangeCallback} class which contains the necessary calls back to
- * the provided {@link TabLayout} so that the tab position is kept in sync.
- *
- * <p>This class stores the provided TabLayout weakly, meaning that you can use {@link
- * ViewPager2#registerOnPageChangeCallback(ViewPager2.OnPageChangeCallback)} without removing
- * the callback and not cause a leak.
- */
- private static class TabLayoutOnPageChangeCallback extends ViewPager2.OnPageChangeCallback {
- private final WeakReference<TabLayout> mTabLayoutRef;
- private int mPreviousScrollState;
- private int mScrollState;
-
- TabLayoutOnPageChangeCallback(TabLayout tabLayout) {
- mTabLayoutRef = new WeakReference<>(tabLayout);
- reset();
- }
-
- @Override
- public void onPageScrollStateChanged(final int state) {
- mPreviousScrollState = mScrollState;
- mScrollState = state;
- }
-
- @Override
- public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
- TabLayout tabLayout = mTabLayoutRef.get();
- if (tabLayout != null) {
- // Only update the text selection if we're not settling, or we are settling after
- // being dragged
- boolean updateText = mScrollState != SCROLL_STATE_SETTLING
- || mPreviousScrollState == SCROLL_STATE_DRAGGING;
- // Update the indicator if we're not settling after being idle. This is caused
- // from a setCurrentItem() call and will be handled by an animation from
- // onPageSelected() instead.
- boolean updateIndicator = !(mScrollState == SCROLL_STATE_SETTLING
- && mPreviousScrollState == SCROLL_STATE_IDLE);
- setScrollPosition(tabLayout, position, positionOffset, updateText, updateIndicator);
- }
- }
-
- @Override
- public void onPageSelected(final int position) {
- TabLayout tabLayout = mTabLayoutRef.get();
- if (tabLayout != null
- && tabLayout.getSelectedTabPosition() != position
- && position < tabLayout.getTabCount()) {
- // Select the tab, only updating the indicator if we're not being dragged/settled
- // (since onPageScrolled will handle that).
- boolean updateIndicator = mScrollState == SCROLL_STATE_IDLE
- || (mScrollState == SCROLL_STATE_SETTLING
- && mPreviousScrollState == SCROLL_STATE_IDLE);
- selectTab(tabLayout, tabLayout.getTabAt(position), updateIndicator);
- }
- }
-
- void reset() {
- mPreviousScrollState = mScrollState = SCROLL_STATE_IDLE;
- }
- }
-
- // region Reflective calls
-
- // Temporarily call methods TabLayout.setScrollPosition(int, float, boolean, boolean) and
- // TabLayout.selectTab(TabLayout.Tab, boolean) through reflection, until they have been made
- // public in the Material Design Components library.
-
- private static Method sSetScrollPosition;
- private static Method sSelectTab;
- private static final String SET_SCROLL_POSITION_NAME = "TabLayout.setScrollPosition(int, float,"
- + " boolean, boolean)";
- private static final String SELECT_TAB_NAME = "TabLayout.selectTab(TabLayout.Tab, boolean)";
-
- static {
- try {
- sSetScrollPosition = TabLayout.class.getDeclaredMethod("setScrollPosition", int.class,
- float.class, boolean.class, boolean.class);
- sSetScrollPosition.setAccessible(true);
-
- sSelectTab = TabLayout.class.getDeclaredMethod("selectTab", TabLayout.Tab.class,
- boolean.class);
- sSelectTab.setAccessible(true);
- } catch (NoSuchMethodException e) {
- throw new IllegalStateException("Can't reflect into method TabLayout"
- + ".setScrollPosition(int, float, boolean, boolean)");
- }
- }
-
- @SuppressWarnings("WeakerAccess")
- static void setScrollPosition(TabLayout tabLayout, int position, float positionOffset,
- boolean updateSelectedText, boolean updateIndicatorPosition) {
- try {
- if (sSetScrollPosition != null) {
- sSetScrollPosition.invoke(tabLayout, position, positionOffset, updateSelectedText,
- updateIndicatorPosition);
- } else {
- throwMethodNotFound(SET_SCROLL_POSITION_NAME);
- }
- } catch (Exception e) {
- throwInvokeFailed(SET_SCROLL_POSITION_NAME);
- }
- }
-
- @SuppressWarnings("WeakerAccess")
- static void selectTab(TabLayout tabLayout, TabLayout.Tab tab, boolean updateIndicator) {
- try {
- if (sSelectTab != null) {
- sSelectTab.invoke(tabLayout, tab, updateIndicator);
- } else {
- throwMethodNotFound(SELECT_TAB_NAME);
- }
- } catch (Exception e) {
- throwInvokeFailed(SELECT_TAB_NAME);
- }
- }
-
- private static void throwMethodNotFound(String method) {
- throw new IllegalStateException("Method " + method + " not found");
- }
-
- private static void throwInvokeFailed(String method) {
- throw new IllegalStateException("Couldn't invoke method " + method);
- }
-
- // endregion
-
- /**
- * A {@link TabLayout.OnTabSelectedListener} class which contains the necessary calls back to
- * the provided {@link ViewPager2} so that the tab position is kept in sync.
- */
- private static class ViewPagerOnTabSelectedListener implements TabLayout.OnTabSelectedListener {
- private final ViewPager2 mViewPager;
-
- ViewPagerOnTabSelectedListener(ViewPager2 viewPager) {
- this.mViewPager = viewPager;
- }
-
- @Override
- public void onTabSelected(TabLayout.Tab tab) {
- mViewPager.setCurrentItem(tab.getPosition(), true);
- }
-
- @Override
- public void onTabUnselected(TabLayout.Tab tab) {
- // No-op
- }
-
- @Override
- public void onTabReselected(TabLayout.Tab tab) {
- // No-op
- }
- }
-
- private class PagerAdapterObserver extends RecyclerView.AdapterDataObserver {
- PagerAdapterObserver() {}
-
- @Override
- public void onChanged() {
- populateTabsFromPagerAdapter();
- }
-
- @Override
- public void onItemRangeChanged(int positionStart, int itemCount) {
- populateTabsFromPagerAdapter();
- }
-
- @Override
- public void onItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload) {
- populateTabsFromPagerAdapter();
- }
-
- @Override
- public void onItemRangeInserted(int positionStart, int itemCount) {
- populateTabsFromPagerAdapter();
- }
-
- @Override
- public void onItemRangeRemoved(int positionStart, int itemCount) {
- populateTabsFromPagerAdapter();
- }
-
- @Override
- public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
- populateTabsFromPagerAdapter();
- }
- }
-}
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/test/ui/SparseAdapter.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/test/ui/SparseAdapter.kt
new file mode 100644
index 0000000..1b770d3
--- /dev/null
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/test/ui/SparseAdapter.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.viewpager2.test.ui
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import androidx.viewpager2.test.R
+
+class SparseAdapter(private val size: Int) : RecyclerView.Adapter<SparseAdapter.TextViewHolder>() {
+ override fun getItemCount(): Int = size
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TextViewHolder {
+ val inflater = LayoutInflater.from(parent.context)
+ val view = inflater.inflate(R.layout.item_test_layout, parent, false)
+ return TextViewHolder(view as TextView)
+ }
+
+ override fun onBindViewHolder(holder: TextViewHolder, position: Int) {
+ holder.textView.text = "$position"
+ }
+
+ class TextViewHolder(val textView: TextView) : RecyclerView.ViewHolder(textView)
+}
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/test/ui/TouchConsumingTextView.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/test/ui/TouchConsumingTextView.kt
index 0061566..3372525a 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/test/ui/TouchConsumingTextView.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/test/ui/TouchConsumingTextView.kt
@@ -21,11 +21,11 @@
import android.view.MotionEvent
import androidx.appcompat.widget.AppCompatTextView
-class TouchConsumingTextView(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) :
+class TouchConsumingTextView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) :
AppCompatTextView(context, attrs, defStyleAttr) {
- constructor(context: Context?) : this(context, null, 0)
- constructor(context: Context?, attrs: AttributeSet?) : this(context, attrs, 0)
+ constructor(context: Context) : this(context, null, 0)
+ constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
var consumeTouches = false
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FakeDragTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FakeDragTest.kt
index 7d6e3a3..874da2c 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FakeDragTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FakeDragTest.kt
@@ -191,7 +191,7 @@
val tracker = PositionTracker().also { test.viewPager.registerOnPageChangeCallback(it) }
val targetPage = test.viewPager.currentItem + 1
startFakeDragWhileSettling(targetPage,
- { targetPage - tracker.lastPosition }, targetPage, true)
+ { (targetPage - tracker.lastPosition).toFloat() }, targetPage, true)
test.viewPager.unregisterOnPageChangeCallback(tracker)
}
}
@@ -213,7 +213,7 @@
val targetPage = test.viewPager.currentItem + 1
val nextPage = targetPage + 1
startFakeDragWhileSettling(targetPage,
- { nextPage - tracker.lastPosition }, nextPage, true)
+ { (nextPage - tracker.lastPosition).toFloat() }, nextPage, true)
test.viewPager.unregisterOnPageChangeCallback(tracker)
}
}
@@ -425,11 +425,11 @@
"state already left SETTLING")
}
// Check 2: setCurrentItem should not have finished
- if (settleTarget - currPosition <= 0f) {
+ if (settleTarget - currPosition <= 0) {
throw RetryException("Interruption of SETTLING too late: already at target")
}
// Check 3: fake drag should not overshoot its target
- if (expectedFinalPage - currPosition < dragDistance) {
+ if ((expectedFinalPage - currPosition).toFloat() < dragDistance) {
throw RetryException("Interruption of SETTLING too late: already closer than " +
"$dragDistance from target")
}
@@ -556,10 +556,10 @@
}
}
- private val ViewPager2.relativeScrollPosition: Float
+ private val ViewPager2.relativeScrollPosition: Double
get() {
val scrollEventAdapter = mScrollEventAdapterField.get(this)
- return getRelativeScrollPositionMethod.invoke(scrollEventAdapter) as Float
+ return getRelativeScrollPositionMethod.invoke(scrollEventAdapter) as Double
}
private fun ViewPager2.addNewRecordingCallback(): RecordingCallback {
@@ -687,16 +687,16 @@
otherPage: Int,
pageSize: Int
) = forEach {
- assertThat(it.position + it.positionOffset,
- isBetweenInInMinMax(initialPage.toFloat(), otherPage.toFloat()))
+ assertThat(it.position + it.positionOffset.toDouble(),
+ isBetweenInInMinMax(initialPage.toDouble(), otherPage.toDouble()))
assertThat(it.positionOffset, isBetweenInEx(0f, 1f))
assertThat((it.positionOffset * pageSize).roundToInt(), equalTo(it.positionOffsetPixels))
}
private class PositionTracker : ViewPager2.OnPageChangeCallback() {
- var lastPosition = 0f
+ var lastPosition = 0.0
override fun onPageScrolled(position: Int, offset: Float, offsetPx: Int) {
- lastPosition = position + offset
+ lastPosition = position + offset.toDouble()
}
}
}
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/OffscreenPageLimitTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/OffscreenPageLimitTest.kt
index 8fae152..18a3c27 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/OffscreenPageLimitTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/OffscreenPageLimitTest.kt
@@ -117,12 +117,12 @@
is OnChildViewRemoved -> assertThat(onscreen.remove(event.position), equalTo(true))
// When VP2 scrolls, check if the set of onscreen pages is the expected value
is OnPageScrolledEvent -> {
- val position = event.position + event.positionOffset
+ val position = event.position + event.positionOffset.toDouble()
val lower = max(0, floor(position - limit).roundToInt())
val upper = min(pageCount - 1, ceil(position + limit).roundToInt())
// First verify this calculation:
- assertThat(lower.toFloat(), lessThanOrEqualTo(position))
- assertThat(upper.toFloat(), greaterThanOrEqualTo(position))
+ assertThat(lower.toDouble(), lessThanOrEqualTo(position))
+ assertThat(upper.toDouble(), greaterThanOrEqualTo(position))
// Then verify the onscreen pages:
assertThat("There should be ${upper - lower + 1} pages laid out at event $i. " +
"Events: ${recorder.dumpEvents()}",
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageChangeCallbackTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageChangeCallbackTest.kt
index f257a8e2..81b6edd 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageChangeCallbackTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageChangeCallbackTest.kt
@@ -28,6 +28,7 @@
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
import androidx.viewpager2.widget.PageChangeCallbackTest.Event.MarkerEvent
import androidx.viewpager2.widget.PageChangeCallbackTest.Event.OnPageScrollStateChangedEvent
@@ -989,6 +990,48 @@
// no crash
}
+ @Test
+ fun test_setCurrentItem_maxIntItems() {
+ val test = setUpTest(config.orientation)
+ test.setAdapterSync { SparseAdapter(Int.MAX_VALUE) }
+ test.assertBasicState(0)
+
+ val testPages = listOf(1073742144, (1 shl 25) + 2, Int.MAX_VALUE - 2)
+
+ val recorder = test.viewPager.addNewRecordingCallback()
+ testPages.forEach { targetPage ->
+ test.viewPager.setCurrentItemSync(targetPage, false, 2, SECONDS)
+ test.assertBasicState(targetPage)
+ test.viewPager.setCurrentItemSync(targetPage + 1, true, 2, SECONDS)
+ test.assertBasicState(targetPage + 1)
+ }
+
+ recorder.assertScrollsAreBetweenSelectedPages()
+ recorder.assertAllPagesSelected(testPages.flatMap { listOf(it, it + 1) })
+ }
+
+ @Test
+ fun test_setCurrentItemWhileScrolling_maxIntItems() {
+ val test = setUpTest(config.orientation)
+ test.setAdapterSync { SparseAdapter(Int.MAX_VALUE) }
+ test.assertBasicState(0)
+
+ val targetPage = 1073742144
+
+ val recorder = test.viewPager.addNewRecordingCallback()
+ val distanceLatch = test.viewPager.addWaitForDistanceToTarget(targetPage, 1.5f)
+ test.runOnUiThread {
+ test.viewPager.setCurrentItem(targetPage, true)
+ }
+
+ distanceLatch.await(2, SECONDS)
+ test.viewPager.setCurrentItemSync(targetPage + 1, true, 2, SECONDS)
+ test.assertBasicState(targetPage + 1)
+
+ recorder.assertScrollsAreBetweenSelectedPages()
+ recorder.assertAllPagesSelected(listOf(targetPage, targetPage + 1))
+ }
+
private fun ViewPager2.addNewRecordingCallback(): RecordingCallback {
return RecordingCallback().also { registerOnPageChangeCallback(it) }
}
@@ -1084,15 +1127,15 @@
private fun RecordingCallback.assertScrollsAreBetweenSelectedPages() {
var selectedPage = -1
- var prevScrollPosition = 0f
+ var prevScrollPosition = 0.0
scrollAndSelectEvents.forEach { event ->
when (event) {
is OnPageSelectedEvent -> selectedPage = event.position
is OnPageScrolledEvent -> {
assertThat(selectedPage, not(equalTo(-1)))
- val currScrollPosition = event.position + event.positionOffset
+ val currScrollPosition = event.position + event.positionOffset.toDouble()
assertThat(currScrollPosition,
- isBetweenInIn(prevScrollPosition, selectedPage.toFloat()))
+ isBetweenInInMinMax(prevScrollPosition, selectedPage.toDouble()))
prevScrollPosition = currScrollPosition
}
}
@@ -1141,7 +1184,7 @@
}
private fun List<OnPageScrolledEvent>.assertOffsetSorted(sortOrder: SortOrder) {
- map { it.position + it.positionOffset }.assertSorted { it * sortOrder.sign }
+ map { it.position + it.positionOffset.toDouble() }.assertSorted { it * sortOrder.sign }
}
private fun List<OnPageScrolledEvent>.assertMaxShownPages() {
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageTransformerTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageTransformerTest.kt
index 18837c4..5981d6b 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageTransformerTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageTransformerTest.kt
@@ -17,7 +17,6 @@
package androidx.viewpager2.widget
import android.view.View
-import androidx.annotation.FloatRange
import androidx.annotation.NonNull
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.test.filters.LargeTest
@@ -234,10 +233,7 @@
/* interface implementations */
- override fun transformPage(
- @NonNull page: View,
- @FloatRange(from = -1.0, to = 1.0) position: Float
- ) {
+ override fun transformPage(@NonNull page: View, position: Float) {
events.add(TransformPageEvent(layoutManager.getPosition(page), position))
}
@@ -270,7 +266,7 @@
map { it as TransformPageEvent }.forEach {
assertThat("transformPage() call must be snapped at page $snappedPage",
// event.page - event.offset resolves to the currently visible page index
- it.page - it.offset, equalTo(snappedPage.toFloat())
+ it.page - it.offset.toDouble(), equalTo(snappedPage.toDouble())
)
}
}
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/SetItemWhileScrollInProgressTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/SetItemWhileScrollInProgressTest.kt
index 5aa3356..3f4e84f 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/SetItemWhileScrollInProgressTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/SetItemWhileScrollInProgressTest.kt
@@ -247,19 +247,19 @@
private fun RecordingCallback.assertScrollTowardsSelectedPage() {
var target = 0
- var prevPosition = 0f
+ var prevPosition = 0.0
events.forEach {
when (it) {
is OnPageSelectedEvent -> target = it.position
- is Event.OnPageScrolledEvent -> {
- val currentPosition = it.position + it.positionOffset
+ is OnPageScrolledEvent -> {
+ val currentPosition = it.position + it.positionOffset.toDouble()
assertThat(
"Scroll event fired before page selected event",
target, not(equalTo(-1))
)
assertThat(
"Scroll event not between start and destination",
- currentPosition, isBetweenInInMinMax(prevPosition, target.toFloat())
+ currentPosition, isBetweenInInMinMax(prevPosition, target.toDouble())
)
prevPosition = currentPosition
}
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..4a3337c
--- /dev/null
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/TransientStateFragmentTest.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.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.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
+ 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/main/java/androidx/viewpager2/adapter/FragmentStateAdapter.java b/viewpager2/src/main/java/androidx/viewpager2/adapter/FragmentStateAdapter.java
index 435762c..a07954e 100644
--- a/viewpager2/src/main/java/androidx/viewpager2/adapter/FragmentStateAdapter.java
+++ b/viewpager2/src/main/java/androidx/viewpager2/adapter/FragmentStateAdapter.java
@@ -395,12 +395,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 620ed4a..f5e9736 100644
--- a/viewpager2/src/main/java/androidx/viewpager2/widget/ScrollEventAdapter.java
+++ b/viewpager2/src/main/java/androidx/viewpager2/widget/ScrollEventAdapter.java
@@ -379,9 +379,9 @@
*
* @return The current scroll position of the ViewPager, relative to its width
*/
- float getRelativeScrollPosition() {
+ double getRelativeScrollPosition() {
updateScrollEventValues();
- return mScrollValues.mPosition + mScrollValues.mOffset;
+ return mScrollValues.mPosition + (double) mScrollValues.mOffset;
}
private void dispatchStateChanged(@ScrollState int state) {
diff --git a/viewpager2/src/main/java/androidx/viewpager2/widget/ViewPager2.java b/viewpager2/src/main/java/androidx/viewpager2/widget/ViewPager2.java
index 5f431bd..5b9f51e 100644
--- a/viewpager2/src/main/java/androidx/viewpager2/widget/ViewPager2.java
+++ b/viewpager2/src/main/java/androidx/viewpager2/widget/ViewPager2.java
@@ -631,7 +631,7 @@
// 2. Update the item internally
- float previousItem = mCurrentItem;
+ double previousItem = mCurrentItem;
mCurrentItem = item;
mAccessibilityProvider.onSetNewCurrentItem();
@@ -895,9 +895,9 @@
if (mPageTransformerAdapter.getPageTransformer() == null) {
return;
}
- float relativePosition = mScrollEventAdapter.getRelativeScrollPosition();
+ double relativePosition = mScrollEventAdapter.getRelativeScrollPosition();
int position = (int) relativePosition;
- float positionOffset = relativePosition - position;
+ float positionOffset = (float) (relativePosition - position);
int positionOffsetPx = Math.round(getPageSize() * positionOffset);
mPageTransformerAdapter.onPageScrolled(position, positionOffset, positionOffsetPx);
}
diff --git a/wear/api/1.1.0-alpha01.ignore b/wear/api/1.1.0-alpha01.ignore
deleted file mode 100644
index 6d5b030..0000000
--- a/wear/api/1.1.0-alpha01.ignore
+++ /dev/null
@@ -1,3 +0,0 @@
-// Baseline format: 1.0
-HiddenSuperclass: androidx.wear.widget.SwipeDismissFrameLayout:
- Public class androidx.wear.widget.SwipeDismissFrameLayout stripped of unavailable superclass androidx.wear.widget.SwipeDismissLayout
diff --git a/webkit/api/1.1.0-alpha02.txt b/webkit/api/1.1.0-alpha02.txt
index 6ef8368..7242db2 100644
--- a/webkit/api/1.1.0-alpha02.txt
+++ b/webkit/api/1.1.0-alpha02.txt
@@ -1,8 +1,9 @@
// Signature format: 3.0
package androidx.webkit {
- public class ProxyConfig {
- field public static final String DIRECT = "direct://";
+ public final class ProxyConfig {
+ method public java.util.List<java.lang.String!> getBypassRules();
+ method public java.util.List<androidx.core.util.Pair<java.lang.String!,java.lang.String!>!> getProxyRules();
field public static final String MATCH_ALL_SCHEMES = "*";
field public static final String MATCH_HTTP = "http";
field public static final String MATCH_HTTPS = "https";
@@ -12,11 +13,13 @@
ctor public ProxyConfig.Builder();
ctor public ProxyConfig.Builder(androidx.webkit.ProxyConfig);
method public androidx.webkit.ProxyConfig.Builder addBypassRule(String);
+ method public androidx.webkit.ProxyConfig.Builder addDirect(String);
+ method public androidx.webkit.ProxyConfig.Builder addDirect();
method public androidx.webkit.ProxyConfig.Builder addProxyRule(String);
method public androidx.webkit.ProxyConfig.Builder addProxyRule(String, String);
method public androidx.webkit.ProxyConfig build();
method public androidx.webkit.ProxyConfig.Builder bypassSimpleHostnames();
- method public androidx.webkit.ProxyConfig.Builder subtractImplicitRules();
+ method public androidx.webkit.ProxyConfig.Builder removeImplicitRules();
}
public abstract class ProxyController {
diff --git a/webkit/api/current.txt b/webkit/api/current.txt
index 6ef8368..7242db2 100644
--- a/webkit/api/current.txt
+++ b/webkit/api/current.txt
@@ -1,8 +1,9 @@
// Signature format: 3.0
package androidx.webkit {
- public class ProxyConfig {
- field public static final String DIRECT = "direct://";
+ public final class ProxyConfig {
+ method public java.util.List<java.lang.String!> getBypassRules();
+ method public java.util.List<androidx.core.util.Pair<java.lang.String!,java.lang.String!>!> getProxyRules();
field public static final String MATCH_ALL_SCHEMES = "*";
field public static final String MATCH_HTTP = "http";
field public static final String MATCH_HTTPS = "https";
@@ -12,11 +13,13 @@
ctor public ProxyConfig.Builder();
ctor public ProxyConfig.Builder(androidx.webkit.ProxyConfig);
method public androidx.webkit.ProxyConfig.Builder addBypassRule(String);
+ method public androidx.webkit.ProxyConfig.Builder addDirect(String);
+ method public androidx.webkit.ProxyConfig.Builder addDirect();
method public androidx.webkit.ProxyConfig.Builder addProxyRule(String);
method public androidx.webkit.ProxyConfig.Builder addProxyRule(String, String);
method public androidx.webkit.ProxyConfig build();
method public androidx.webkit.ProxyConfig.Builder bypassSimpleHostnames();
- method public androidx.webkit.ProxyConfig.Builder subtractImplicitRules();
+ method public androidx.webkit.ProxyConfig.Builder removeImplicitRules();
}
public abstract class ProxyController {
diff --git a/webkit/build.gradle b/webkit/build.gradle
index 8c0c18d..622bf29 100644
--- a/webkit/build.gradle
+++ b/webkit/build.gradle
@@ -26,7 +26,7 @@
dependencies {
api("androidx.annotation:annotation:1.1.0")
- api("androidx.core:core:1.1.0-rc01")
+ api("androidx.core:core:1.1.0")
androidTestImplementation(OKHTTP_MOCKWEBSERVER)
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
diff --git a/webkit/src/androidTest/java/androidx/webkit/ProxyControllerTest.java b/webkit/src/androidTest/java/androidx/webkit/ProxyControllerTest.java
index 994f0ae..e96423ef 100644
--- a/webkit/src/androidTest/java/androidx/webkit/ProxyControllerTest.java
+++ b/webkit/src/androidTest/java/androidx/webkit/ProxyControllerTest.java
@@ -104,7 +104,7 @@
// Localhost should use proxy with loopback rule
setProxyOverrideSync(new ProxyConfig.Builder()
.addProxyRule(proxyUrl)
- .subtractImplicitRules()
+ .removeImplicitRules()
.build());
mWebViewOnUiThread.loadUrl(contentUrl);
assertNotNull(mProxyServer.takeRequest(WebkitUtils.TEST_TIMEOUT_MS,
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/ProxyConfig.java b/webkit/src/main/java/androidx/webkit/ProxyConfig.java
index 4491f3c..74adb9c 100644
--- a/webkit/src/main/java/androidx/webkit/ProxyConfig.java
+++ b/webkit/src/main/java/androidx/webkit/ProxyConfig.java
@@ -19,6 +19,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
import androidx.annotation.StringDef;
+import androidx.core.util.Pair;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -39,15 +40,11 @@
* <pre class="prettyprint">
* ProxyConfig proxyConfig = new ProxyConfig.Builder().addProxyRule("proxy1.com")
* .addProxyRule("proxy2.com")
- * .addProxyRule(ProxyConfig.DIRECT)
+ * .addDirect()
* .build();
* </pre>
*/
-public class ProxyConfig {
- /**
- * Connect to URLs directly instead of using a proxy server.
- */
- public static final String DIRECT = "direct://";
+public final class ProxyConfig {
/**
* HTTP scheme.
*/
@@ -65,36 +62,46 @@
@StringDef({MATCH_HTTP, MATCH_HTTPS, MATCH_ALL_SCHEMES})
@Retention(RetentionPolicy.SOURCE)
public @interface ProxyScheme {}
+ private static final String DIRECT = "direct://";
private static final String BYPASS_RULE_SIMPLE_NAMES = "<local>";
- private static final String BYPASS_RULE_SUBTRACT_IMPLICIT = "<-loopback>";
+ private static final String BYPASS_RULE_REMOVE_IMPLICIT = "<-loopback>";
- private List<String[]> mProxyRules;
+ private List<Pair<String, String>> mProxyRules;
private List<String> mBypassRules;
/**
* @hide Internal use only
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
- public ProxyConfig(List<String[]> proxyRules, List<String> bypassRules) {
+ public ProxyConfig(List<Pair<String, String>> proxyRules, List<String> bypassRules) {
mProxyRules = proxyRules;
mBypassRules = bypassRules;
}
/**
- * @hide Internal use only
+ * Returns the current list of proxy rules. Each pair of (String, String) consists of the proxy
+ * URL and the URL schemes for which this proxy is used (one of {@code MATCH_HTTP},
+ * {@code MATCH_HTTPS}, {@code MATCH_ALL_SCHEMES}).
+ *
+ * <p>To add new rules use {@link Builder#addProxyRule(String)} or
+ * {@link Builder#addProxyRule(String, String)}.
+ *
+ * @return List of proxy rules
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
@NonNull
- public List<String[]> proxyRules() {
+ public List<Pair<String, String>> getProxyRules() {
return mProxyRules;
}
/**
- * @hide Internal use only
+ * Returns the current list that holds the bypass rules represented by this object.
+ *
+ * <p>To add new rules use {@link Builder#addBypassRule(String)}.
+ *
+ * @return List of bypass rules
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
@NonNull
- public List<String> bypassRules() {
+ public List<String> getBypassRules() {
return mBypassRules;
}
@@ -108,7 +115,7 @@
* connections to be made directly.
*/
public static final class Builder {
- private List<String[]> mProxyRules;
+ private List<Pair<String, String>> mProxyRules;
private List<String> mBypassRules;
/**
@@ -123,12 +130,14 @@
* Create a ProxyConfig Builder from an existing ProxyConfig object.
*/
public Builder(@NonNull ProxyConfig proxyConfig) {
- mProxyRules = proxyConfig.proxyRules();
- mBypassRules = proxyConfig.bypassRules();
+ mProxyRules = proxyConfig.getProxyRules();
+ mBypassRules = proxyConfig.getBypassRules();
}
/**
* Builds the current rules into a ProxyConfig object.
+ *
+ * @return The ProxyConfig object represented by this Builder
*/
@NonNull
public ProxyConfig build() {
@@ -136,12 +145,13 @@
}
/**
- * Adds a proxy to be used for all URLs.
- * <p>Proxy is either {@link ProxyConfig#DIRECT} or a string in the format
- * {@code [scheme://]host[:port]}. Scheme is optional, if present must be {@code HTTP},
- * {@code HTTPS} or <a href="https://tools.ietf.org/html/rfc1928">SOCKS</a> and defaults to
- * {@code HTTP}. Host is one of an IPv6 literal with brackets, an IPv4 literal or one or
- * more labels separated by a period. Port number is optional and defaults to {@code 80} for
+ * Adds a proxy to be used for all URLs. This method can be called multiple times to add
+ * multiple rules. Additional rules have decreasing precedence.
+ * <p>Proxy is a string in the format {@code [scheme://]host[:port]}. Scheme is optional, if
+ * present must be {@code HTTP}, {@code HTTPS} or
+ * <a href="https://tools.ietf.org/html/rfc1928">SOCKS</a> and defaults to {@code HTTP}.
+ * Host is one of an IPv6 literal with brackets, an IPv4 literal or one or more labels
+ * separated by a period. Port number is optional and defaults to {@code 80} for
* {@code HTTP}, {@code 443} for {@code HTTPS} and {@code 1080} for {@code SOCKS}.
* <p>
* The correct syntax for hosts is defined by
@@ -161,6 +171,7 @@
* </table>
*
* @param proxyUrl Proxy URL
+ * @return This Builder object
*/
@NonNull
public Builder addProxyRule(@NonNull String proxyUrl) {
@@ -175,23 +186,24 @@
*
* @param proxyUrl Proxy URL
* @param schemeFilter Scheme filter
+ * @return This Builder object
*/
@NonNull
public Builder addProxyRule(@NonNull String proxyUrl,
@NonNull @ProxyScheme String schemeFilter) {
- String[] rule = {schemeFilter, proxyUrl};
- mProxyRules.add(rule);
+ mProxyRules.add(new Pair<>(schemeFilter, proxyUrl));
return this;
}
/**
* Adds a new bypass rule that describes URLs that should skip proxy override settings
- * and make a direct connection instead. Wildcards are accepted. For instance, the rule
- * {@code "*example.com"} would mean that requests to {@code "http://example.com"} and
- * {@code "www.example.com"} would not be directed to any proxy, instead, would be made
- * directly to the origin specified by the URL.
+ * and make a direct connection instead. These can be URLs or IP addresses. Wildcards are
+ * accepted. For instance, the rule {@code "*example.com"} would mean that requests to
+ * {@code "http://example.com"} and {@code "www.example.com"} would not be directed to any
+ * proxy, instead, would be made directly to the origin specified by the URL.
*
* @param bypassRule Rule to be added to the exclusion list
+ * @return This Builder object
*/
@NonNull
public Builder addBypassRule(@NonNull String bypassRule) {
@@ -200,11 +212,36 @@
}
/**
+ * Adds a proxy rule so URLs that match the scheme filter are connected to directly instead
+ * of using a proxy server.
+ *
+ * @param schemeFilter Scheme filter
+ * @return This Builder object
+ */
+ @NonNull
+ public Builder addDirect(@NonNull @ProxyScheme String schemeFilter) {
+ mProxyRules.add(new Pair<>(DIRECT, schemeFilter));
+ return this;
+ }
+
+ /**
+ * Adds a proxy rule so URLs are connected to directly instead of using a proxy server.
+ *
+ * @return This Builder object
+ */
+ @NonNull
+ public Builder addDirect() {
+ return addDirect(MATCH_ALL_SCHEMES);
+ }
+
+ /**
* Hostnames without a period in them (and that are not IP literals) will skip proxy
* settings and be connected to directly instead. Examples: {@code "abc"}, {@code "local"},
* {@code "some-domain"}.
* <p>
* Hostnames with a trailing dot are not considered simple by this definition.
+ *
+ * @return This Builder object
*/
@NonNull
public Builder bypassSimpleHostnames() {
@@ -225,14 +262,16 @@
* <p>
* Call this function to override the default behavior and force localhost and link-local
* URLs to be sent through the proxy.
+ *
+ * @return This Builder object
*/
@NonNull
- public Builder subtractImplicitRules() {
- return addBypassRule(BYPASS_RULE_SUBTRACT_IMPLICIT);
+ public Builder removeImplicitRules() {
+ return addBypassRule(BYPASS_RULE_REMOVE_IMPLICIT);
}
@NonNull
- private List<String[]> proxyRules() {
+ private List<Pair<String, String>> proxyRules() {
return mProxyRules;
}
diff --git a/webkit/src/main/java/androidx/webkit/WebViewAssetLoader.java b/webkit/src/main/java/androidx/webkit/WebViewAssetLoader.java
index fa064fc..b34d682 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_PREFIX)
+ 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/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/webkit/src/main/java/androidx/webkit/internal/ProxyControllerImpl.java b/webkit/src/main/java/androidx/webkit/internal/ProxyControllerImpl.java
index 1b61f33..c2b4675 100644
--- a/webkit/src/main/java/androidx/webkit/internal/ProxyControllerImpl.java
+++ b/webkit/src/main/java/androidx/webkit/internal/ProxyControllerImpl.java
@@ -17,12 +17,14 @@
package androidx.webkit.internal;
import androidx.annotation.NonNull;
+import androidx.core.util.Pair;
import androidx.webkit.ProxyConfig;
import androidx.webkit.ProxyController;
import androidx.webkit.WebViewFeature;
import org.chromium.support_lib_boundary.ProxyControllerBoundaryInterface;
+import java.util.List;
import java.util.concurrent.Executor;
/**
@@ -37,9 +39,16 @@
WebViewFeatureInternal webViewFeature =
WebViewFeatureInternal.getFeature(WebViewFeature.PROXY_OVERRIDE);
if (webViewFeature.isSupportedByWebView()) {
- getBoundaryInterface().setProxyOverride(
- proxyConfig.proxyRules().toArray(new String[0][]),
- proxyConfig.bypassRules().toArray(new String[0]), listener, executor);
+ List<Pair<String, String>> proxyRulesList = proxyConfig.getProxyRules();
+
+ // A 2D String array representation is required by reflection
+ String[][] proxyRulesArray = new String[proxyRulesList.size()][2];
+ for (int i = 0; i < proxyRulesList.size(); i++) {
+ proxyRulesArray[i][0] = proxyRulesList.get(0).first;
+ proxyRulesArray[i][1] = proxyRulesList.get(0).second;
+ }
+ getBoundaryInterface().setProxyOverride(proxyRulesArray,
+ proxyConfig.getBypassRules().toArray(new String[0]), listener, executor);
} else {
throw WebViewFeatureInternal.getUnsupportedOperationException();
}
diff --git a/work/workmanager-gcm/src/androidTest/java/androidx/work/impl/background/gcm/GcmTaskConverterTest.kt b/work/workmanager-gcm/src/androidTest/java/androidx/work/impl/background/gcm/GcmTaskConverterTest.kt
index 547c3ac..89a0e4d 100644
--- a/work/workmanager-gcm/src/androidTest/java/androidx/work/impl/background/gcm/GcmTaskConverterTest.kt
+++ b/work/workmanager-gcm/src/androidTest/java/androidx/work/impl/background/gcm/GcmTaskConverterTest.kt
@@ -58,16 +58,17 @@
val expected = request.workSpec.calculateNextRunTime()
val offset = offset(expected, now)
- val delta = task.windowEnd - (offset + EXECUTION_WINDOW_SIZE_IN_SECONDS)
+ val deltaStart = task.windowStart - offset
+ val deltaEnd = task.windowEnd - (offset + EXECUTION_WINDOW_SIZE_IN_SECONDS)
assertEquals(task.serviceName, WorkManagerGcmService::class.java.name)
assertEquals(task.isPersisted, false)
assertEquals(task.isUpdateCurrent, true)
assertEquals(task.requiredNetwork, Task.NETWORK_STATE_ANY)
assertEquals(task.requiresCharging, false)
- assertEquals(task.windowStart, offset)
// Account for time unit quantization errors
- assertThat(delta, lessThanOrEqualTo(1L))
+ assertThat(deltaStart, lessThanOrEqualTo(1L))
+ assertThat(deltaEnd, lessThanOrEqualTo(1L))
}
@Test
@@ -87,16 +88,17 @@
val task = mTaskConverter.convert(request.workSpec)
val expected = request.workSpec.calculateNextRunTime()
val offset = offset(expected, now)
- val delta = task.windowEnd - (offset + EXECUTION_WINDOW_SIZE_IN_SECONDS)
+ val deltaStart = task.windowStart - offset
+ val deltaEnd = task.windowEnd - (offset + EXECUTION_WINDOW_SIZE_IN_SECONDS)
assertEquals(task.serviceName, WorkManagerGcmService::class.java.name)
assertEquals(task.isPersisted, false)
assertEquals(task.isUpdateCurrent, true)
assertEquals(task.requiredNetwork, Task.NETWORK_STATE_CONNECTED)
assertEquals(task.requiresCharging, true)
- assertEquals(task.windowStart, offset)
// Account for time unit quantization errors
- assertThat(delta, lessThanOrEqualTo(1L))
+ assertThat(deltaStart, lessThanOrEqualTo(1L))
+ assertThat(deltaEnd, lessThanOrEqualTo(1L))
}
@Test
@@ -115,16 +117,17 @@
val task = mTaskConverter.convert(request.workSpec)
val expected = request.workSpec.calculateNextRunTime()
val offset = offset(expected, now)
- val delta = task.windowEnd - (offset + EXECUTION_WINDOW_SIZE_IN_SECONDS)
+ val deltaStart = task.windowStart - offset
+ val deltaEnd = task.windowEnd - (offset + EXECUTION_WINDOW_SIZE_IN_SECONDS)
assertEquals(task.serviceName, WorkManagerGcmService::class.java.name)
assertEquals(task.isPersisted, false)
assertEquals(task.isUpdateCurrent, true)
assertEquals(task.requiredNetwork, Task.NETWORK_STATE_UNMETERED)
assertEquals(task.requiresCharging, false)
- assertEquals(task.windowStart, offset)
// Account for time unit quantization errors
- assertThat(delta, lessThanOrEqualTo(1L))
+ assertThat(deltaStart, lessThanOrEqualTo(1L))
+ assertThat(deltaEnd, lessThanOrEqualTo(1L))
}
@Test
@@ -140,16 +143,17 @@
val task = mTaskConverter.convert(request.workSpec)
val expected = request.workSpec.calculateNextRunTime()
val offset = offset(expected, now)
- val delta = task.windowEnd - (offset + EXECUTION_WINDOW_SIZE_IN_SECONDS)
+ val deltaStart = task.windowStart - offset
+ val deltaEnd = task.windowEnd - (offset + EXECUTION_WINDOW_SIZE_IN_SECONDS)
assertEquals(task.serviceName, WorkManagerGcmService::class.java.name)
assertEquals(task.isPersisted, false)
assertEquals(task.isUpdateCurrent, true)
assertEquals(task.requiredNetwork, Task.NETWORK_STATE_ANY)
assertEquals(task.requiresCharging, false)
- assertEquals(task.windowStart, offset)
// Account for time unit quantization errors
- assertThat(delta, lessThanOrEqualTo(1L))
+ assertThat(deltaStart, lessThanOrEqualTo(1L))
+ assertThat(deltaEnd, lessThanOrEqualTo(1L))
}
@Test
@@ -165,16 +169,17 @@
val task = mTaskConverter.convert(request.workSpec)
val expected = workSpec.calculateNextRunTime()
val offset = offset(expected, now)
- val delta = task.windowEnd - (offset + EXECUTION_WINDOW_SIZE_IN_SECONDS)
+ val deltaStart = task.windowStart - offset
+ val deltaEnd = task.windowEnd - (offset + EXECUTION_WINDOW_SIZE_IN_SECONDS)
assertEquals(task.serviceName, WorkManagerGcmService::class.java.name)
assertEquals(task.isPersisted, false)
assertEquals(task.isUpdateCurrent, true)
assertEquals(task.requiredNetwork, Task.NETWORK_STATE_ANY)
assertEquals(task.requiresCharging, false)
- assertEquals(task.windowStart, offset)
// Account for time unit quantization errors
- assertThat(delta, lessThanOrEqualTo(1L))
+ assertThat(deltaStart, lessThanOrEqualTo(1L))
+ assertThat(deltaEnd, lessThanOrEqualTo(1L))
}
@Test
@@ -189,16 +194,17 @@
val task = mTaskConverter.convert(request.workSpec)
val expected = request.workSpec.calculateNextRunTime()
val offset = offset(expected, now)
- val delta = task.windowEnd - (offset + EXECUTION_WINDOW_SIZE_IN_SECONDS)
+ val deltaStart = task.windowStart - offset
+ val deltaEnd = task.windowEnd - (offset + EXECUTION_WINDOW_SIZE_IN_SECONDS)
assertEquals(task.serviceName, WorkManagerGcmService::class.java.name)
assertEquals(task.isPersisted, false)
assertEquals(task.isUpdateCurrent, true)
assertEquals(task.requiredNetwork, Task.NETWORK_STATE_ANY)
assertEquals(task.requiresCharging, false)
- assertEquals(task.windowStart, offset)
// Account for time unit quantization errors
- assertThat(delta, lessThanOrEqualTo(1L))
+ assertThat(deltaStart, lessThanOrEqualTo(1L))
+ assertThat(deltaEnd, lessThanOrEqualTo(1L))
}
@Test
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/api/2.3.0-alpha01.txt b/work/workmanager-ktx/api/2.3.0-alpha01.txt
index 4ed72f8..ddfb009 100644
--- a/work/workmanager-ktx/api/2.3.0-alpha01.txt
+++ b/work/workmanager-ktx/api/2.3.0-alpha01.txt
@@ -6,6 +6,7 @@
method public abstract suspend Object doWork(kotlin.coroutines.Continuation<? super androidx.work.ListenableWorker.Result> p);
method @Deprecated public kotlinx.coroutines.CoroutineDispatcher getCoroutineContext();
method public final void onStopped();
+ method public final suspend Object! setProgress(androidx.work.Data data, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
method public final com.google.common.util.concurrent.ListenableFuture<androidx.work.ListenableWorker.Result> startWork();
property @Deprecated public kotlinx.coroutines.CoroutineDispatcher coroutineContext;
}
diff --git a/work/workmanager-ktx/api/current.txt b/work/workmanager-ktx/api/current.txt
index 4ed72f8..ddfb009 100644
--- a/work/workmanager-ktx/api/current.txt
+++ b/work/workmanager-ktx/api/current.txt
@@ -6,6 +6,7 @@
method public abstract suspend Object doWork(kotlin.coroutines.Continuation<? super androidx.work.ListenableWorker.Result> p);
method @Deprecated public kotlinx.coroutines.CoroutineDispatcher getCoroutineContext();
method public final void onStopped();
+ method public final suspend Object! setProgress(androidx.work.Data data, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
method public final com.google.common.util.concurrent.ListenableFuture<androidx.work.ListenableWorker.Result> startWork();
property @Deprecated public kotlinx.coroutines.CoroutineDispatcher coroutineContext;
}
diff --git a/work/workmanager-ktx/api/restricted_2.3.0-alpha01.txt b/work/workmanager-ktx/api/restricted_2.3.0-alpha01.txt
index 2a2fd52..5b6423e 100644
--- a/work/workmanager-ktx/api/restricted_2.3.0-alpha01.txt
+++ b/work/workmanager-ktx/api/restricted_2.3.0-alpha01.txt
@@ -6,6 +6,7 @@
method public abstract suspend Object doWork(kotlin.coroutines.Continuation<? super androidx.work.ListenableWorker.Result> p);
method @Deprecated public kotlinx.coroutines.CoroutineDispatcher getCoroutineContext();
method public final void onStopped();
+ method public final suspend Object! setProgress(androidx.work.Data data, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
method public final com.google.common.util.concurrent.ListenableFuture<androidx.work.ListenableWorker.Result> startWork();
property @Deprecated public kotlinx.coroutines.CoroutineDispatcher coroutineContext;
}
diff --git a/work/workmanager-ktx/api/restricted_current.txt b/work/workmanager-ktx/api/restricted_current.txt
index 2a2fd52..5b6423e 100644
--- a/work/workmanager-ktx/api/restricted_current.txt
+++ b/work/workmanager-ktx/api/restricted_current.txt
@@ -6,6 +6,7 @@
method public abstract suspend Object doWork(kotlin.coroutines.Continuation<? super androidx.work.ListenableWorker.Result> p);
method @Deprecated public kotlinx.coroutines.CoroutineDispatcher getCoroutineContext();
method public final void onStopped();
+ method public final suspend Object! setProgress(androidx.work.Data data, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
method public final com.google.common.util.concurrent.ListenableFuture<androidx.work.ListenableWorker.Result> startWork();
property @Deprecated public kotlinx.coroutines.CoroutineDispatcher coroutineContext;
}
diff --git a/work/workmanager-ktx/build.gradle b/work/workmanager-ktx/build.gradle
index 93c1a0e..0cee793 100644
--- a/work/workmanager-ktx/build.gradle
+++ b/work/workmanager-ktx/build.gradle
@@ -53,6 +53,9 @@
androidTestImplementation(ANDROIDX_TEST_CORE)
androidTestImplementation(ANDROIDX_TEST_RUNNER)
androidTestImplementation(ESPRESSO_CORE)
+ androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has its own MockMaker
+ androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has its own MockMaker
+ androidTestImplementation(WORK_ARCH_ROOM_TESTING)
testImplementation(JUNIT)
}
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 6232be6..301e7e1 100644
--- a/work/workmanager-ktx/src/androidTest/java/androidx/work/CoroutineWorkerTest.kt
+++ b/work/workmanager-ktx/src/androidTest/java/androidx/work/CoroutineWorkerTest.kt
@@ -19,15 +19,19 @@
import android.content.Context
import android.util.Log
import androidx.arch.core.executor.ArchTaskExecutor
-
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
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
+import androidx.work.workers.ProgressUpdatingWorker
import kotlinx.coroutines.asCoroutineDispatcher
+import kotlinx.coroutines.runBlocking
import org.hamcrest.CoreMatchers.`is`
import org.hamcrest.CoreMatchers.instanceOf
import org.hamcrest.MatcherAssert.assertThat
@@ -35,6 +39,11 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
import java.util.UUID
import java.util.concurrent.Executor
@@ -65,7 +74,7 @@
}
})
- context = ApplicationProvider.getApplicationContext() as android.content.Context
+ context = ApplicationProvider.getApplicationContext()
configuration = Configuration.Builder()
.setExecutor(SynchronousExecutor())
.setMinimumLoggingLevel(Log.DEBUG)
@@ -141,6 +150,44 @@
assertThat(worker.job.isCancelled, `is`(true))
}
+ @Test
+ @LargeTest
+ fun testProgressUpdates() {
+ val workerFactory = WorkerFactory.getDefaultWorkerFactory()
+ val progressUpdater = spy(WorkProgressUpdater(database, workManagerImpl.workTaskExecutor))
+ val workRequest = OneTimeWorkRequestBuilder<ProgressUpdatingWorker>().build()
+ database.workSpecDao().insertWorkSpec(workRequest.workSpec)
+ val worker = workerFactory.createWorkerWithDefaultFallback(
+ context,
+ ProgressUpdatingWorker::class.java.name,
+ WorkerParameters(
+ workRequest.id,
+ Data.EMPTY,
+ emptyList(),
+ WorkerParameters.RuntimeExtras(),
+ 1,
+ configuration.executor,
+ workManagerImpl.workTaskExecutor,
+ workerFactory,
+ progressUpdater
+ )
+ ) as ProgressUpdatingWorker
+
+ runBlocking {
+ val result = worker.doWork()
+ val captor = ArgumentCaptor.forClass(Data::class.java)
+ verify(progressUpdater, times(2))
+ .updateProgress(
+ any(Context::class.java),
+ any(UUID::class.java),
+ captor.capture()
+ )
+ assertThat(result, `is`(instanceOf(ListenableWorker.Result.Success::class.java)))
+ val recent = captor.allValues.lastOrNull()
+ assertThat(recent?.getInt(ProgressUpdatingWorker.Progress, 0), `is`(100))
+ }
+ }
+
class SynchronousExecutor : Executor {
override fun execute(command: Runnable) {
@@ -151,6 +198,7 @@
class InstantWorkTaskExecutor : TaskExecutor {
private val mSynchronousExecutor = SynchronousExecutor()
+ private val mSerialExecutor = SerialExecutor(mSynchronousExecutor)
override fun postToMainThread(runnable: Runnable) {
runnable.run()
@@ -161,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-ktx/src/androidTest/java/androidx/work/workers/ProgressUpdatingWorker.kt b/work/workmanager-ktx/src/androidTest/java/androidx/work/workers/ProgressUpdatingWorker.kt
new file mode 100644
index 0000000..f6d65ba
--- /dev/null
+++ b/work/workmanager-ktx/src/androidTest/java/androidx/work/workers/ProgressUpdatingWorker.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.workers
+
+import android.content.Context
+import androidx.work.CoroutineWorker
+import androidx.work.Data
+import androidx.work.WorkerParameters
+import kotlinx.coroutines.delay
+
+class ProgressUpdatingWorker(context: Context, parameters: WorkerParameters) :
+ CoroutineWorker(context, parameters) {
+
+ companion object {
+ const val Progress = "Progress"
+ private const val delayDuration = 1L
+ }
+
+ override suspend fun doWork(): Result {
+ val firstUpdate = Data.Builder().putInt(Progress, 10).build()
+ val lastUpdate = Data.Builder().putInt(Progress, 100).build()
+ setProgress(firstUpdate)
+ delay(delayDuration)
+ setProgress(lastUpdate)
+ return Result.success()
+ }
+}
\ No newline at end of file
diff --git a/work/workmanager-ktx/src/main/java/androidx/work/CoroutineWorker.kt b/work/workmanager-ktx/src/main/java/androidx/work/CoroutineWorker.kt
index 56e5679..c1d12bb 100644
--- a/work/workmanager-ktx/src/main/java/androidx/work/CoroutineWorker.kt
+++ b/work/workmanager-ktx/src/main/java/androidx/work/CoroutineWorker.kt
@@ -88,6 +88,16 @@
*/
abstract suspend fun doWork(): Result
+ /**
+ * Updates the progress for the [CoroutineWorker]. This is a suspending function unlike the
+ * [setProgressAsync] API which returns a [ListenableFuture].
+ *
+ * @param data The progress [Data]
+ */
+ suspend fun setProgress(data: Data) {
+ setProgressAsync(data).await()
+ }
+
final override fun onStopped() {
super.onStopped()
future.cancel(false)
diff --git a/work/workmanager-rxjava2/api/2.3.0-alpha01.txt b/work/workmanager-rxjava2/api/2.3.0-alpha01.txt
index e43f0e5..757ca4c 100644
--- a/work/workmanager-rxjava2/api/2.3.0-alpha01.txt
+++ b/work/workmanager-rxjava2/api/2.3.0-alpha01.txt
@@ -5,6 +5,7 @@
ctor public RxWorker(android.content.Context, androidx.work.WorkerParameters);
method @MainThread public abstract io.reactivex.Single<androidx.work.ListenableWorker.Result!> createWork();
method protected io.reactivex.Scheduler getBackgroundScheduler();
+ method public final io.reactivex.Single<java.lang.Void!> setProgress(androidx.work.Data);
method public com.google.common.util.concurrent.ListenableFuture<androidx.work.ListenableWorker.Result!> startWork();
}
diff --git a/work/workmanager-rxjava2/api/current.txt b/work/workmanager-rxjava2/api/current.txt
index e43f0e5..757ca4c 100644
--- a/work/workmanager-rxjava2/api/current.txt
+++ b/work/workmanager-rxjava2/api/current.txt
@@ -5,6 +5,7 @@
ctor public RxWorker(android.content.Context, androidx.work.WorkerParameters);
method @MainThread public abstract io.reactivex.Single<androidx.work.ListenableWorker.Result!> createWork();
method protected io.reactivex.Scheduler getBackgroundScheduler();
+ method public final io.reactivex.Single<java.lang.Void!> setProgress(androidx.work.Data);
method public com.google.common.util.concurrent.ListenableFuture<androidx.work.ListenableWorker.Result!> startWork();
}
diff --git a/work/workmanager-rxjava2/api/restricted_2.3.0-alpha01.txt b/work/workmanager-rxjava2/api/restricted_2.3.0-alpha01.txt
index e43f0e5..757ca4c 100644
--- a/work/workmanager-rxjava2/api/restricted_2.3.0-alpha01.txt
+++ b/work/workmanager-rxjava2/api/restricted_2.3.0-alpha01.txt
@@ -5,6 +5,7 @@
ctor public RxWorker(android.content.Context, androidx.work.WorkerParameters);
method @MainThread public abstract io.reactivex.Single<androidx.work.ListenableWorker.Result!> createWork();
method protected io.reactivex.Scheduler getBackgroundScheduler();
+ method public final io.reactivex.Single<java.lang.Void!> setProgress(androidx.work.Data);
method public com.google.common.util.concurrent.ListenableFuture<androidx.work.ListenableWorker.Result!> startWork();
}
diff --git a/work/workmanager-rxjava2/api/restricted_current.txt b/work/workmanager-rxjava2/api/restricted_current.txt
index e43f0e5..757ca4c 100644
--- a/work/workmanager-rxjava2/api/restricted_current.txt
+++ b/work/workmanager-rxjava2/api/restricted_current.txt
@@ -5,6 +5,7 @@
ctor public RxWorker(android.content.Context, androidx.work.WorkerParameters);
method @MainThread public abstract io.reactivex.Single<androidx.work.ListenableWorker.Result!> createWork();
method protected io.reactivex.Scheduler getBackgroundScheduler();
+ method public final io.reactivex.Single<java.lang.Void!> setProgress(androidx.work.Data);
method public com.google.common.util.concurrent.ListenableFuture<androidx.work.ListenableWorker.Result!> startWork();
}
diff --git a/work/workmanager-rxjava2/src/main/java/androidx/work/RxWorker.java b/work/workmanager-rxjava2/src/main/java/androidx/work/RxWorker.java
index de7fcf0..2200863 100644
--- a/work/workmanager-rxjava2/src/main/java/androidx/work/RxWorker.java
+++ b/work/workmanager-rxjava2/src/main/java/androidx/work/RxWorker.java
@@ -117,10 +117,22 @@
@MainThread
public abstract @NonNull Single<Result> createWork();
+ /**
+ * Updates the progress for a {@link RxWorker}. This method returns a {@link Single} unlike the
+ * {@link ListenableWorker#setProgressAsync(Data)} API.
+ *
+ * @param data The progress {@link Data}
+ * @return The {@link Single}
+ */
+ @NonNull
+ public final Single<Void> setProgress(@NonNull Data data) {
+ return Single.fromFuture(setProgressAsync(data));
+ }
+
@Override
public void onStopped() {
super.onStopped();
- final SingleFutureAdapter observer = mSingleFutureObserverAdapter;
+ final SingleFutureAdapter<Result> observer = mSingleFutureObserverAdapter;
if (observer != null) {
observer.dispose();
mSingleFutureObserverAdapter = null;
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 3656be3..a6cb786 100644
--- a/work/workmanager/api/2.3.0-alpha01.txt
+++ b/work/workmanager/api/2.3.0-alpha01.txt
@@ -143,7 +143,7 @@
method @RequiresApi(24) public final java.util.List<android.net.Uri!> getTriggeredContentUris();
method public final boolean isStopped();
method public void onStopped();
- method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> setProgress(androidx.work.Data);
+ method public final com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> setProgressAsync(androidx.work.Data);
method @MainThread public abstract com.google.common.util.concurrent.ListenableFuture<androidx.work.ListenableWorker.Result!> startWork();
}
diff --git a/work/workmanager/api/current.txt b/work/workmanager/api/current.txt
index 3656be3..a6cb786 100644
--- a/work/workmanager/api/current.txt
+++ b/work/workmanager/api/current.txt
@@ -143,7 +143,7 @@
method @RequiresApi(24) public final java.util.List<android.net.Uri!> getTriggeredContentUris();
method public final boolean isStopped();
method public void onStopped();
- method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> setProgress(androidx.work.Data);
+ method public final com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> setProgressAsync(androidx.work.Data);
method @MainThread public abstract com.google.common.util.concurrent.ListenableFuture<androidx.work.ListenableWorker.Result!> startWork();
}
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/background/greedy/GreedySchedulerTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/background/greedy/GreedySchedulerTest.java
index 32d6c33..99d2a83 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/background/greedy/GreedySchedulerTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/background/greedy/GreedySchedulerTest.java
@@ -24,6 +24,7 @@
import static org.mockito.Mockito.when;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import androidx.work.Constraints;
import androidx.work.OneTimeWorkRequest;
@@ -107,6 +108,20 @@
@Test
@SmallTest
+ @SdkSuppress(minSdkVersion = 23)
+ public void testGreedyScheduler_ignoresIdleWorkConstraint() {
+ Constraints constraints = new Constraints.Builder()
+ .setRequiresDeviceIdle(true)
+ .build();
+ OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class)
+ .setConstraints(constraints)
+ .build();
+ mGreedyScheduler.schedule(getWorkSpec(work));
+ verify(mMockWorkConstraintsTracker, never()).replace(ArgumentMatchers.<WorkSpec>anyList());
+ }
+
+ @Test
+ @SmallTest
public void testGreedyScheduler_startsWorkWhenConstraintsMet() {
mGreedyScheduler.onAllConstraintsMet(Collections.singletonList(TEST_ID));
verify(mWorkManagerImpl).startWork(TEST_ID);
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/androidTest/java/androidx/work/impl/workers/ConstraintTrackingWorkerTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/workers/ConstraintTrackingWorkerTest.java
index f897235..8469605 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/workers/ConstraintTrackingWorkerTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/workers/ConstraintTrackingWorkerTest.java
@@ -30,8 +30,8 @@
import androidx.annotation.NonNull;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
import androidx.test.filters.SdkSuppress;
-import androidx.test.filters.SmallTest;
import androidx.work.Configuration;
import androidx.work.Constraints;
import androidx.work.Data;
@@ -69,7 +69,7 @@
import java.util.concurrent.Executors;
@RunWith(AndroidJUnit4.class)
-@SmallTest
+@LargeTest
public class ConstraintTrackingWorkerTest extends DatabaseTest {
private static final long DELAY_IN_MS = 100;
diff --git a/work/workmanager/src/main/java/androidx/work/ListenableWorker.java b/work/workmanager/src/main/java/androidx/work/ListenableWorker.java
index 72a7d5d..2203618 100644
--- a/work/workmanager/src/main/java/androidx/work/ListenableWorker.java
+++ b/work/workmanager/src/main/java/androidx/work/ListenableWorker.java
@@ -195,7 +195,7 @@
* Cancelling this future is a no-op.
*/
@NonNull
- public ListenableFuture<Void> setProgress(@NonNull Data data) {
+ public final ListenableFuture<Void> setProgressAsync(@NonNull Data data) {
return mWorkerParams.getProgressUpdater()
.updateProgress(getApplicationContext(), getId(), data);
}
diff --git a/work/workmanager/src/main/java/androidx/work/impl/background/greedy/GreedyScheduler.java b/work/workmanager/src/main/java/androidx/work/impl/background/greedy/GreedyScheduler.java
index c2d0e4f..c54c6f9 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/background/greedy/GreedyScheduler.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/background/greedy/GreedyScheduler.java
@@ -16,8 +16,9 @@
package androidx.work.impl.background.greedy;
+import static android.os.Build.VERSION.SDK_INT;
+
import android.content.Context;
-import android.os.Build;
import android.text.TextUtils;
import androidx.annotation.NonNull;
@@ -87,10 +88,17 @@
&& workSpec.initialDelay == 0L
&& !workSpec.isBackedOff()) {
if (workSpec.hasConstraints()) {
- // Exclude content URI triggers - we don't know how to handle them here so the
- // background scheduler should take care of them.
- if (Build.VERSION.SDK_INT < 24
- || !workSpec.constraints.hasContentUriTriggers()) {
+ if (SDK_INT >= 23 && workSpec.constraints.requiresDeviceIdle()) {
+ // Ignore requests that have an idle mode constraint.
+ Logger.get().debug(TAG,
+ String.format("Ignoring WorkSpec %s, Requires device idle.",
+ workSpec));
+ } else if (SDK_INT >= 24 && workSpec.constraints.hasContentUriTriggers()) {
+ // Ignore requests that have content uri triggers.
+ Logger.get().debug(TAG,
+ String.format("Ignoring WorkSpec %s, Requires ContentUri triggers.",
+ workSpec));
+ } else {
constrainedWorkSpecs.add(workSpec);
constrainedWorkSpecIds.add(workSpec.id);
}
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/WorkProgressUpdater.java b/work/workmanager/src/main/java/androidx/work/impl/utils/WorkProgressUpdater.java
index 38cd978..dc114ee 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/utils/WorkProgressUpdater.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/utils/WorkProgressUpdater.java
@@ -73,13 +73,14 @@
if (state == null) {
Logger.get().warning(TAG,
String.format(
- "Ignoring setProgress(...). WorkSpec (%s) does not exist.",
+ "Ignoring setProgressAsync(...). WorkSpec (%s) does not "
+ + "exist.",
workSpecId));
} else if (state.isFinished()) {
Logger.get().warning(TAG,
String.format(
- "Ignoring setProgress(...). WorkSpec (%s) has finished "
- + "execution.",
+ "Ignoring setProgressAsync(...). WorkSpec (%s) has "
+ + "finished execution.",
workSpecId));
} else {
WorkProgress progress = new WorkProgress(workSpecId, data);
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;
}
}