Merge "Bump paging to 3.0.0-beta02" into androidx-main
diff --git a/.github/workflows/presubmit.yml b/.github/workflows/presubmit.yml
index addc9e4..3248074 100644
--- a/.github/workflows/presubmit.yml
+++ b/.github/workflows/presubmit.yml
@@ -74,7 +74,7 @@
env:
JAVA_HOME: ${{ steps.setup-java.outputs.path }}
with:
- arguments: -q :ktlintCheckFile ${{ steps.ktlint-file-args.outputs.ktlint-file-args }}
+ arguments: -q :ktlintCheckFile ${{ steps.ktlint-file-args.outputs.ktlint-file-args }} --stacktrace
build-root-directory: activity
configuration-cache-enabled: true
dependencies-cache-enabled: true
@@ -123,7 +123,7 @@
env:
JAVA_HOME: ${{ steps.setup-java.outputs.path }}
with:
- arguments: buildOnServer
+ arguments: buildOnServer --stacktrace
build-root-directory: ${{ env.project-root }}
configuration-cache-enabled: true
dependencies-cache-enabled: true
@@ -180,7 +180,7 @@
env:
JAVA_HOME: ${{ steps.setup-java.outputs.path }}
with:
- arguments: buildOnServer
+ arguments: buildOnServer --stacktrace
build-root-directory: ${{ env.project-root }}
configuration-cache-enabled: true
dependencies-cache-enabled: true
@@ -237,7 +237,7 @@
env:
JAVA_HOME: ${{ steps.setup-java.outputs.path }}
with:
- arguments: buildOnServer
+ arguments: buildOnServer --stacktrace
build-root-directory: ${{ env.project-root }}
configuration-cache-enabled: true
dependencies-cache-enabled: true
@@ -294,7 +294,7 @@
env:
JAVA_HOME: ${{ steps.setup-java.outputs.path }}
with:
- arguments: buildOnServer
+ arguments: buildOnServer --stacktrace
build-root-directory: ${{ env.project-root }}
configuration-cache-enabled: true
dependencies-cache-enabled: true
@@ -351,7 +351,7 @@
env:
JAVA_HOME: ${{ steps.setup-java.outputs.path }}
with:
- arguments: buildOnServer
+ arguments: buildOnServer --stacktrace
build-root-directory: ${{ env.project-root }}
configuration-cache-enabled: true
dependencies-cache-enabled: true
@@ -408,7 +408,7 @@
env:
JAVA_HOME: ${{ steps.setup-java.outputs.path }}
with:
- arguments: buildOnServer
+ arguments: buildOnServer --stacktrace
build-root-directory: ${{ env.project-root }}
configuration-cache-enabled: true
dependencies-cache-enabled: true
@@ -465,7 +465,7 @@
env:
JAVA_HOME: ${{ steps.setup-java.outputs.path }}
with:
- arguments: buildOnServer
+ arguments: buildOnServer --stacktrace
build-root-directory: ${{ env.project-root }}
configuration-cache-enabled: true
dependencies-cache-enabled: true
@@ -522,7 +522,7 @@
env:
JAVA_HOME: ${{ steps.setup-java.outputs.path }}
with:
- arguments: buildOnServer
+ arguments: buildOnServer --stacktrace
build-root-directory: ${{ env.project-root }}
configuration-cache-enabled: true
dependencies-cache-enabled: true
@@ -580,7 +580,7 @@
env:
JAVA_HOME: ${{ steps.setup-java.outputs.path }}
with:
- arguments: buildOnServer
+ arguments: buildOnServer --stacktrace
build-root-directory: ${{ env.project-root }}
configuration-cache-enabled: true
dependencies-cache-enabled: true
diff --git a/appcompat/appcompat/src/main/java/androidx/appcompat/widget/SuggestionsAdapter.java b/appcompat/appcompat/src/main/java/androidx/appcompat/widget/SuggestionsAdapter.java
index 87e1061..acba2f1 100644
--- a/appcompat/appcompat/src/main/java/androidx/appcompat/widget/SuggestionsAdapter.java
+++ b/appcompat/appcompat/src/main/java/androidx/appcompat/widget/SuggestionsAdapter.java
@@ -16,7 +16,6 @@
* limitations under the License.
*/
-import android.annotation.SuppressLint;
import android.app.SearchManager;
import android.app.SearchableInfo;
import android.content.ComponentName;
@@ -56,7 +55,6 @@
/**
* Provides the contents for the suggestion drop-down list.in {@link SearchView}.
*/
-@SuppressLint("RestrictedAPI") // Temporary until we have correct restriction scopes for 1.0
class SuggestionsAdapter extends ResourceCursorAdapter implements OnClickListener {
private static final boolean DBG = false;
@@ -88,10 +86,11 @@
private int mIconName2Col = INVALID_INDEX;
private int mFlagsCol = INVALID_INDEX;
- // private final Runnable mStartSpinnerRunnable;
- // private final Runnable mStopSpinnerRunnable;
+ @SuppressWarnings("deprecation")
public SuggestionsAdapter(Context context, SearchView searchView, SearchableInfo searchable,
WeakHashMap<String, Drawable.ConstantState> outsideDrawablesCache) {
+ // Auto-requery is discouraged, as it results in Cursor queries being performed on the
+ // application's UI thread.
super(context, searchView.getSuggestionRowLayout(), null /* no initial cursor */,
true /* auto-requery */);
mSearchView = searchView;
@@ -335,8 +334,9 @@
if (mUrlColor == null) {
// Lazily get the URL color from the current theme.
TypedValue colorValue = new TypedValue();
- mContext.getTheme().resolveAttribute(R.attr.textColorSearchUrl, colorValue, true);
- mUrlColor = mContext.getResources().getColorStateList(colorValue.resourceId);
+ mProviderContext.getTheme().resolveAttribute(
+ R.attr.textColorSearchUrl, colorValue, true);
+ mUrlColor = mProviderContext.getResources().getColorStateList(colorValue.resourceId);
}
SpannableString text = new SpannableString(url);
@@ -450,7 +450,7 @@
} catch (RuntimeException e) {
Log.w(LOG_TAG, "Search suggestions cursor threw exception.", e);
// Put exception string in item title
- View v = newView(mContext, mCursor, parent);
+ View v = newView(mProviderContext, getCursor(), parent);
if (v != null) {
ChildViewCache views = (ChildViewCache) v.getTag();
TextView tv = views.mText1;
@@ -473,7 +473,7 @@
} catch (RuntimeException e) {
Log.w(LOG_TAG, "Search suggestions cursor threw exception.", e);
// Put exception string in item title
- final View v = newDropDownView(mContext, mCursor, parent);
+ final View v = newDropDownView(mProviderContext, getCursor(), parent);
if (v != null) {
final ChildViewCache views = (ChildViewCache) v.getTag();
final TextView tv = views.mText1;
@@ -607,7 +607,7 @@
}
// Fall back to a default icon
- return mContext.getPackageManager().getDefaultActivityIcon();
+ return mProviderContext.getPackageManager().getDefaultActivityIcon();
}
/**
@@ -642,7 +642,7 @@
* have an icon set.
*/
private Drawable getActivityIcon(ComponentName component) {
- PackageManager pm = mContext.getPackageManager();
+ PackageManager pm = mProviderContext.getPackageManager();
final ActivityInfo activityInfo;
try {
activityInfo = pm.getActivityInfo(component, PackageManager.GET_META_DATA);
@@ -700,7 +700,7 @@
throw new FileNotFoundException("No authority: " + uri);
} else {
try {
- r = mContext.getPackageManager().getResourcesForApplication(authority);
+ r = mProviderContext.getPackageManager().getResourcesForApplication(authority);
} catch (NameNotFoundException ex) {
throw new FileNotFoundException("No package found for authority: " + uri);
}
@@ -773,6 +773,6 @@
Uri uri = uriBuilder.build();
// finally, make the query
- return mContext.getContentResolver().query(uri, null, selection, selArgs, null);
+ return mProviderContext.getContentResolver().query(uri, null, selection, selArgs, null);
}
}
\ No newline at end of file
diff --git a/buildSrc/build_dependencies.gradle b/buildSrc/build_dependencies.gradle
index 647fe0a..cbfcbbe 100644
--- a/buildSrc/build_dependencies.gradle
+++ b/buildSrc/build_dependencies.gradle
@@ -22,7 +22,7 @@
build_versions.agp = build_versions.studio["agp"]
build_versions.lint = build_versions.studio["lint"]
-build_versions.kotlin = "1.4.30"
+build_versions.kotlin = "1.4.31"
build_versions.kotlin_coroutines = "1.4.1"
build_versions.ksp = "1.4.30-1.0.0-alpha03"
diff --git a/buildSrc/src/main/kotlin/androidx/build/ErrorProneConfiguration.kt b/buildSrc/src/main/kotlin/androidx/build/ErrorProneConfiguration.kt
index afb2421..deca706 100644
--- a/buildSrc/src/main/kotlin/androidx/build/ErrorProneConfiguration.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/ErrorProneConfiguration.kt
@@ -182,8 +182,7 @@
name = ERROR_PRONE_TASK,
>
val compileTask = compileTaskProvider.get()
- it.classpath = compileTask.classpath +
- project.files("${project.buildDir}/classes/kotlin/main")
+ it.classpath = compileTask.classpath
it.source = compileTask.source
it.destinationDir = file(buildDir.resolve("errorProne"))
diff --git a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
index 828d2e8..79f5afb 100644
--- a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
@@ -101,7 +101,7 @@
val RECYCLERVIEW_SELECTION = Version("2.0.0-alpha01")
val REMOTECALLBACK = Version("1.0.0-alpha02")
val RESOURCEINSPECTION = Version("1.0.0-alpha01")
- val ROOM = Version("2.3.0-rc01")
+ val ROOM = Version("2.3.0-beta03")
val SAVEDSTATE = Version("1.2.0-alpha01")
val SECURITY = Version("1.1.0-alpha03")
val SECURITY_APP_AUTHENTICATOR = Version("1.0.0-alpha01")
@@ -131,18 +131,18 @@
val VIEWPAGER = Version("1.1.0-alpha01")
val VIEWPAGER2 = Version("1.1.0-alpha02")
val WEAR = Version("1.2.0-alpha07")
- val WEAR_COMPLICATIONS = Version("1.0.0-alpha08")
+ val WEAR_COMPLICATIONS = Version("1.0.0-alpha09")
val WEAR_INPUT = Version("1.1.0-alpha02")
val WEAR_ONGOING = Version("1.0.0-alpha03")
val WEAR_PHONE_INTERACTIONS = Version("1.0.0-alpha03")
val WEAR_REMOTE_INTERACTIONS = Version("1.0.0-alpha02")
val WEAR_TILES = Version("1.0.0-alpha01")
val WEAR_TILES_DATA = WEAR_TILES
- val WEAR_WATCHFACE = Version("1.0.0-alpha08")
- val WEAR_WATCHFACE_CLIENT = Version("1.0.0-alpha08")
- val WEAR_WATCHFACE_DATA = Version("1.0.0-alpha08")
- val WEAR_WATCHFACE_EDITOR = Version("1.0.0-alpha08")
- val WEAR_WATCHFACE_STYLE = Version("1.0.0-alpha08")
+ val WEAR_WATCHFACE = Version("1.0.0-alpha09")
+ val WEAR_WATCHFACE_CLIENT = Version("1.0.0-alpha09")
+ val WEAR_WATCHFACE_DATA = Version("1.0.0-alpha09")
+ val WEAR_WATCHFACE_EDITOR = Version("1.0.0-alpha09")
+ val WEAR_WATCHFACE_STYLE = Version("1.0.0-alpha09")
val WEBKIT = Version("1.5.0-alpha01")
val WINDOW = Version("1.0.0-alpha04")
val WINDOW_EXTENSIONS = Version("1.0.0-alpha01")
diff --git a/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt b/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
index 90ec734..6d0f116 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
@@ -89,7 +89,7 @@
val KSP_VERSION get() = kspVersion
val KOTLIN_KSP_API get() = "com.google.devtools.ksp:symbol-processing-api:$KSP_VERSION"
val KOTLIN_KSP get() = "com.google.devtools.ksp:symbol-processing:$KSP_VERSION"
-const val KOTLIN_GRADLE_PLUGIN = "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.30"
+const val KOTLIN_GRADLE_PLUGIN = "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.31"
const val KOTLIN_METADATA = "me.eugeniomarletti.kotlin.metadata:kotlin-metadata:1.4.0"
const val KOTLIN_METADATA_JVM = "org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.1.0"
@@ -104,6 +104,7 @@
const val MULTIDEX = "androidx.multidex:multidex:2.0.1"
const val NULLAWAY = "com.uber.nullaway:nullaway:0.3.7"
const val PLAY_CORE = "com.google.android.play:core:1.9.1"
+const val PLAY_SERVICES_BASE = "com.google.android.gms:play-services-base:17.0.0"
const val REACTIVE_STREAMS = "org.reactivestreams:reactive-streams:1.0.0"
const val RX_JAVA = "io.reactivex.rxjava2:rxjava:2.2.9"
const val RX_JAVA3 = "io.reactivex.rxjava3:rxjava:3.0.0"
diff --git a/car/app/app/src/main/java/androidx/car/app/serialization/Bundler.java b/car/app/app/src/main/java/androidx/car/app/serialization/Bundler.java
index ea621d8..16d514c 100644
--- a/car/app/app/src/main/java/androidx/car/app/serialization/Bundler.java
+++ b/car/app/app/src/main/java/androidx/car/app/serialization/Bundler.java
@@ -65,6 +65,7 @@
* <li>{@link String}
* <li>{@link Parcelable} - parcelables will serialize using {@link Parcelable} logic.
* <li>{@link IInterface}
+ * <li>{@link IBinder}
* <li>{@link Map} - maps will be deserialize into {@link HashMap}s, and need to hold objects that
* are also serializable as per this list.
* <li>{@link List} - lists will deserialize into {@link ArrayList}s, and need to hold objects that
@@ -99,7 +100,7 @@
private static final String TAG_2 = "tag_2";
private static final int PRIMITIVE = 0;
- private static final int BINDER = 1;
+ private static final int IINTERFACE = 1;
private static final int MAP = 2;
private static final int SET = 3;
private static final int LIST = 4;
@@ -107,6 +108,7 @@
private static final int IMAGE = 6;
private static final int ENUM = 7;
private static final int CLASS = 8;
+ private static final int IBINDER = 9;
/**
* Serializes an object into a {@link Bundle} for sending over IPC.
@@ -144,7 +146,9 @@
} else if (isPrimitiveType(obj) || obj instanceof Parcelable) {
return serializePrimitive(obj, trace);
} else if (obj instanceof IInterface) {
- return serializeBinder((IInterface) obj);
+ return serializeIInterface((IInterface) obj);
+ } else if (obj instanceof IBinder) {
+ return serializeIBinder((IBinder) obj);
} else if (obj instanceof Map) {
return serializeMap((Map<Object, Object>) obj, trace);
} else if (obj instanceof List) {
@@ -200,8 +204,10 @@
switch (classType) {
case PRIMITIVE:
return deserializePrimitive(bundle, trace);
- case BINDER:
- return deserializeBinder(bundle, trace);
+ case IINTERFACE:
+ return deserializeIInterface(bundle, trace);
+ case IBINDER:
+ return deserializeIBinder(bundle, trace);
case MAP:
return deserializeMap(bundle, trace);
case SET:
@@ -256,18 +262,27 @@
return bundle;
}
- private static Bundle serializeBinder(IInterface iInterface) {
+ private static Bundle serializeIInterface(IInterface iInterface) {
Bundle bundle = new Bundle(3);
String className = iInterface.getClass().getName();
- bundle.putInt(TAG_CLASS_TYPE, BINDER);
+ bundle.putInt(TAG_CLASS_TYPE, IINTERFACE);
bundle.putBinder(TAG_VALUE, iInterface.asBinder());
bundle.putString(TAG_CLASS_NAME, className);
return bundle;
}
+ private static Bundle serializeIBinder(IBinder binder) {
+ Bundle bundle = new Bundle(2);
+
+ bundle.putInt(TAG_CLASS_TYPE, IBINDER);
+ bundle.putBinder(TAG_VALUE, binder);
+
+ return bundle;
+ }
+
private static Bundle serializeMap(Map<Object, Object> map, Trace trace)
throws BundlerException {
Bundle bundle = new Bundle(2);
@@ -392,7 +407,8 @@
}
@SuppressWarnings("argument.type.incompatible") // so that we can invoke static asInterface
- private static Object deserializeBinder(Bundle bundle, Trace trace) throws BundlerException {
+ private static Object deserializeIInterface(Bundle bundle, Trace trace)
+ throws BundlerException {
IBinder binder = bundle.getBinder(TAG_VALUE);
if (binder == null) {
throw new TracedBundlerException("Bundle is missing the binder", trace);
@@ -426,6 +442,15 @@
}
}
+ private static Object deserializeIBinder(Bundle bundle, Trace trace) throws BundlerException {
+ IBinder binder = bundle.getBinder(TAG_VALUE);
+ if (binder == null) {
+ throw new TracedBundlerException("Bundle is missing the binder", trace);
+ }
+
+ return binder;
+ }
+
@SuppressWarnings("argument.type.incompatible") // so that we can put null values in the map
private static Object deserializeMap(Bundle bundle, Trace trace) throws BundlerException {
ArrayList<Parcelable> list = bundle.getParcelableArrayList(TAG_VALUE);
@@ -641,7 +666,8 @@
private static Map<Integer, String> initBundledTypeNames() {
ArrayMap<Integer, String> map = new ArrayMap<>();
map.put(PRIMITIVE, "primitive");
- map.put(BINDER, "binder");
+ map.put(IINTERFACE, "iInterface");
+ map.put(IBINDER, "iBinder");
map.put(MAP, "map");
map.put(SET, "set");
map.put(LIST, "list");
diff --git a/car/app/app/src/test/java/androidx/car/app/serialization/BundlerTest.java b/car/app/app/src/test/java/androidx/car/app/serialization/BundlerTest.java
index 5dc3718..8e10a451 100644
--- a/car/app/app/src/test/java/androidx/car/app/serialization/BundlerTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/serialization/BundlerTest.java
@@ -25,10 +25,11 @@
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
+import android.os.Binder;
import android.os.Bundle;
+import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
-import android.os.RemoteException;
import androidx.annotation.Nullable;
import androidx.car.app.OnDoneCallback;
@@ -158,7 +159,15 @@
}
@Test
- public void binderSerialization() throws BundlerException, RemoteException {
+ public void iBinderSerialization() throws BundlerException {
+ IBinder binder = new Binder();
+ Bundle bundle = Bundler.toBundle(binder);
+
+ assertThat(Bundler.fromBundle(bundle)).isEqualTo(binder);
+ }
+
+ @Test
+ public void iInterfaceSerialization() throws BundlerException {
OnClickListener clickListener = mock(OnClickListener.class);
ItemList itemList =
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposePlugin.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposePlugin.kt
index c9e61d1..2a24198 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposePlugin.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposePlugin.kt
@@ -129,7 +129,7 @@
project: Project,
configuration: CompilerConfiguration
) {
- val KOTLIN_VERSION_EXPECTATION = "1.4.30"
+ val KOTLIN_VERSION_EXPECTATION = "1.4.31"
KotlinCompilerVersion.getVersion()?.let { version ->
val suppressKotlinVersionCheck = configuration.get(
ComposeConfiguration.SUPPRESS_KOTLIN_VERSION_COMPATIBILITY_CHECK,
diff --git a/compose/runtime/runtime/compose-runtime-benchmark/build.gradle b/compose/runtime/runtime/compose-runtime-benchmark/build.gradle
index 698b80d..9d8fb3c 100644
--- a/compose/runtime/runtime/compose-runtime-benchmark/build.gradle
+++ b/compose/runtime/runtime/compose-runtime-benchmark/build.gradle
@@ -51,6 +51,7 @@
androidTestImplementation(project(":compose:material:material"))
androidTestImplementation(project(":compose:runtime:runtime"))
androidTestImplementation(project(":compose:ui:ui-text"))
+ androidTestImplementation(project(":compose:ui:ui-util"))
androidTestImplementation(JUNIT)
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
diff --git a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/ComposeLayoutInspector.kt b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/ComposeLayoutInspector.kt
index 2c51dfd..f351dd3 100644
--- a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/ComposeLayoutInspector.kt
+++ b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/ComposeLayoutInspector.kt
@@ -22,6 +22,7 @@
import androidx.compose.ui.inspection.compose.convertParameters
import androidx.compose.ui.inspection.compose.flatten
import androidx.compose.ui.inspection.framework.flatten
+import androidx.compose.ui.inspection.inspector.InspectorNode
import androidx.compose.ui.inspection.inspector.LayoutInspectorTree
import androidx.compose.ui.inspection.proto.StringTable
import androidx.compose.ui.inspection.proto.convertAll
@@ -58,8 +59,25 @@
private val environment: InspectorEnvironment
) : Inspector(connection) {
+ /** Cache data which allows us to reuse previously queried inspector nodes */
+ private class CacheData(
+ /** If the cached data includes system nodes or not */
+ val systemComposablesSkipped: Boolean,
+ /** The cached nodes themselves as a map from node id to InspectorNode */
+ val nodes: Map<Long, InspectorNode>,
+ )
+
private val layoutInspectorTree = LayoutInspectorTree()
+ // Sidestep threading concerns by only ever accessing cachedNodes on the inspector thread
+ private val inspectorThread = Thread.currentThread()
+ private val _cachedNodes = mutableMapOf<Long, CacheData>()
+ private val cachedNodes: MutableMap<Long, CacheData>
+ get() {
+ check(Thread.currentThread() == inspectorThread)
+ return _cachedNodes
+ }
+
override fun onReceiveCommand(data: ByteArray, callback: CommandCallback) {
val command = Command.parseFrom(data)
when (command.specializedCase) {
@@ -82,15 +100,19 @@
) {
ThreadUtils.runOnMainThread {
val stringTable = StringTable()
- val composeRoots =
- getComposableRoots(
- getComposablesCommand.rootViewId,
- getComposablesCommand.skipSystemComposables
- )
- .map { it.createComposableRoot(stringTable) }
- .toList()
+ val composeViews = getAndroidComposeViews(
+ getComposablesCommand.rootViewId,
+ getComposablesCommand.skipSystemComposables
+ )
+
+ val composeRoots = composeViews.map { it.createComposableRoot(stringTable) }
environment.executors().primary().execute {
+ cachedNodes[getComposablesCommand.rootViewId] = CacheData(
+ getComposablesCommand.skipSystemComposables,
+ composeViews.toInspectorNodes().associateBy { it.id }
+ )
+
callback.reply {
getComposablesResponse = GetComposablesResponse.newBuilder().apply {
addAllStrings(stringTable.toStringEntries())
@@ -105,31 +127,26 @@
getParametersCommand: GetParametersCommand,
callback: CommandCallback
) {
- ThreadUtils.runOnMainThread {
- val foundComposable = getComposableRoots(
+ val foundComposable =
+ getComposableNodes(
getParametersCommand.rootViewId,
getParametersCommand.skipSystemComposables
- )
- .flatMap { it.inspectorNodes }
- .flatMap { it.flatten() }
- .firstOrNull { it.id == getParametersCommand.composableId }
+ )[getParametersCommand.composableId]
- environment.executors().primary().execute {
- callback.reply {
- getParametersResponse = if (foundComposable != null) {
- val stringTable = StringTable()
- val parameters = foundComposable.convertParameters().convertAll(stringTable)
- GetParametersResponse.newBuilder().apply {
- parameterGroup = ParameterGroup.newBuilder().apply {
- composableId = getParametersCommand.composableId
- addAllParameter(parameters)
- }.build()
- addAllStrings(stringTable.toStringEntries())
- }.build()
- } else {
- GetParametersResponse.getDefaultInstance()
- }
- }
+ callback.reply {
+ getParametersResponse = if (foundComposable != null) {
+ val stringTable = StringTable()
+ val parameters =
+ foundComposable.convertParameters(layoutInspectorTree).convertAll(stringTable)
+ GetParametersResponse.newBuilder().apply {
+ parameterGroup = ParameterGroup.newBuilder().apply {
+ composableId = getParametersCommand.composableId
+ addAllParameter(parameters)
+ }.build()
+ addAllStrings(stringTable.toStringEntries())
+ }.build()
+ } else {
+ GetParametersResponse.getDefaultInstance()
}
}
}
@@ -138,58 +155,80 @@
getAllParametersCommand: GetAllParametersCommand,
callback: CommandCallback
) {
- ThreadUtils.runOnMainThread {
-
- val allComposables = getComposableRoots(
+ val allComposables =
+ getComposableNodes(
getAllParametersCommand.rootViewId,
getAllParametersCommand.skipSystemComposables
- )
- .flatMap { it.inspectorNodes }
- .flatMap { it.flatten() }
- .toList()
+ ).values
- environment.executors().primary().execute {
- callback.reply {
- val stringTable = StringTable()
- val parameterGroups = allComposables.map { composable ->
- val parameters = composable.convertParameters().convertAll(stringTable)
- ParameterGroup.newBuilder().apply {
- composableId = composable.id
- addAllParameter(parameters)
- }.build()
- }
-
- getAllParametersResponse = GetAllParametersResponse.newBuilder().apply {
- rootViewId = getAllParametersCommand.rootViewId
- addAllParameterGroups(parameterGroups)
- addAllStrings(stringTable.toStringEntries())
- }.build()
- }
+ callback.reply {
+ val stringTable = StringTable()
+ val parameterGroups = allComposables.map { composable ->
+ val parameters =
+ composable.convertParameters(layoutInspectorTree).convertAll(stringTable)
+ ParameterGroup.newBuilder().apply {
+ composableId = composable.id
+ addAllParameter(parameters)
+ }.build()
}
+
+ getAllParametersResponse = GetAllParametersResponse.newBuilder().apply {
+ rootViewId = getAllParametersCommand.rootViewId
+ addAllParameterGroups(parameterGroups)
+ addAllStrings(stringTable.toStringEntries())
+ }.build()
}
}
- private fun getComposableRoots(
+ /**
+ * Get all [InspectorNode]s found under the layout tree rooted by [rootViewId]. They will be
+ * mapped with their ID as the key.
+ *
+ * This will return cached data if possible, but may request new data otherwise, blocking the
+ * current thread potentially.
+ */
+ private fun getComposableNodes(
rootViewId: Long,
skipSystemComposables: Boolean
- ): Sequence<AndroidComposeViewWrapper> {
+ ): Map<Long, InspectorNode> {
+ cachedNodes[rootViewId]?.let { cacheData ->
+ if (cacheData.systemComposablesSkipped == skipSystemComposables) {
+ return cacheData.nodes
+ }
+ }
+
+ return ThreadUtils.runOnMainThread {
+ getAndroidComposeViews(rootViewId, skipSystemComposables)
+ .toInspectorNodes()
+ .associateBy { it.id }
+ }.get()
+ }
+
+ /**
+ * Get all AndroidComposeView instances found within the layout tree rooted by [rootViewId].
+ */
+ private fun getAndroidComposeViews(
+ rootViewId: Long,
+ skipSystemComposables: Boolean
+ ): List<AndroidComposeViewWrapper> {
ThreadUtils.assertOnMainThread()
+ layoutInspectorTree.resetGeneratedId()
return WindowInspector.getGlobalWindowViews()
.asSequence()
- .filter { view -> view.visibility == View.VISIBLE && view.isAttachedToWindow }
- // Note: When querying root views, there should only be 0 or 1 match here, but it's
- // easier to handle this as a general filter, to avoid ? operators all the rest of
- // the way down
- .filter { it.uniqueDrawingId == rootViewId }
+ .filter { root ->
+ root.visibility == View.VISIBLE && root.isAttachedToWindow &&
+ root.uniqueDrawingId == rootViewId
+ }
.flatMap { it.flatten() }
- .mapNotNull {
+ .mapNotNull { view ->
AndroidComposeViewWrapper.tryCreateFor(
layoutInspectorTree,
- it,
+ view,
skipSystemComposables
)
}
+ .toList()
}
}
@@ -198,3 +237,14 @@
response.initResponse()
reply(response.build().toByteArray())
}
+
+/**
+ * Convert an [AndroidComposeViewWrapper] to a flat list of all inspector nodes (including children)
+ * that live underneath it.
+ */
+private fun List<AndroidComposeViewWrapper>.toInspectorNodes(): List<InspectorNode> {
+ return this
+ .flatMap { it.inspectorNodes }
+ .flatMap { it.flatten() }
+ .toList()
+}
diff --git a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/compose/ComposeExtensions.kt b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/compose/ComposeExtensions.kt
index d3b309b..a655403 100644
--- a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/compose/ComposeExtensions.kt
+++ b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/compose/ComposeExtensions.kt
@@ -27,17 +27,15 @@
* This method can take a long time, especially the first time, and should be called off the main
* thread.
*/
-fun InspectorNode.convertParameters(): List<NodeParameter> {
+fun InspectorNode.convertParameters(layoutInspectorTree: LayoutInspectorTree): List<NodeParameter> {
ThreadUtils.assertOffMainThread()
- return LayoutInspectorTree().convertParameters(this)
+ return layoutInspectorTree.convertParameters(this)
}
/**
* Flatten an inspector node into a list containing itself and all its children.
*/
fun InspectorNode.flatten(): Sequence<InspectorNode> {
- ThreadUtils.assertOnMainThread()
-
val remaining = mutableListOf(this)
return generateSequence {
val next = remaining.removeLastOrNull()
diff --git a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/util/ThreadUtils.kt b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/util/ThreadUtils.kt
index fc96122..d088b13 100644
--- a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/util/ThreadUtils.kt
+++ b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/util/ThreadUtils.kt
@@ -18,6 +18,8 @@
import android.os.Handler
import android.os.Looper
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.Future
object ThreadUtils {
fun assertOnMainThread() {
@@ -32,7 +34,21 @@
}
}
- fun runOnMainThread(block: () -> Unit) {
- Handler.createAsync(Looper.getMainLooper()).post(block)
+ /**
+ * Run some logic on the main thread, returning a future that will contain any data computed
+ * by and returned from the block.
+ *
+ * If this method is called from the main thread, it will run immediately.
+ */
+ fun <T> runOnMainThread(block: () -> T): Future<T> {
+ return if (!Looper.getMainLooper().isCurrentThread) {
+ val future = CompletableFuture<T>()
+ Handler.createAsync(Looper.getMainLooper()).post {
+ future.complete(block())
+ }
+ future
+ } else {
+ CompletableFuture.completedFuture(block())
+ }
}
}
diff --git a/compose/ui/ui-test-junit4/build.gradle b/compose/ui/ui-test-junit4/build.gradle
index d9596ce..aa26329 100644
--- a/compose/ui/ui-test-junit4/build.gradle
+++ b/compose/ui/ui-test-junit4/build.gradle
@@ -37,19 +37,25 @@
api("androidx.activity:activity:1.2.0")
api(ANDROIDX_TEST_EXT_JUNIT)
api(JUNIT)
+ api(KOTLIN_STDLIB)
+ api(KOTLIN_STDLIB_COMMON)
- implementation(KOTLIN_STDLIB)
- implementation(ANDROIDX_TEST_RULES)
- implementation(ANDROIDX_TEST_RUNNER)
+ implementation(project(":compose:animation:animation-core"))
+ implementation(project(":compose:runtime:runtime-saveable"))
+ implementation("androidx.annotation:annotation:1.1.0")
+ implementation("androidx.lifecycle:lifecycle-common:2.3.0")
+ implementation("androidx.lifecycle:lifecycle-runtime:2.3.0")
+ implementation(ANDROIDX_TEST_CORE)
+ implementation(ANDROIDX_TEST_MONITOR)
implementation(ESPRESSO_CORE)
- implementation(project(":compose:animation:animation"))
- implementation(project(":compose:foundation:foundation"))
-
- testImplementation(TRUTH)
+ implementation(ESPRESSO_IDLING_RESOURCE)
+ implementation(KOTLIN_COROUTINES_CORE)
+ implementation(KOTLIN_COROUTINES_TEST)
androidTestImplementation(project(":compose:test-utils"))
androidTestImplementation(project(":compose:material:material"))
- androidTestImplementation(project(":compose:foundation:foundation"))
+ androidTestImplementation(ANDROIDX_TEST_RULES)
+ androidTestImplementation(ANDROIDX_TEST_RUNNER)
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
@@ -67,38 +73,41 @@
sourceSets {
commonMain.dependencies {
- api(project(":compose:ui:ui-test"))
- implementation(project(":compose:animation:animation"))
- implementation(project(":compose:foundation:foundation"))
-
- implementation(KOTLIN_STDLIB_COMMON)
+ implementation(project(":compose:ui:ui-text"))
+ implementation(KOTLIN_STDLIB)
}
jvmMain.dependencies {
- implementation("androidx.collection:collection:1.1.0")
- implementation(JUNIT)
+ api(project(":compose:ui:ui-test"))
+ api(JUNIT)
+ api(KOTLIN_STDLIB)
+ api(KOTLIN_STDLIB_COMMON)
+
+ implementation("androidx.annotation:annotation:1.1.0")
+ implementation(KOTLIN_COROUTINES_CORE)
}
androidMain.dependencies {
- api(JUNIT)
- api(ANDROIDX_TEST_EXT_JUNIT)
api("androidx.activity:activity:1.2.0")
+ api(ANDROIDX_TEST_EXT_JUNIT)
- implementation(ANDROIDX_TEST_RULES)
- implementation(ANDROIDX_TEST_RUNNER)
+ implementation(project(":compose:animation:animation-core"))
+ implementation(project(":compose:runtime:runtime-saveable"))
+ implementation("androidx.lifecycle:lifecycle-common:2.3.0")
+ implementation("androidx.lifecycle:lifecycle-runtime:2.3.0")
+ implementation(ANDROIDX_TEST_CORE)
+ implementation(ANDROIDX_TEST_MONITOR)
implementation(ESPRESSO_CORE)
- }
-
- test.dependencies {
- implementation(TRUTH)
+ implementation(ESPRESSO_IDLING_RESOURCE)
+ implementation(KOTLIN_COROUTINES_TEST)
}
androidAndroidTest.dependencies {
- implementation(TRUTH)
implementation(project(":compose:test-utils"))
- implementation(project(":compose:foundation:foundation"))
implementation(project(":compose:material:material"))
-
+ implementation(ANDROIDX_TEST_RULES)
+ implementation(ANDROIDX_TEST_RUNNER)
+ implementation(TRUTH)
implementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
implementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
implementation(MOCKITO_KOTLIN, {
@@ -107,11 +116,11 @@
}
desktopMain.dependencies {
- implementation(JUNIT)
implementation(TRUTH)
implementation(SKIKO)
}
+ androidMain.dependsOn(jvmMain)
desktopMain.dependsOn(jvmMain)
jvmMain.dependsOn(commonMain)
}
diff --git a/compose/ui/ui-test/build.gradle b/compose/ui/ui-test/build.gradle
index 6848517..4fe73f6 100644
--- a/compose/ui/ui-test/build.gradle
+++ b/compose/ui/ui-test/build.gradle
@@ -32,27 +32,25 @@
dependencies {
kotlinPlugin(project(":compose:compiler:compiler"))
- if(!AndroidXUiPlugin.isMultiplatformEnabled(project)) {
+ if (!AndroidXUiPlugin.isMultiplatformEnabled(project)) {
+ api(project(":compose:runtime:runtime"))
api(project(":compose:ui:ui"))
api(project(":compose:ui:ui-graphics"))
- api(project(":compose:ui:ui-geometry"))
api(project(":compose:ui:ui-text"))
api(project(":compose:ui:ui-unit"))
- api(project(":compose:ui:ui-util"))
- api(project(":compose:runtime:runtime"))
+ api(KOTLIN_COROUTINES_CORE)
api(KOTLIN_COROUTINES_TEST)
+ api(KOTLIN_STDLIB)
+ api(KOTLIN_STDLIB_COMMON)
- implementation(KOTLIN_STDLIB)
+ implementation(project(":compose:ui:ui-util"))
+ implementation("androidx.annotation:annotation:1.1.0")
implementation(ESPRESSO_CORE)
- implementation("androidx.collection:collection:1.1.0")
- implementation(project(":compose:runtime:runtime"))
- testImplementation(TRUTH)
-
- androidTestImplementation(project(':compose:material:material'))
- androidTestImplementation(project(':compose:test-utils'))
- androidTestImplementation(project(':compose:ui:ui-test-junit4'))
androidTestImplementation(project(":activity:activity-compose"))
+ androidTestImplementation(project(":compose:material:material"))
+ androidTestImplementation(project(":compose:test-utils"))
+ androidTestImplementation(project(":compose:ui:ui-test-junit4"))
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
@@ -63,7 +61,7 @@
}
-if(AndroidXUiPlugin.isMultiplatformEnabled(project)) {
+if (AndroidXUiPlugin.isMultiplatformEnabled(project)) {
kotlin {
android()
jvm("desktop")
@@ -71,39 +69,33 @@
sourceSets {
commonMain.dependencies {
api(project(":compose:ui:ui"))
- api(project(":compose:ui:ui-geometry"))
api(project(":compose:ui:ui-text"))
api(project(":compose:ui:ui-unit"))
- api(project(":compose:ui:ui-util"))
+ api(KOTLIN_STDLIB)
- api(KOTLIN_COROUTINES_TEST)
-
- implementation(KOTLIN_STDLIB_COMMON)
- implementation(project(":compose:runtime:runtime"))
+ implementation(project(":compose:ui:ui-util"))
}
jvmMain.dependencies {
- implementation("androidx.collection:collection:1.1.0")
+ api(project(":compose:runtime:runtime"))
+ api(KOTLIN_COROUTINES_CORE)
+ api(KOTLIN_COROUTINES_TEST)
+ api(KOTLIN_STDLIB_COMMON)
}
androidMain.dependencies {
- api(project(":compose:runtime:runtime"))
api(project(":compose:ui:ui-graphics"))
+ implementation("androidx.annotation:annotation:1.1.0")
implementation(ESPRESSO_CORE)
}
- test.dependencies {
- implementation(TRUTH)
- }
-
androidAndroidTest.dependencies {
- implementation(TRUTH)
implementation(project(":compose:material:material"))
implementation(project(":compose:test-utils"))
implementation(project(":compose:ui:ui-test-junit4"))
implementation(project(":activity:activity-compose"))
-
+ implementation(TRUTH)
implementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
implementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
implementation(MOCKITO_KOTLIN, {
@@ -117,6 +109,7 @@
implementation(SKIKO)
}
+ androidMain.dependsOn(jvmMain)
desktopMain.dependsOn(jvmMain)
jvmMain.dependsOn(commonMain)
}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
index 68f644c..49ee7fc 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
@@ -32,7 +32,10 @@
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.requiredSize
+import androidx.compose.foundation.layout.size
import androidx.compose.foundation.selection.toggleable
import androidx.compose.foundation.text.BasicText
import androidx.compose.foundation.text.BasicTextField
@@ -42,6 +45,7 @@
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.AndroidComposeView
import androidx.compose.ui.platform.AndroidComposeViewAccessibilityDelegateCompat
+import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.semantics.SemanticsActions
import androidx.compose.ui.semantics.SemanticsProperties
@@ -55,6 +59,7 @@
import androidx.compose.ui.test.assertIsOn
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performSemanticsAction
import androidx.compose.ui.text.AnnotatedString
@@ -63,6 +68,7 @@
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Dialog
import androidx.core.view.ViewCompat
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -102,31 +108,8 @@
private lateinit var container: OpenComposeView
private lateinit var delegate: AndroidComposeViewAccessibilityDelegateCompat
private lateinit var provider: AccessibilityNodeProvider
- private lateinit var textLayoutResult: TextLayoutResult
private val argument = ArgumentCaptor.forClass(AccessibilityEvent::class.java)
- private var isTextFieldVisible by mutableStateOf(true)
- private var textFieldSelectionOne = false
- private var isPaneVisible by mutableStateOf(false)
- private var paneTestTitle by mutableStateOf(PaneTitleOne)
- private var textFieldValue = mutableStateOf(TextFieldValue(InitialText))
-
- companion object {
- private const val TimeOutInitialization: Long = 5000
- private const val TopColTag = "topColumn"
- private const val ToggleableTag = "toggleable"
- private const val DisabledToggleableTag = "disabledToggleable"
- private const val TextFieldTag = "textField"
- private const val TextNodeTag = "textNode"
- private const val ParentForOverlappedChildrenTag = "parentForOverlappedChildren"
- private const val OverlappedChildOneTag = "overlappedChildOne"
- private const val OverlappedChildTwoTag = "overlappedChildTwo"
- private const val PaneTag = "pane"
- private const val PaneTitleOne = "pane title one"
- private const val PaneTitleTwo = "pane title two"
- private const val InputText = "hello"
- private const val InitialText = "h"
- }
@Before
fun setup() {
@@ -144,77 +127,7 @@
}
activity.setContentView(container)
- container.setContent {
- var checked by remember { mutableStateOf(true) }
- var value by remember { textFieldValue }
- Column(Modifier.testTag(TopColTag)) {
- Box(
- Modifier
- .toggleable(value = checked, checked = it })
- .testTag(ToggleableTag)
- ) {
- BasicText("ToggleableText")
- Box {
- BasicText("TextNode", Modifier.testTag(TextNodeTag))
- }
- }
- Box(
- Modifier
- .toggleable(
- value = checked,
- enabled = false,
- checked = it }
- )
- .testTag(DisabledToggleableTag),
- content = {
- BasicText("ToggleableText")
- }
- )
- Box(Modifier.testTag(ParentForOverlappedChildrenTag)) {
- BasicText(
- "Child One",
- Modifier
- .zIndex(1f)
- .testTag(OverlappedChildOneTag)
- .requiredSize(50.dp)
- )
- BasicText(
- "Child Two",
- Modifier
- .testTag(OverlappedChildTwoTag)
- .requiredSize(50.dp)
- )
- }
- if (isTextFieldVisible) {
- BasicTextField(
- modifier = Modifier
- .semantics {
- // Make sure this block will be executed when selection changes.
- this.textSelectionRange = value.selection
- if (value.selection == TextRange(1)) {
- textFieldSelectionOne = true
- }
- }
- .testTag(TextFieldTag),
- value = value,
- value = it },
- textLayoutResult = it },
- visualTransformation = PasswordVisualTransformation(),
- decorationBox = {
- BasicText("Label")
- it()
- }
- )
- }
- }
- if (isPaneVisible) {
- Box(
- Modifier
- .testTag(PaneTag)
- .semantics { paneTitle = paneTestTitle }
- ) {}
- }
- }
+
androidComposeView = container.getChildAt(0) as AndroidComposeView
delegate = ViewCompat.getAccessibilityDelegate(androidComposeView) as
AndroidComposeViewAccessibilityDelegateCompat
@@ -225,9 +138,21 @@
}
@Test
- fun testCreateAccessibilityNodeInfo() {
- val toggleableNode = rule.onNodeWithTag(ToggleableTag)
- .fetchSemanticsNode("couldn't find node with tag $ToggleableTag")
+ fun testCreateAccessibilityNodeInfo_forToggleable() {
+ val tag = "Toggleable"
+ container.setContent {
+ var checked by remember { mutableStateOf(true) }
+ Box(
+ Modifier
+ .toggleable(value = checked, checked = it })
+ .testTag(tag)
+ ) {
+ BasicText("ToggleableText")
+ }
+ }
+
+ val toggleableNode = rule.onNodeWithTag(tag)
+ .fetchSemanticsNode("couldn't find node with tag $tag")
val accessibilityNodeInfo = provider.createAccessibilityNodeInfo(toggleableNode.id)
assertEquals("android.view.View", accessibilityNodeInfo.className)
val stateDescription = when {
@@ -255,13 +180,22 @@
@Test
fun testCreateAccessibilityNodeInfo_forTextField() {
- textFieldValue.value = TextFieldValue(InitialText)
- val textFieldNode = rule.onNodeWithTag(TextFieldTag)
- .fetchSemanticsNode("couldn't find node with tag $TextFieldTag")
+ val tag = "TextField"
+ container.setContent {
+ var value by remember { mutableStateOf(TextFieldValue("hello")) }
+ BasicTextField(
+ modifier = Modifier.testTag(tag),
+ value = value,
+ value = it }
+ )
+ }
+
+ val textFieldNode = rule.onNodeWithTag(tag)
+ .fetchSemanticsNode("couldn't find node with tag $tag")
val accessibilityNodeInfo = provider.createAccessibilityNodeInfo(textFieldNode.id)
assertEquals("android.widget.EditText", accessibilityNodeInfo.className)
- assertEquals(InitialText, accessibilityNodeInfo.text.toString())
+ assertEquals("hello", accessibilityNodeInfo.text.toString())
assertTrue(accessibilityNodeInfo.isFocusable)
assertFalse(accessibilityNodeInfo.isFocused)
assertTrue(accessibilityNodeInfo.isEditable)
@@ -307,21 +241,46 @@
@Test
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
fun reportedTexts_inTextFieldWithLabel_whenEditableTextNotEmpty() {
- textFieldValue.value = TextFieldValue(InitialText)
- val textFieldNode = rule.onNodeWithTag(TextFieldTag)
- .fetchSemanticsNode("couldn't find node with tag $TextFieldTag")
+ val tag = "TextField"
+
+ container.setContent {
+ BasicTextField(
+ modifier = Modifier.testTag(tag),
+ value = "hello",
+ >
+ decorationBox = {
+ BasicText("Label")
+ it()
+ }
+ )
+ }
+
+ val textFieldNode = rule.onNodeWithTag(tag)
+ .fetchSemanticsNode("couldn't find node with tag $tag")
val accessibilityNodeInfo = provider.createAccessibilityNodeInfo(textFieldNode.id)
- assertEquals(InitialText, accessibilityNodeInfo.text.toString())
+ assertEquals("hello", accessibilityNodeInfo.text.toString())
assertEquals("Label", accessibilityNodeInfo.hintText.toString())
}
@Test
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
fun reportedText_inTextFieldWithLabel_whenEditableTextEmpty() {
- textFieldValue.value = TextFieldValue()
- val textFieldNode = rule.onNodeWithTag(TextFieldTag)
- .fetchSemanticsNode("couldn't find node with tag $TextFieldTag")
+ val tag = "TextField"
+ container.setContent {
+ BasicTextField(
+ modifier = Modifier.testTag(tag),
+ value = "",
+ >
+ decorationBox = {
+ BasicText("Label")
+ it()
+ }
+ )
+ }
+
+ val textFieldNode = rule.onNodeWithTag(tag)
+ .fetchSemanticsNode("couldn't find node with tag $tag")
val accessibilityNodeInfo = provider.createAccessibilityNodeInfo(textFieldNode.id)
assertEquals("Label", accessibilityNodeInfo.text.toString())
@@ -330,38 +289,123 @@
@Test
fun testPerformAction_succeedOnEnabledNodes() {
- rule.onNodeWithTag(ToggleableTag)
+ val tag = "Toggleable"
+ container.setContent {
+ var checked by remember { mutableStateOf(true) }
+ Box(
+ Modifier
+ .toggleable(value = checked, checked = it })
+ .testTag(tag)
+ ) {
+ BasicText("ToggleableText")
+ }
+ }
+
+ rule.onNodeWithTag(tag)
.assertIsDisplayed()
.assertIsOn()
waitForSubtreeEventToSend()
- val toggleableNode = rule.onNodeWithTag(ToggleableTag)
- .fetchSemanticsNode("couldn't find node with tag $ToggleableTag")
+ val toggleableNode = rule.onNodeWithTag(tag)
+ .fetchSemanticsNode("couldn't find node with tag $tag")
rule.runOnUiThread {
assertTrue(provider.performAction(toggleableNode.id, ACTION_CLICK, null))
}
- rule.onNodeWithTag(ToggleableTag)
+ rule.onNodeWithTag(tag)
.assertIsOff()
+ }
- val textFieldNode = rule.onNodeWithTag(TextFieldTag)
+ @Test
+ fun testPerformAction_failOnDisabledNodes() {
+ val tag = "DisabledToggleable"
+ container.setContent {
+ var checked by remember { mutableStateOf(true) }
+ Box(
+ Modifier
+ .toggleable(
+ value = checked,
+ enabled = false,
+ checked = it }
+ )
+ .testTag(tag),
+ content = {
+ BasicText("ToggleableText")
+ }
+ )
+ }
+
+ rule.onNodeWithTag(tag)
.assertIsDisplayed()
- .fetchSemanticsNode("couldn't find node with tag $TextFieldTag")
+ .assertIsOn()
+
+ waitForSubtreeEventToSend()
+ val toggleableNode = rule.onNodeWithTag(tag)
+ .fetchSemanticsNode("couldn't find node with tag $tag")
+ rule.runOnUiThread {
+ assertFalse(provider.performAction(toggleableNode.id, ACTION_CLICK, null))
+ }
+ rule.onNodeWithTag(tag)
+ .assertIsOn()
+ }
+
+ @Test
+ fun testTextField_performClickAction_succeedOnEnabledNode() {
+ val tag = "TextField"
+ container.setContent {
+ BasicTextField(
+ modifier = Modifier.testTag(tag),
+ value = "value",
+ >
+ )
+ }
+
+ val textFieldNode = rule.onNodeWithTag(tag)
+ .assertIsDisplayed()
+ .fetchSemanticsNode("couldn't find node with tag $tag")
+
rule.runOnUiThread {
assertTrue(provider.performAction(textFieldNode.id, ACTION_CLICK, null))
}
- rule.onNodeWithTag(TextFieldTag)
+
+ rule.onNodeWithTag(tag)
.assert(SemanticsMatcher.expectValue(SemanticsProperties.Focused, true))
+ }
+
+ @Test
+ fun testTextField_performSetSelectionAction_succeedOnEnabledNode() {
+ val tag = "TextField"
+ var textFieldSelectionOne = false
+ container.setContent {
+ var value by remember { mutableStateOf(TextFieldValue("hello")) }
+ BasicTextField(
+ modifier = Modifier
+ .semantics {
+ // Make sure this block will be executed when selection changes.
+ this.textSelectionRange = value.selection
+ if (value.selection == TextRange(1)) {
+ textFieldSelectionOne = true
+ }
+ }
+ .testTag(tag),
+ value = value,
+ value = it }
+ )
+ }
+
+ val textFieldNode = rule.onNodeWithTag(tag)
+ .assertIsDisplayed()
+ .fetchSemanticsNode("couldn't find node with tag $tag")
val argument = Bundle()
argument.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, 1)
argument.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, 1)
+
rule.runOnUiThread {
textFieldSelectionOne = false
assertTrue(provider.performAction(textFieldNode.id, ACTION_SET_SELECTION, argument))
}
-
rule.waitUntil { textFieldSelectionOne }
- rule.onNodeWithTag(TextFieldTag)
+ rule.onNodeWithTag(tag)
.assert(
SemanticsMatcher.expectValue(
SemanticsProperties.TextSelectionRange,
@@ -371,26 +415,22 @@
}
@Test
- fun testPerformAction_failOnDisabledNodes() {
- rule.onNodeWithTag(DisabledToggleableTag)
- .assertIsDisplayed()
- .assertIsOn()
-
- waitForSubtreeEventToSend()
- val toggleableNode = rule.onNodeWithTag(DisabledToggleableTag)
- .fetchSemanticsNode("couldn't find node with tag $DisabledToggleableTag")
- rule.runOnUiThread {
- assertFalse(provider.performAction(toggleableNode.id, ACTION_CLICK, null))
- }
- rule.onNodeWithTag(DisabledToggleableTag)
- .assertIsOn()
- }
-
- @Test
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
fun testAddExtraDataToAccessibilityNodeInfo() {
- val textFieldNode = rule.onNodeWithTag(TextFieldTag)
- .fetchSemanticsNode("couldn't find node with tag $TextFieldTag")
+ val tag = "TextField"
+ lateinit var textLayoutResult: TextLayoutResult
+
+ container.setContent {
+ BasicTextField(
+ modifier = Modifier.testTag(tag),
+ value = "texy",
+ >
+ textLayoutResult = it }
+ )
+ }
+
+ val textFieldNode = rule.onNodeWithTag(tag)
+ .fetchSemanticsNode("couldn't find node with tag $tag")
val info = AccessibilityNodeInfo.obtain()
val argument = Bundle()
argument.putInt(AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX, 0)
@@ -406,8 +446,7 @@
assertEquals(1, data!!.size)
val rectF = data[0] as RectF
val expectedRect = textLayoutResult.getBoundingBox(0).translate(
- textFieldNode
- .positionInWindow
+ textFieldNode.positionInWindow
)
assertEquals(expectedRect.left, rectF.left)
assertEquals(expectedRect.top, rectF.top)
@@ -417,17 +456,29 @@
@Test
fun sendStateChangeEvent_whenClickToggleable() {
- rule.onNodeWithTag(ToggleableTag)
+ val tag = "Toggleable"
+ container.setContent {
+ var checked by remember { mutableStateOf(true) }
+ Box(
+ Modifier
+ .toggleable(value = checked, checked = it })
+ .testTag(tag)
+ ) {
+ BasicText("ToggleableText")
+ }
+ }
+
+ rule.onNodeWithTag(tag)
.assertIsDisplayed()
.assertIsOn()
waitForSubtreeEventToSend()
- rule.onNodeWithTag(ToggleableTag)
+ rule.onNodeWithTag(tag)
.performClick()
.assertIsOff()
- val toggleableNode = rule.onNodeWithTag(ToggleableTag)
- .fetchSemanticsNode("couldn't find node with tag $ToggleableTag")
+ val toggleableNode = rule.onNodeWithTag(tag)
+ .fetchSemanticsNode("couldn't find node with tag $tag")
val stateEvent = delegate.createEvent(
toggleableNode.id,
@@ -446,49 +497,59 @@
@Test
fun sendTextEvents_whenSetText() {
- textFieldValue.value = TextFieldValue(InitialText)
+ val tag = "TextField"
+ val initialText = "h"
+ val text = "hello"
+ container.setContent {
+ var value by remember { mutableStateOf(TextFieldValue(initialText)) }
+ BasicTextField(
+ modifier = Modifier.testTag(tag),
+ value = value,
+ value = it }
+ )
+ }
- rule.onNodeWithTag(TextFieldTag)
+ rule.onNodeWithTag(tag)
.assertIsDisplayed()
.assert(
SemanticsMatcher.expectValue(
SemanticsProperties.EditableText,
- AnnotatedString(InitialText)
+ AnnotatedString(initialText)
)
)
waitForSubtreeEventToSend()
- rule.onNodeWithTag(TextFieldTag)
- .performSemanticsAction(SemanticsActions.SetText) { it(AnnotatedString(InputText)) }
- rule.onNodeWithTag(TextFieldTag)
+ rule.onNodeWithTag(tag)
+ .performSemanticsAction(SemanticsActions.SetText) { it(AnnotatedString(text)) }
+ rule.onNodeWithTag(tag)
.assert(
SemanticsMatcher.expectValue(
SemanticsProperties.EditableText,
- AnnotatedString(InputText)
+ AnnotatedString(text)
)
)
- val textFieldNode = rule.onNodeWithTag(TextFieldTag)
- .fetchSemanticsNode("couldn't find node with tag $TextFieldTag")
+ val textFieldNode = rule.onNodeWithTag(tag)
+ .fetchSemanticsNode("couldn't find node with tag $tag")
val textEvent = delegate.createEvent(
textFieldNode.id,
AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED
)
- textEvent.fromIndex = InitialText.length
+ textEvent.fromIndex = initialText.length
textEvent.removedCount = 0
- textEvent.addedCount = InputText.length - InitialText.length
- textEvent.beforeText = InitialText
- textEvent.text.add(InputText)
+ textEvent.addedCount = text.length - initialText.length
+ textEvent.beforeText = initialText
+ textEvent.text.add(text)
val selectionEvent = delegate.createEvent(
textFieldNode.id,
AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED
)
- selectionEvent.fromIndex = InputText.length
- selectionEvent.toIndex = InputText.length
- selectionEvent.itemCount = InputText.length
- selectionEvent.text.add(InputText)
+ selectionEvent.fromIndex = text.length
+ selectionEvent.toIndex = text.length
+ selectionEvent.itemCount = text.length
+ selectionEvent.text.add(text)
rule.runOnIdle {
verify(container, atLeastOnce()).requestSendAccessibilityEvent(
@@ -503,9 +564,25 @@
@Test
@Ignore("b/177656801")
fun sendSubtreeChangeEvents_whenNodeRemoved() {
- val topColumn = rule.onNodeWithTag(TopColTag)
- .fetchSemanticsNode("couldn't find node with tag $TopColTag")
- rule.onNodeWithTag(TextFieldTag)
+ val columnTag = "topColumn"
+ val textFieldTag = "TextFieldTag"
+ var isTextFieldVisible by mutableStateOf(true)
+
+ container.setContent {
+ Column(Modifier.testTag(columnTag)) {
+ if (isTextFieldVisible) {
+ BasicTextField(
+ modifier = Modifier.testTag(textFieldTag),
+ value = "text",
+ >
+ )
+ }
+ }
+ }
+
+ val parentNode = rule.onNodeWithTag(columnTag)
+ .fetchSemanticsNode("couldn't find node with tag $columnTag")
+ rule.onNodeWithTag(textFieldTag)
.assertExists()
// wait for the subtree change events from initialization to send
waitForSubtreeEventToSendAndVerify {
@@ -513,7 +590,7 @@
eq(androidComposeView),
argThat(
ArgumentMatcher {
- getAccessibilityEventSourceSemanticsNodeId(it) == topColumn.id &&
+ getAccessibilityEventSourceSemanticsNodeId(it) == parentNode.id &&
it.eventType == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED &&
it.contentChangeTypes == AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE
}
@@ -524,14 +601,14 @@
// TextField is removed compared to setup.
isTextFieldVisible = false
- rule.onNodeWithTag(TextFieldTag)
+ rule.onNodeWithTag(textFieldTag)
.assertDoesNotExist()
waitForSubtreeEventToSendAndVerify {
verify(container, atLeastOnce()).requestSendAccessibilityEvent(
eq(androidComposeView),
argThat(
ArgumentMatcher {
- getAccessibilityEventSourceSemanticsNodeId(it) == topColumn.id &&
+ getAccessibilityEventSourceSemanticsNodeId(it) == parentNode.id &&
it.eventType == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED &&
it.contentChangeTypes == AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE
}
@@ -543,9 +620,25 @@
@Test
@Ignore("b/178524529")
fun traverseEventBeforeSelectionEvent_whenTraverseTextField() {
- val textFieldNode = rule.onNodeWithTag(TextFieldTag)
+ val tag = "TextFieldTag"
+ val text = "h"
+ container.setContent {
+ var value by remember { mutableStateOf(TextFieldValue(text)) }
+ BasicTextField(
+ modifier = Modifier.testTag(tag),
+ value = value,
+ value = it },
+ visualTransformation = PasswordVisualTransformation(),
+ decorationBox = {
+ BasicText("Label")
+ it()
+ }
+ )
+ }
+
+ val textFieldNode = rule.onNodeWithTag(tag)
.assertIsDisplayed()
- .fetchSemanticsNode("couldn't find node with tag $TextFieldTag")
+ .fetchSemanticsNode("couldn't find node with tag $tag")
waitForSubtreeEventToSend()
val args = Bundle()
@@ -566,10 +659,10 @@
textFieldNode.id,
AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED
)
- selectionEvent.fromIndex = InitialText.length
- selectionEvent.toIndex = InitialText.length
- selectionEvent.itemCount = InitialText.length
- selectionEvent.text.add(InitialText)
+ selectionEvent.fromIndex = text.length
+ selectionEvent.toIndex = text.length
+ selectionEvent.itemCount = text.length
+ selectionEvent.text.add(text)
val traverseEvent = delegate.createEvent(
textFieldNode.id,
@@ -580,7 +673,7 @@
traverseEvent.action = AccessibilityNodeInfoCompat.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
traverseEvent.movementGranularity =
AccessibilityNodeInfoCompat.MOVEMENT_GRANULARITY_CHARACTER
- traverseEvent.text.add(InitialText)
+ traverseEvent.text.add(text)
rule.runOnIdle {
verify(container, atLeastOnce()).requestSendAccessibilityEvent(
@@ -600,10 +693,25 @@
@Test
@Ignore("b/177656801")
fun semanticsNodeBeingMergedLayoutChange_sendThrottledSubtreeEventsForMergedSemanticsNode() {
- val toggleableNode = rule.onNodeWithTag(ToggleableTag)
- .fetchSemanticsNode("couldn't find node with tag $ToggleableTag")
- val textNode = rule.onNodeWithTag(TextNodeTag, useUnmergedTree = true)
- .fetchSemanticsNode("couldn't find node with tag $TextNodeTag")
+ val tag = "Toggleable"
+ container.setContent {
+ var checked by remember { mutableStateOf(true) }
+ Box(
+ Modifier
+ .toggleable(value = checked, checked = it })
+ .testTag(tag)
+ ) {
+ BasicText("ToggleableText")
+ Box {
+ BasicText("TextNode")
+ }
+ }
+ }
+
+ val toggleableNode = rule.onNodeWithTag(tag)
+ .fetchSemanticsNode("couldn't find node with tag $tag")
+ val textNode = rule.onNodeWithText("TextNode", useUnmergedTree = true)
+ .fetchSemanticsNode("couldn't find node with text TextNode")
// wait for the subtree change events from initialization to send
waitForSubtreeEventToSendAndVerify {
verify(container, atLeastOnce()).requestSendAccessibilityEvent(
@@ -642,10 +750,25 @@
@Test
@Ignore("b/177656801")
fun layoutNodeWithoutSemanticsLayoutChange_sendThrottledSubtreeEventsForMergedSemanticsNode() {
- val toggleableNode = rule.onNodeWithTag(ToggleableTag)
- .fetchSemanticsNode("couldn't find node with tag $ToggleableTag")
- val textNode = rule.onNodeWithTag(TextNodeTag, useUnmergedTree = true)
- .fetchSemanticsNode("couldn't find node with tag $TextNodeTag")
+ val tag = "Toggleable"
+ container.setContent {
+ var checked by remember { mutableStateOf(true) }
+ Box(
+ Modifier
+ .toggleable(value = checked, checked = it })
+ .testTag(tag)
+ ) {
+ BasicText("ToggleableText")
+ Box {
+ BasicText("TextNode")
+ }
+ }
+ }
+
+ val toggleableNode = rule.onNodeWithTag(tag)
+ .fetchSemanticsNode("couldn't find node with tag $tag")
+ val textNode = rule.onNodeWithText("TextNode", useUnmergedTree = true)
+ .fetchSemanticsNode("couldn't find node with text TextNode")
// wait for the subtree change events from initialization to send
waitForSubtreeEventToSendAndVerify {
verify(container, atLeastOnce()).requestSendAccessibilityEvent(
@@ -685,6 +808,18 @@
@Test
fun testGetVirtualViewAt() {
+ val tag = "Toggleable"
+ container.setContent {
+ var checked by remember { mutableStateOf(true) }
+ Box(
+ Modifier
+ .toggleable(value = checked, checked = it })
+ .testTag(tag)
+ ) {
+ BasicText("ToggleableText")
+ }
+ }
+
var rootNodeBoundsLeft = 0f
var rootNodeBoundsTop = 0f
rule.runOnIdle {
@@ -692,20 +827,53 @@
rootNodeBoundsLeft = rootNode.boundsInWindow.left
rootNodeBoundsTop = rootNode.boundsInWindow.top
}
- val toggleableNode = rule.onNodeWithTag(ToggleableTag)
- .fetchSemanticsNode("couldn't find node with tag $ToggleableTag")
+
+ val toggleableNode = rule.onNodeWithTag(tag)
+ .fetchSemanticsNode("couldn't find node with tag $tag")
val toggleableNodeBounds = toggleableNode.boundsInWindow
val toggleableNodeId = delegate.getVirtualViewAt(
(toggleableNodeBounds.left + toggleableNodeBounds.right) / 2 - rootNodeBoundsLeft,
(toggleableNodeBounds.top + toggleableNodeBounds.bottom) / 2 - rootNodeBoundsTop
)
- assertEquals(toggleableNode.id, toggleableNodeId)
- val overlappedChildOneNode = rule.onNodeWithTag(OverlappedChildOneTag)
- .fetchSemanticsNode("couldn't find node with tag $OverlappedChildOneTag")
- val overlappedChildTwoNode = rule.onNodeWithTag(OverlappedChildTwoTag)
- .fetchSemanticsNode("couldn't find node with tag $OverlappedChildTwoTag")
+ assertEquals(toggleableNode.id, toggleableNodeId)
+ }
+
+ @Test
+ fun testGetVirtualViewAt_overlappedChildren() {
+ val childOneTag = "OverlappedChildOne"
+ val childTwoTag = "OverlappedChildTwo"
+ container.setContent {
+ Box {
+ BasicText(
+ "Child One",
+ Modifier
+ .zIndex(1f)
+ .testTag(childOneTag)
+ .requiredSize(50.dp)
+ )
+ BasicText(
+ "Child Two",
+ Modifier
+ .testTag(childTwoTag)
+ .requiredSize(50.dp)
+ )
+ }
+ }
+
+ var rootNodeBoundsLeft = 0f
+ var rootNodeBoundsTop = 0f
+ rule.runOnIdle {
+ val rootNode = androidComposeView.semanticsOwner.rootSemanticsNode
+ rootNodeBoundsLeft = rootNode.boundsInWindow.left
+ rootNodeBoundsTop = rootNode.boundsInWindow.top
+ }
+
+ val overlappedChildOneNode = rule.onNodeWithTag(childOneTag)
+ .fetchSemanticsNode("couldn't find node with tag $childOneTag")
+ val overlappedChildTwoNode = rule.onNodeWithTag(childTwoTag)
+ .fetchSemanticsNode("couldn't find node with tag $childTwoTag")
val overlappedChildNodeBounds = overlappedChildTwoNode.boundsInWindow
val overlappedChildNodeId = delegate.getVirtualViewAt(
(overlappedChildNodeBounds.left + overlappedChildNodeBounds.right) / 2 -
@@ -713,18 +881,41 @@
(overlappedChildNodeBounds.top + overlappedChildNodeBounds.bottom) / 2 -
rootNodeBoundsTop
)
+
assertEquals(overlappedChildOneNode.id, overlappedChildNodeId)
assertNotEquals(overlappedChildTwoNode.id, overlappedChildNodeId)
}
@Test
fun testAccessibilityNodeInfoTreePruned() {
- val parentNode = rule.onNodeWithTag(ParentForOverlappedChildrenTag)
- .fetchSemanticsNode("couldn't find node with tag $ParentForOverlappedChildrenTag")
- val overlappedChildOneNode = rule.onNodeWithTag(OverlappedChildOneTag)
- .fetchSemanticsNode("couldn't find node with tag $OverlappedChildOneTag")
- val overlappedChildTwoNode = rule.onNodeWithTag(OverlappedChildTwoTag)
- .fetchSemanticsNode("couldn't find node with tag $OverlappedChildTwoTag")
+ val parentTag = "ParentForOverlappedChildren"
+ val childOneTag = "OverlappedChildOne"
+ val childTwoTag = "OverlappedChildTwo"
+ container.setContent {
+ Box(Modifier.testTag(parentTag)) {
+ BasicText(
+ "Child One",
+ Modifier
+ .zIndex(1f)
+ .testTag(childOneTag)
+ .requiredSize(50.dp)
+ )
+ BasicText(
+ "Child Two",
+ Modifier
+ .testTag(childTwoTag)
+ .requiredSize(50.dp)
+ )
+ }
+ }
+
+ val parentNode = rule.onNodeWithTag(parentTag)
+ .fetchSemanticsNode("couldn't find node with tag $parentTag")
+ val overlappedChildOneNode = rule.onNodeWithTag(childOneTag)
+ .fetchSemanticsNode("couldn't find node with tag $childOneTag")
+ val overlappedChildTwoNode = rule.onNodeWithTag(childTwoTag)
+ .fetchSemanticsNode("couldn't find node with tag $childTwoTag")
+
assertEquals(1, provider.createAccessibilityNodeInfo(parentNode.id).childCount)
assertEquals(
"Child One",
@@ -735,18 +926,33 @@
@Test
fun testPaneAppear() {
- rule.onNodeWithTag(PaneTag).assertDoesNotExist()
+ val paneTag = "Pane"
+ var isPaneVisible by mutableStateOf(false)
+ val paneTestTitle by mutableStateOf("pane title")
+
+ container.setContent {
+ if (isPaneVisible) {
+ Box(
+ Modifier
+ .testTag(paneTag)
+ .semantics { paneTitle = paneTestTitle }
+ ) {}
+ }
+ }
+
+ rule.onNodeWithTag(paneTag).assertDoesNotExist()
+
isPaneVisible = true
- rule.onNodeWithTag(PaneTag)
+ rule.onNodeWithTag(paneTag)
.assert(
SemanticsMatcher.expectValue(
SemanticsProperties.PaneTitle,
- PaneTitleOne
+ "pane title"
)
)
.assertIsDisplayed()
waitForSubtreeEventToSend()
- val paneNode = rule.onNodeWithTag(PaneTag).fetchSemanticsNode()
+ val paneNode = rule.onNodeWithTag(paneTag).fetchSemanticsNode()
rule.runOnIdle {
verify(container, times(1)).requestSendAccessibilityEvent(
eq(androidComposeView),
@@ -764,26 +970,42 @@
@Test
fun testPaneTitleChange() {
- rule.onNodeWithTag(PaneTag).assertDoesNotExist()
+ val paneTag = "Pane"
+ var isPaneVisible by mutableStateOf(false)
+ var paneTestTitle by mutableStateOf("pane title")
+
+ container.setContent {
+ if (isPaneVisible) {
+ Box(
+ Modifier
+ .testTag(paneTag)
+ .semantics { paneTitle = paneTestTitle }
+ ) {}
+ }
+ }
+
+ rule.onNodeWithTag(paneTag).assertDoesNotExist()
+
isPaneVisible = true
- rule.onNodeWithTag(PaneTag)
+ rule.onNodeWithTag(paneTag)
.assert(
SemanticsMatcher.expectValue(
SemanticsProperties.PaneTitle,
- PaneTitleOne
+ "pane title"
)
)
.assertIsDisplayed()
waitForSubtreeEventToSend()
- val paneNode = rule.onNodeWithTag(PaneTag).fetchSemanticsNode()
- paneTestTitle = PaneTitleTwo
- rule.onNodeWithTag(PaneTag)
+
+ paneTestTitle = "new pane title"
+ rule.onNodeWithTag(paneTag)
.assert(
SemanticsMatcher.expectValue(
SemanticsProperties.PaneTitle,
- PaneTitleTwo
+ "new pane title"
)
)
+ val paneNode = rule.onNodeWithTag(paneTag).fetchSemanticsNode()
rule.runOnIdle {
verify(container, times(1)).requestSendAccessibilityEvent(
eq(androidComposeView),
@@ -801,19 +1023,31 @@
@Test
fun testPaneDisappear() {
- rule.onNodeWithTag(PaneTag).assertDoesNotExist()
+ val paneTag = "Pane"
+ var isPaneVisible by mutableStateOf(false)
+ val paneTestTitle by mutableStateOf("pane title")
+
+ container.setContent {
+ if (isPaneVisible) {
+ Box(Modifier.testTag(paneTag).semantics { paneTitle = paneTestTitle }) {}
+ }
+ }
+
+ rule.onNodeWithTag(paneTag).assertDoesNotExist()
+
isPaneVisible = true
- rule.onNodeWithTag(PaneTag)
+ rule.onNodeWithTag(paneTag)
.assert(
SemanticsMatcher.expectValue(
SemanticsProperties.PaneTitle,
- PaneTitleOne
+ "pane title"
)
)
.assertIsDisplayed()
waitForSubtreeEventToSend()
+
isPaneVisible = false
- rule.onNodeWithTag(PaneTag).assertDoesNotExist()
+ rule.onNodeWithTag(paneTag).assertDoesNotExist()
rule.runOnIdle {
verify(container, times(1)).requestSendAccessibilityEvent(
eq(androidComposeView),
@@ -830,9 +1064,18 @@
@Test
fun testEventForPasswordTextField() {
- val textFieldNode = rule.onNodeWithTag(TextFieldTag)
- .fetchSemanticsNode("Couldn't fetch node with tag $TextFieldTag")
+ val tag = "TextField"
+ container.setContent {
+ BasicTextField(
+ modifier = Modifier.testTag(tag),
+ value = "value",
+ >
+ visualTransformation = PasswordVisualTransformation()
+ )
+ }
+ val textFieldNode = rule.onNodeWithTag(tag)
+ .fetchSemanticsNode("Couldn't fetch node with tag $tag")
val event = delegate.createEvent(
textFieldNode.id,
AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED
@@ -841,6 +1084,54 @@
assertTrue(event.isPassword)
}
+ @Test
+ fun testDialog_setCorrectBounds() {
+ var dialogComposeView: AndroidComposeView? = null
+ container.setContent {
+ Dialog( {
+ dialogComposeView = LocalView.current as AndroidComposeView
+ delegate = ViewCompat.getAccessibilityDelegate(dialogComposeView!!) as
+ AndroidComposeViewAccessibilityDelegateCompat
+
+ Box(Modifier.size(300.dp)) {
+ BasicText(
+ text = "text",
+ modifier = Modifier.offset(10.dp, 10.dp).fillMaxSize()
+ )
+ }
+ }
+ }
+
+ val textNode = rule.onNodeWithText("text").fetchSemanticsNode()
+ val info = AccessibilityNodeInfoCompat.obtain()
+ delegate.populateAccessibilityNodeInfoProperties(
+ textNode.id,
+ info,
+ textNode
+ )
+
+ val viewPosition = intArrayOf(0, 0)
+ dialogComposeView!!.getLocationOnScreen(viewPosition)
+ with(rule.density) {
+ val offset = 10.dp.roundToPx()
+ val size = 300.dp.roundToPx()
+ val textPositionOnScreenX = viewPosition[0] + offset
+ val textPositionOnScreenY = viewPosition[1] + offset
+
+ val textRect = android.graphics.Rect()
+ info.getBoundsInScreen(textRect)
+ assertEquals(
+ android.graphics.Rect(
+ textPositionOnScreenX,
+ textPositionOnScreenY,
+ textPositionOnScreenX + size,
+ textPositionOnScreenY + size
+ ),
+ textRect
+ )
+ }
+ }
+
private fun eventIndex(list: List<AccessibilityEvent>, event: AccessibilityEvent): Int {
for (i in list.indices) {
if (ReflectionEquals(list[i], null).matches(event)) {
@@ -870,7 +1161,7 @@
private fun waitForSubtreeEventToSend() {
// When the subtree events are sent, we will also update our previousSemanticsNodes,
// which will affect our next accessibility events from semantics tree comparison.
- rule.mainClock.advanceTimeBy(TimeOutInitialization)
+ rule.mainClock.advanceTimeBy(5000)
rule.waitForIdle()
}
}
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofill.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofill.android.kt
index 9c57132..59a95ba 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofill.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofill.android.kt
@@ -21,8 +21,10 @@
import android.util.SparseArray
import android.view.View
import android.view.ViewStructure
+import android.view.autofill.AutofillId
import android.view.autofill.AutofillManager
import android.view.autofill.AutofillValue
+import androidx.annotation.DoNotInline
import androidx.annotation.RequiresApi
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.graphics.toAndroidRect
@@ -70,14 +72,21 @@
internal fun AndroidAutofill.populateViewStructure(root: ViewStructure) {
// Add child nodes. The function returns the index to the first item.
- var index = root.addChildCount(autofillTree.children.count())
+ var index = AutofillApi23Helper.addChildCount(root, autofillTree.children.count())
for ((id, autofillNode) in autofillTree.children) {
- root.newChild(index)?.apply {
- setAutofillId(root.autofillId!!, id)
- setId(id, view.context.packageName, null, null)
- setAutofillType(View.AUTOFILL_TYPE_TEXT)
- setAutofillHints(autofillNode.autofillTypes.map { it.androidType }.toTypedArray())
+ AutofillApi23Helper.newChild(root, index)?.also { child ->
+ AutofillApi26Helper.setAutofillId(
+ child,
+ AutofillApi26Helper.getAutofillId(root)!!,
+ id
+ )
+ AutofillApi23Helper.setId(child, id, view.context.packageName, null, null)
+ AutofillApi26Helper.setAutofillType(child, View.AUTOFILL_TYPE_TEXT)
+ AutofillApi26Helper.setAutofillHints(
+ child,
+ autofillNode.autofillTypes.map { it.androidType }.toTypedArray()
+ )
if (autofillNode.boundingBox == null) {
// Do we need an exception here? warning? silently ignore? If the boundingbox is
@@ -89,7 +98,7 @@
)
}
autofillNode.boundingBox?.toAndroidRect()?.run {
- setDimens(left, top, 0, 0, width(), height())
+ AutofillApi23Helper.setDimens(child, left, top, 0, 0, width(), height())
}
}
index++
@@ -106,10 +115,102 @@
val itemId = values.keyAt(index)
val value = values[itemId]
when {
- value.isText -> autofillTree.performAutofill(itemId, value.textValue.toString())
- value.isDate -> TODO("b/138604541: Add onFill() callback for date")
- value.isList -> TODO("b/138604541: Add onFill() callback for list")
- value.isToggle -> TODO("b/138604541: Add onFill() callback for toggle")
+ AutofillApi26Helper.isText(value) -> autofillTree.performAutofill(
+ itemId,
+ AutofillApi26Helper.textValue(value).toString()
+ )
+ AutofillApi26Helper.isDate(value) ->
+ TODO("b/138604541: Add onFill() callback for date")
+ AutofillApi26Helper.isList(value) ->
+ TODO("b/138604541: Add onFill() callback for list")
+ AutofillApi26Helper.isToggle(value) ->
+ TODO("b/138604541: Add onFill() callback for toggle")
}
}
+}
+
+/**
+ * This class is here to ensure that the classes that use this API will get verified and can be
+ * AOT compiled. It is expected that this class will soft-fail verification, but the classes
+ * which use this method will pass.
+ */
+@RequiresApi(26)
+internal object AutofillApi26Helper {
+ @RequiresApi(26)
+ @DoNotInline
+ fun setAutofillId(structure: ViewStructure, parent: AutofillId, virtualId: Int) =
+ structure.setAutofillId(parent, virtualId)
+
+ @RequiresApi(26)
+ @DoNotInline
+ fun getAutofillId(structure: ViewStructure) = structure.autofillId
+
+ @RequiresApi(26)
+ @DoNotInline
+ fun setAutofillType(structure: ViewStructure, type: Int) = structure.setAutofillType(type)
+
+ @RequiresApi(26)
+ @DoNotInline
+ fun setAutofillHints(structure: ViewStructure, hints: Array<String>) =
+ structure.setAutofillHints(hints)
+
+ @RequiresApi(26)
+ @DoNotInline
+ fun isText(value: AutofillValue) = value.isText
+
+ @RequiresApi(26)
+ @DoNotInline
+ fun isDate(value: AutofillValue) = value.isDate
+
+ @RequiresApi(26)
+ @DoNotInline
+ fun isList(value: AutofillValue) = value.isList
+
+ @RequiresApi(26)
+ @DoNotInline
+ fun isToggle(value: AutofillValue) = value.isToggle
+
+ @RequiresApi(26)
+ @DoNotInline
+ fun textValue(value: AutofillValue): CharSequence = value.textValue
+}
+
+/**
+ * This class is here to ensure that the classes that use this API will get verified and can be
+ * AOT compiled. It is expected that this class will soft-fail verification, but the classes
+ * which use this method will pass.
+ */
+@RequiresApi(23)
+internal object AutofillApi23Helper {
+ @RequiresApi(23)
+ @DoNotInline
+ fun newChild(structure: ViewStructure, index: Int): ViewStructure? =
+ structure.newChild(index)
+
+ @RequiresApi(23)
+ @DoNotInline
+ fun addChildCount(structure: ViewStructure, num: Int) =
+ structure.addChildCount(num)
+
+ @RequiresApi(23)
+ @DoNotInline
+ fun setId(
+ structure: ViewStructure,
+ id: Int,
+ packageName: String?,
+ typeName: String?,
+ entryName: String?
+ ) = structure.setId(id, packageName, typeName, entryName)
+
+ @RequiresApi(23)
+ @DoNotInline
+ fun setDimens(
+ structure: ViewStructure,
+ left: Int,
+ top: Int,
+ scrollX: Int,
+ scrollY: Int,
+ width: Int,
+ height: Int
+ ) = structure.setDimens(left, top, scrollX, scrollY, width, height)
}
\ No newline at end of file
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofillDebugUtils.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofillDebugUtils.android.kt
index 839587ce..79ada4f 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofillDebugUtils.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofillDebugUtils.android.kt
@@ -20,6 +20,7 @@
import android.util.Log
import android.view.View
import android.view.autofill.AutofillManager
+import androidx.annotation.DoNotInline
import androidx.annotation.RequiresApi
import androidx.compose.ui.ExperimentalComposeUiApi
@@ -61,6 +62,7 @@
*/
@ExperimentalComposeUiApi
@RequiresApi(Build.VERSION_CODES.O)
+@DoNotInline
internal fun AndroidAutofill.registerCallback() {
autofillManager.registerCallback(AutofillCallback)
}
@@ -70,6 +72,7 @@
*/
@ExperimentalComposeUiApi
@RequiresApi(Build.VERSION_CODES.O)
+@DoNotInline
internal fun AndroidAutofill.unregisterCallback() {
autofillManager.unregisterCallback(AutofillCallback)
}
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
index d9de0d8..6f66a7b 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
@@ -33,6 +33,7 @@
import android.view.autofill.AutofillValue
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputConnection
+import androidx.annotation.DoNotInline
import androidx.annotation.RequiresApi
import androidx.annotation.RestrictTo
import androidx.compose.runtime.getValue
@@ -307,9 +308,11 @@
setWillNotDraw(false)
isFocusable = true
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- focusable = View.FOCUSABLE
- // not to add the default focus highlight to the whole compose view
- defaultFocusHighlightEnabled = false
+ AndroidComposeViewVerificationHelperMethods.focusable(
+ this,
+ focusable = View.FOCUSABLE,
+ defaultFocusHighlightEnabled = false
+ )
}
isFocusableInTouchMode = true
clipChildren = false
@@ -925,4 +928,20 @@
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@InternalComposeUiApi // used by testing infra
var textInputServiceFactory: (PlatformTextInputService) -> TextInputService =
- { TextInputService(it) }
\ No newline at end of file
+ { TextInputService(it) }
+
+/**
+ * This class is here to ensure that the classes that use this API will get verified and can be
+ * AOT compiled. It is expected that this class will soft-fail verification, but the classes
+ * which use this method will pass.
+ */
+@RequiresApi(Build.VERSION_CODES.O)
+internal object AndroidComposeViewVerificationHelperMethods {
+ @RequiresApi(Build.VERSION_CODES.O)
+ @DoNotInline
+ fun focusable(view: View, focusable: Int, defaultFocusHighlightEnabled: Boolean) {
+ view.focusable = focusable
+ // not to add the default focus highlight to the whole compose view
+ view.defaultFocusHighlightEnabled = defaultFocusHighlightEnabled
+ }
+}
\ No newline at end of file
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
index cfa7cd7..91251f3 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
@@ -33,6 +33,7 @@
import android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX
import android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY
import android.view.accessibility.AccessibilityNodeProvider
+import androidx.annotation.DoNotInline
import androidx.annotation.IntRange
import androidx.annotation.RequiresApi
import androidx.annotation.VisibleForTesting
@@ -268,12 +269,15 @@
}
info.packageName = view.context.packageName
try {
+ val boundsInRoot = semanticsNode.boundsInRoot
+ val topLeftInScreen = view.localToScreen(boundsInRoot.topLeft)
+ val bottomRightInScreen = view.localToScreen(boundsInRoot.bottomRight)
info.setBoundsInScreen(
android.graphics.Rect(
- semanticsNode.boundsInWindow.left.toInt(),
- semanticsNode.boundsInWindow.top.toInt(),
- semanticsNode.boundsInWindow.right.toInt(),
- semanticsNode.boundsInWindow.bottom.toInt()
+ topLeftInScreen.x.toInt(),
+ topLeftInScreen.y.toInt(),
+ bottomRightInScreen.x.toInt(),
+ bottomRightInScreen.y.toInt()
)
)
} catch (e: IllegalStateException) {
@@ -427,7 +431,10 @@
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !info.text.isNullOrEmpty() &&
semanticsNode.config.contains(SemanticsActions.GetTextLayoutResult)
) {
- info.unwrap().availableExtraData = listOf(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY)
+ AccessibilityNodeInfoVerificationHelperMethods.setAvailableExtraData(
+ info.unwrap(),
+ listOf(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY)
+ )
}
val rangeInfo =
@@ -2090,4 +2097,18 @@
findAllSemanticNodesRecursive(root)
return nodes
+}
+
+/**
+ * This class is here to ensure that the classes that use this API will get verified and can be
+ * AOT compiled. It is expected that this class will soft-fail verification, but the classes
+ * which use this method will pass.
+ */
+@RequiresApi(Build.VERSION_CODES.O)
+internal object AccessibilityNodeInfoVerificationHelperMethods {
+ @RequiresApi(Build.VERSION_CODES.O)
+ @DoNotInline
+ fun setAvailableExtraData(node: AccessibilityNodeInfo, data: List<String>) {
+ node.availableExtraData = data
+ }
}
\ No newline at end of file
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/Wrapper.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/Wrapper.android.kt
index 179dcbf..ee209af 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/Wrapper.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/Wrapper.android.kt
@@ -19,7 +19,9 @@
import android.util.Log
import android.view.View
import android.view.ViewGroup
+import androidx.annotation.DoNotInline
import androidx.annotation.MainThread
+import androidx.annotation.RequiresApi
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Composition
import androidx.compose.runtime.CompositionContext
@@ -205,4 +207,17 @@
*/
private fun inspectionWanted(owner: AndroidComposeView): Boolean =
Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q &&
- owner.attributeSourceResourceMap.isNotEmpty()
+ WrapperVerificationHelperMethods.attributeSourceResourceMap(owner).isNotEmpty()
+
+/**
+ * This class is here to ensure that the classes that use this API will get verified and can be
+ * AOT compiled. It is expected that this class will soft-fail verification, but the classes
+ * which use this method will pass.
+ */
+@RequiresApi(Build.VERSION_CODES.Q)
+internal object WrapperVerificationHelperMethods {
+ @RequiresApi(Build.VERSION_CODES.Q)
+ @DoNotInline
+ fun attributeSourceResourceMap(view: View): Map<Int, Int> =
+ view.attributeSourceResourceMap
+}
\ No newline at end of file
diff --git a/docs-public/build.gradle b/docs-public/build.gradle
index 6c0bc87..eb363ba 100644
--- a/docs-public/build.gradle
+++ b/docs-public/build.gradle
@@ -105,9 +105,9 @@
docs("androidx.enterprise:enterprise-feedback:1.1.0")
docs("androidx.enterprise:enterprise-feedback-testing:1.1.0")
docs("androidx.exifinterface:exifinterface:1.3.2")
- docs("androidx.fragment:fragment:1.3.0")
- docs("androidx.fragment:fragment-ktx:1.3.0")
- docs("androidx.fragment:fragment-testing:1.3.0")
+ docs("androidx.fragment:fragment:1.3.1")
+ docs("androidx.fragment:fragment-ktx:1.3.1")
+ docs("androidx.fragment:fragment-testing:1.3.1")
docs("androidx.gridlayout:gridlayout:1.0.0")
docs("androidx.heifwriter:heifwriter:1.1.0-alpha01")
docs("androidx.hilt:hilt-common:1.0.0-alpha03")
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 bcd5e7d..8412469 100644
--- a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt
+++ b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt
@@ -26,6 +26,8 @@
import android.view.View
import androidx.activity.ComponentActivity
import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.ViewModelStore
+import androidx.lifecycle.testing.TestLifecycleOwner
import androidx.navigation.test.R
import androidx.test.annotation.UiThreadTest
import androidx.test.core.app.ActivityScenario
@@ -225,6 +227,58 @@
@UiThreadTest
@Test
+ fun testSetViewModelStoreOwnerAfterGraphSet() {
+ 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)
+
+ try {
+ navController.setViewModelStore(ViewModelStore())
+ } catch (e: IllegalStateException) {
+ assertThat(e).hasMessageThat().contains(
+ "ViewModelStore should be set before setGraph call"
+ )
+ }
+ }
+
+ @UiThreadTest
+ @Test
+ fun testSetSameViewModelStoreOwnerAfterGraphSet() {
+ val navController = createNavController()
+ val viewModelStore = ViewModelStore()
+ navController.setViewModelStore(viewModelStore)
+ val navGraph = navController.navigatorProvider.navigation(
+ id = 1,
+ startDestination = R.id.start_test
+ ) {
+ test(R.id.start_test)
+ }
+ navController.setGraph(navGraph, null)
+
+ navController.setViewModelStore(viewModelStore)
+ }
+
+ @UiThreadTest
+ @Test
+ fun testSetSameLifecycleOwner() {
+ val navController = createNavController()
+ val lifecycleOwner = TestLifecycleOwner(Lifecycle.State.RESUMED)
+
+ navController.setLifecycleOwner(lifecycleOwner)
+ assertThat(lifecycleOwner.observerCount).isEqualTo(1)
+
+ navController.setLifecycleOwner(lifecycleOwner)
+ assertThat(lifecycleOwner.observerCount).isEqualTo(1)
+ }
+
+ @UiThreadTest
+ @Test
fun testNavigate() {
val navController = createNavController()
navController.setGraph(R.navigation.nav_simple)
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 6850ae1..eae7d8f 100644
--- a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.java
+++ b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.java
@@ -1279,6 +1279,9 @@
}
void setLifecycleOwner(@NonNull LifecycleOwner owner) {
+ if (owner == mLifecycleOwner) {
+ return;
+ }
mLifecycleOwner = owner;
mLifecycleOwner.getLifecycle().addObserver(mLifecycleObserver);
}
@@ -1305,6 +1308,9 @@
}
void setViewModelStore(@NonNull ViewModelStore viewModelStore) {
+ if (mViewModel == NavControllerViewModel.getInstance(viewModelStore)) {
+ return;
+ }
if (!mBackStack.isEmpty()) {
throw new IllegalStateException("ViewModelStore should be set before setGraph call");
}
diff --git a/paging/common/build.gradle b/paging/common/build.gradle
index 61339c5..4d61a3f 100644
--- a/paging/common/build.gradle
+++ b/paging/common/build.gradle
@@ -52,7 +52,6 @@
mavenGroup = LibraryGroups.PAGING
inceptionYear = "2017"
description = "Android Paging-Common"
- legacyDisableKotlinStrictApiMode = true
}
// Allow usage of Kotlin's @OptIn.
diff --git a/paging/common/src/main/kotlin/androidx/paging/CachedPagingData.kt b/paging/common/src/main/kotlin/androidx/paging/CachedPagingData.kt
index d43cccf..6f6177f 100644
--- a/paging/common/src/main/kotlin/androidx/paging/CachedPagingData.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/CachedPagingData.kt
@@ -75,9 +75,9 @@
* @param scope The coroutine scope where this page cache will be kept alive.
*/
@CheckResult
-fun <T : Any> Flow<PagingData<T>>.cachedIn(
+public fun <T : Any> Flow<PagingData<T>>.cachedIn(
scope: CoroutineScope
-) = cachedIn(scope, null)
+): Flow<PagingData<T>> = cachedIn(scope, null)
internal fun <T : Any> Flow<PagingData<T>>.cachedIn(
scope: CoroutineScope,
diff --git a/paging/common/src/main/kotlin/androidx/paging/CombinedLoadStates.kt b/paging/common/src/main/kotlin/androidx/paging/CombinedLoadStates.kt
index 19efffd..4714cc2 100644
--- a/paging/common/src/main/kotlin/androidx/paging/CombinedLoadStates.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/CombinedLoadStates.kt
@@ -19,7 +19,7 @@
/**
* Collection of pagination [LoadState]s for both a [PagingSource], and [RemoteMediator].
*/
-class CombinedLoadStates(
+public class CombinedLoadStates(
/**
* Convenience for combined behavior of [REFRESH][LoadType.REFRESH] [LoadState], which
* generally defers to [mediator] if it exists, but if previously was [LoadState.Loading],
@@ -30,7 +30,7 @@
* specifically, e.g., showing cached data when network loads via [mediator] fail,
* [LoadStates] exposed via [source] and [mediator] should be used directly.
*/
- val refresh: LoadState,
+ public val refresh: LoadState,
/**
* Convenience for combined behavior of [PREPEND][LoadType.REFRESH] [LoadState], which
* generally defers to [mediator] if it exists, but if previously was [LoadState.Loading],
@@ -41,7 +41,7 @@
* specifically, e.g., showing cached data when network loads via [mediator] fail,
* [LoadStates] exposed via [source] and [mediator] should be used directly.
*/
- val prepend: LoadState,
+ public val prepend: LoadState,
/**
* Convenience for combined behavior of [APPEND][LoadType.REFRESH] [LoadState], which
* generally defers to [mediator] if it exists, but if previously was [LoadState.Loading],
@@ -52,17 +52,17 @@
* specifically, e.g., showing cached data when network loads via [mediator] fail,
* [LoadStates] exposed via [source] and [mediator] should be used directly.
*/
- val append: LoadState,
+ public val append: LoadState,
/**
* [LoadStates] corresponding to loads from a [PagingSource].
*/
- val source: LoadStates,
+ public val source: LoadStates,
/**
* [LoadStates] corresponding to loads from a [RemoteMediator], or `null` if [RemoteMediator]
* not present.
*/
- val mediator: LoadStates? = null,
+ public val mediator: LoadStates? = null,
) {
override fun equals(other: Any?): Boolean {
diff --git a/paging/common/src/main/kotlin/androidx/paging/ContiguousPagedList.kt b/paging/common/src/main/kotlin/androidx/paging/ContiguousPagedList.kt
index e7d9382..777d96a 100644
--- a/paging/common/src/main/kotlin/androidx/paging/ContiguousPagedList.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/ContiguousPagedList.kt
@@ -34,7 +34,7 @@
*/
@Suppress("DEPRECATION")
@RestrictTo(RestrictTo.Scope.LIBRARY)
-open class ContiguousPagedList<K : Any, V : Any>(
+public open class ContiguousPagedList<K : Any, V : Any>(
final override val pagingSource: PagingSource<K, V>,
coroutineScope: CoroutineScope,
notifyDispatcher: CoroutineDispatcher,
@@ -52,6 +52,7 @@
),
PagedStorage.Callback,
LegacyPageFetcher.PageConsumer<V> {
+
internal companion object {
internal fun getPrependItemsRequested(
prefetchDistance: Int,
@@ -102,7 +103,7 @@
?: initialLastKey
}
- override val isDetached
+ override val isDetached: Boolean
get() = pager.isDetached
/**
@@ -368,7 +369,7 @@
tryDispatchBoundaryCallbacks(true)
}
- override fun detach() = pager.detach()
+ override fun detach(): Unit = pager.detach()
@MainThread
override fun onInitialized(count: Int) {
@@ -399,8 +400,11 @@
notifyInserted(endPosition + changed, added)
}
- override fun onPagesRemoved(startOfDrops: Int, count: Int) = notifyRemoved(startOfDrops, count)
+ override fun onPagesRemoved(startOfDrops: Int, count: Int) {
+ notifyRemoved(startOfDrops, count)
+ }
- override fun onPagesSwappedToPlaceholder(startOfDrops: Int, count: Int) =
+ override fun onPagesSwappedToPlaceholder(startOfDrops: Int, count: Int) {
notifyChanged(startOfDrops, count)
+ }
}
diff --git a/paging/common/src/main/kotlin/androidx/paging/DataSource.kt b/paging/common/src/main/kotlin/androidx/paging/DataSource.kt
index cbceeae..500558c 100644
--- a/paging/common/src/main/kotlin/androidx/paging/DataSource.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/DataSource.kt
@@ -96,7 +96,7 @@
* position in data set. Note - this is distinct from e.g. Room's `<Value> Value type
* loaded by the DataSource.
*/
-abstract class DataSource<Key : Any, Value : Any>
+public abstract class DataSource<Key : Any, Value : Any>
// Since we currently rely on implementation details of two implementations, prevent external
// subclassing, except through exposed subclasses.
internal constructor(internal val type: KeyType) {
@@ -109,7 +109,7 @@
/**
* @return `true` if the data source is invalid, and can no longer be queried for data.
*/
- open val isInvalid
+ public open val isInvalid: Boolean
@WorkerThread
get() = _invalid.get()
@@ -135,7 +135,7 @@
* @param Key Key identifying items in DataSource.
* @param Value Type of items in the list loaded by the DataSources.
*/
- abstract class Factory<Key : Any, Value : Any> {
+ public abstract class Factory<Key : Any, Value : Any> {
/**
* Create a [DataSource].
*
@@ -149,7 +149,7 @@
*
* @return the new DataSource.
*/
- abstract fun create(): DataSource<Key, Value>
+ public abstract fun create(): DataSource<Key, Value>
/**
* Applies the given function to each value emitted by DataSources produced by this Factory.
@@ -165,8 +165,11 @@
* @see DataSource.map
* @see DataSource.mapByPage
*/
- open fun <ToValue : Any> map(function: Function<Value, ToValue>): Factory<Key, ToValue> =
- mapByPage(Function { list -> list.map { function.apply(it) } })
+ public open fun <ToValue : Any> map(
+ function: Function<Value, ToValue>
+ ): Factory<Key, ToValue> {
+ return mapByPage(Function { list -> list.map { function.apply(it) } })
+ }
/**
* Applies the given function to each value emitted by DataSources produced by this Factory.
@@ -185,8 +188,9 @@
* @see DataSource.mapByPage
*/
@JvmSynthetic // hidden to preserve Java source compat with arch.core.util.Function variant
- open fun <ToValue : Any> map(function: (Value) -> ToValue): Factory<Key, ToValue> =
- mapByPage(Function { list -> list.map(function) })
+ public open fun <ToValue : Any> map(function: (Value) -> ToValue): Factory<Key, ToValue> {
+ return mapByPage(Function { list -> list.map(function) })
+ }
/**
* Applies the given function to each value emitted by DataSources produced by this Factory.
@@ -202,7 +206,7 @@
* @see DataSource.map
* @see DataSource.mapByPage
*/
- open fun <ToValue : Any> mapByPage(
+ public open fun <ToValue : Any> mapByPage(
function: Function<List<Value>, List<ToValue>>
): Factory<Key, ToValue> = object : Factory<Key, ToValue>() {
override fun create(): DataSource<Key, ToValue> =
@@ -226,12 +230,12 @@
* @see DataSource.mapByPage
*/
@JvmSynthetic // hidden to preserve Java source compat with arch.core.util.Function variant
- open fun <ToValue : Any> mapByPage(
+ public open fun <ToValue : Any> mapByPage(
function: (List<Value>) -> List<ToValue>
): Factory<Key, ToValue> = mapByPage(Function { function(it) })
@JvmOverloads
- fun asPagingSourceFactory(
+ public fun asPagingSourceFactory(
fetchDispatcher: CoroutineDispatcher = Dispatchers.IO
): () -> PagingSource<Key, Value> = SuspendingPagingSourceFactory(
delegate = {
@@ -255,7 +259,7 @@
* @see DataSource.Factory.map
* @see DataSource.Factory.mapByPage
*/
- open fun <ToValue : Any> mapByPage(
+ public open fun <ToValue : Any> mapByPage(
function: Function<List<Value>, List<ToValue>>
): DataSource<Key, ToValue> = WrapperDataSource(this, function)
@@ -276,7 +280,7 @@
* @see DataSource.Factory.mapByPage
*/
@JvmSynthetic // hidden to preserve Java source compat with arch.core.util.Function variant
- open fun <ToValue : Any> mapByPage(
+ public open fun <ToValue : Any> mapByPage(
function: (List<Value>) -> List<ToValue>
): DataSource<Key, ToValue> = mapByPage(Function { function(it) })
@@ -294,8 +298,11 @@
* @see DataSource.Factory.map
* @see DataSource.Factory.mapByPage
*/
- open fun <ToValue : Any> map(function: Function<Value, ToValue>): DataSource<Key, ToValue> =
- mapByPage { list -> list.map { function.apply(it) } }
+ public open fun <ToValue : Any> map(
+ function: Function<Value, ToValue>
+ ): DataSource<Key, ToValue> {
+ return mapByPage { list -> list.map { function.apply(it) } }
+ }
/**
* Applies the given function to each value emitted by the DataSource.
@@ -314,7 +321,7 @@
*
*/
@JvmSynthetic // hidden to preserve Java source compat with arch.core.util.Function variant
- open fun <ToValue : Any> map(
+ public open fun <ToValue : Any> map(
function: (Value) -> ToValue
): DataSource<Key, ToValue> = map(Function { function(it) })
@@ -332,7 +339,7 @@
* Used to signal when a [DataSource] a data source has become invalid, and that a new data
* source is needed to continue loading data.
*/
- fun interface InvalidatedCallback {
+ public fun interface InvalidatedCallback {
/**
* Called when the data backing the list has become invalid. This callback is typically used
* to signal that a new data source is needed.
@@ -342,7 +349,7 @@
* invalidate it.
*/
@AnyThread
- fun onInvalidated()
+ public fun onInvalidated()
}
/**
@@ -358,7 +365,7 @@
*/
@AnyThread
@Suppress("RegistrationName")
- open fun addInvalidatedCallback(onInvalidatedCallback: InvalidatedCallback) {
+ public open fun addInvalidatedCallback(onInvalidatedCallback: InvalidatedCallback) {
onInvalidatedCallbacks.add(onInvalidatedCallback)
}
@@ -369,7 +376,7 @@
*/
@AnyThread
@Suppress("RegistrationName")
- open fun removeInvalidatedCallback(onInvalidatedCallback: InvalidatedCallback) {
+ public open fun removeInvalidatedCallback(onInvalidatedCallback: InvalidatedCallback) {
onInvalidatedCallbacks.remove(onInvalidatedCallback)
}
@@ -379,7 +386,7 @@
* If invalidate has already been called, this method does nothing.
*/
@AnyThread
- open fun invalidate() {
+ public open fun invalidate() {
if (_invalid.compareAndSet(false, true)) {
onInvalidatedCallbacks.forEach { it.onInvalidated() }
}
diff --git a/paging/common/src/main/kotlin/androidx/paging/ExperimentalPagingApi.kt b/paging/common/src/main/kotlin/androidx/paging/ExperimentalPagingApi.kt
index 359a6e8..c31ff68 100644
--- a/paging/common/src/main/kotlin/androidx/paging/ExperimentalPagingApi.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/ExperimentalPagingApi.kt
@@ -21,4 +21,4 @@
* source-incompatible change in newer versions of the artifact that supplies it.
*/
@RequiresOptIn
-annotation class ExperimentalPagingApi
\ No newline at end of file
+public annotation class ExperimentalPagingApi
\ No newline at end of file
diff --git a/paging/common/src/main/kotlin/androidx/paging/InitialPagedList.kt b/paging/common/src/main/kotlin/androidx/paging/InitialPagedList.kt
index 7014826..19562a5 100644
--- a/paging/common/src/main/kotlin/androidx/paging/InitialPagedList.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/InitialPagedList.kt
@@ -29,7 +29,7 @@
* @suppress
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
-class InitialPagedList<K : Any, V : Any>(
+public class InitialPagedList<K : Any, V : Any>(
pagingSource: PagingSource<K, V>,
coroutineScope: CoroutineScope,
notifyDispatcher: CoroutineDispatcher,
diff --git a/paging/common/src/main/kotlin/androidx/paging/InvalidatingPagingSourceFactory.kt b/paging/common/src/main/kotlin/androidx/paging/InvalidatingPagingSourceFactory.kt
index 8004326..92dc909 100644
--- a/paging/common/src/main/kotlin/androidx/paging/InvalidatingPagingSourceFactory.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/InvalidatingPagingSourceFactory.kt
@@ -28,7 +28,7 @@
*
* @param pagingSourceFactory The [PagingSource] factory that returns a PagingSource when called
*/
-class InvalidatingPagingSourceFactory<Key : Any, Value : Any>(
+public class InvalidatingPagingSourceFactory<Key : Any, Value : Any>(
private val pagingSourceFactory: () -> PagingSource<Key, Value>
) : () -> PagingSource<Key, Value> {
@@ -47,7 +47,7 @@
* Calls [PagingSource.invalidate] on each [PagingSource] that was produced by this
* [InvalidatingPagingSourceFactory]
*/
- fun invalidate() {
+ public fun invalidate() {
while (pagingSources.isNotEmpty()) {
pagingSources.removeFirst().also {
if (!it.invalid) {
diff --git a/paging/common/src/main/kotlin/androidx/paging/ItemKeyedDataSource.kt b/paging/common/src/main/kotlin/androidx/paging/ItemKeyedDataSource.kt
index 10ea32b..68a85be 100644
--- a/paging/common/src/main/kotlin/androidx/paging/ItemKeyedDataSource.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/ItemKeyedDataSource.kt
@@ -47,7 +47,9 @@
"androidx.paging.PagingSource"
)
)
-abstract class ItemKeyedDataSource<Key : Any, Value : Any> : DataSource<Key, Value>(ITEM_KEYED) {
+public abstract class ItemKeyedDataSource<Key : Any, Value : Any> : DataSource<Key, Value>(
+ ITEM_KEYED
+) {
/**
* Holder object for inputs to [loadInitial].
@@ -64,13 +66,13 @@
* @property placeholdersEnabled Defines whether placeholders are enabled, and whether the
* loaded total count will be ignored.
*/
- open class LoadInitialParams<Key : Any>(
+ public open class LoadInitialParams<Key : Any>(
@JvmField
- val requestedInitialKey: Key?,
+ public val requestedInitialKey: Key?,
@JvmField
- val requestedLoadSize: Int,
+ public val requestedLoadSize: Int,
@JvmField
- val placeholdersEnabled: Boolean
+ public val placeholdersEnabled: Boolean
)
/**
@@ -85,7 +87,12 @@
* Returned page can be of this size, but it may be altered if that is easier, e.g. a network
* data source where the backend defines page size.
*/
- open class LoadParams<Key : Any>(@JvmField val key: Key, @JvmField val requestedLoadSize: Int)
+ public open class LoadParams<Key : Any>(
+ @JvmField
+ public val key: Key,
+ @JvmField
+ public val requestedLoadSize: Int
+ )
/**
* Callback for [loadInitial]
@@ -105,7 +112,7 @@
*
* @param Value Type of items being loaded.
*/
- abstract class LoadInitialCallback<Value> : LoadCallback<Value>() {
+ public abstract class LoadInitialCallback<Value> : LoadCallback<Value>() {
/**
* Called to pass initial load state from a DataSource.
*
@@ -125,7 +132,7 @@
* Includes the number in the initial `data` parameter as well as any items that can be
* loaded in front or behind of `data`.
*/
- abstract fun onResult(data: List<Value>, position: Int, totalCount: Int)
+ public abstract fun onResult(data: List<Value>, position: Int, totalCount: Int)
}
/**
@@ -139,7 +146,7 @@
*
* @param Value Type of items being loaded.
*/
- abstract class LoadCallback<Value> {
+ public abstract class LoadCallback<Value> {
/**
* Called to pass loaded data from a DataSource.
*
@@ -154,7 +161,7 @@
*
* @param data List of items loaded from the [ItemKeyedDataSource].
*/
- abstract fun onResult(data: List<Value>)
+ public abstract fun onResult(data: List<Value>)
}
@Suppress("RedundantVisibilityModifier") // Metalava doesn't inherit visibility properly.
@@ -252,7 +259,10 @@
* @param params Parameters for initial load, including initial key and requested size.
* @param callback Callback that receives initial load data.
*/
- abstract fun loadInitial(params: LoadInitialParams<Key>, callback: LoadInitialCallback<Value>)
+ public abstract fun loadInitial(
+ params: LoadInitialParams<Key>,
+ callback: LoadInitialCallback<Value>
+ )
/**
* Load list data after the key specified in [LoadParams.key].
@@ -271,7 +281,7 @@
* @param params Parameters for the load, including the key to load after, and requested size.
* @param callback Callback that receives loaded data.
*/
- abstract fun loadAfter(params: LoadParams<Key>, callback: LoadCallback<Value>)
+ public abstract fun loadAfter(params: LoadParams<Key>, callback: LoadCallback<Value>)
/**
* Load list data before the key specified in [LoadParams.key].
@@ -293,7 +303,7 @@
* @param params Parameters for the load, including the key to load before, and requested size.
* @param callback Callback that receives loaded data.
*/
- abstract fun loadBefore(params: LoadParams<Key>, callback: LoadCallback<Value>)
+ public abstract fun loadBefore(params: LoadParams<Key>, callback: LoadCallback<Value>)
/**
* Return a key associated with the given item.
@@ -311,29 +321,29 @@
* @param item Item to get the key from.
* @return Key associated with given item.
*/
- abstract fun getKey(item: Value): Key
+ public abstract fun getKey(item: Value): Key
@Suppress("RedundantVisibilityModifier") // Metalava doesn't inherit visibility properly.
internal override fun getKeyInternal(item: Value): Key = getKey(item)
@Suppress("DEPRECATION")
- final override fun <ToValue : Any> mapByPage(
+ public final override fun <ToValue : Any> mapByPage(
function: Function<List<Value>, List<ToValue>>
): ItemKeyedDataSource<Key, ToValue> = WrapperItemKeyedDataSource(this, function)
@Suppress("DEPRECATION")
- final override fun <ToValue : Any> mapByPage(
+ public final override fun <ToValue : Any> mapByPage(
function: (List<Value>) -> List<ToValue>
): ItemKeyedDataSource<Key, ToValue> = mapByPage(Function { function(it) })
@Suppress("DEPRECATION")
- final override fun <ToValue : Any> map(
+ public final override fun <ToValue : Any> map(
function: Function<Value, ToValue>
): ItemKeyedDataSource<Key, ToValue> =
mapByPage(Function { list -> list.map { function.apply(it) } })
@Suppress("DEPRECATION")
- final override fun <ToValue : Any> map(
+ public final override fun <ToValue : Any> map(
function: (Value) -> ToValue
): ItemKeyedDataSource<Key, ToValue> = mapByPage(Function { list -> list.map(function) })
}
diff --git a/paging/common/src/main/kotlin/androidx/paging/ItemSnapshotList.kt b/paging/common/src/main/kotlin/androidx/paging/ItemSnapshotList.kt
index 8fabddd..1512288 100644
--- a/paging/common/src/main/kotlin/androidx/paging/ItemSnapshotList.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/ItemSnapshotList.kt
@@ -23,23 +23,23 @@
* [AsyncPagingDataDiffer][androidx.pagingAsyncPagingDataDiffer] or a
* [PagingDataAdapter][androidx.paging.PagingDataAdapter].
*/
-class ItemSnapshotList<T>(
+public class ItemSnapshotList<T>(
/**
* Number of placeholders before the presented [items], 0 if
* [enablePlaceholders][androidx.paging.PagingConfig.enablePlaceholders] is `false`.
*/
@IntRange(from = 0)
- val placeholdersBefore: Int,
+ public val placeholdersBefore: Int,
/**
* Number of placeholders after the presented [items], 0 if
* [enablePlaceholders][androidx.paging.PagingConfig.enablePlaceholders] is `false`.
*/
@IntRange(from = 0)
- val placeholdersAfter: Int,
+ public val placeholdersAfter: Int,
/**
* The presented data, excluding placeholders.
*/
- val items: List<T>
+ public val items: List<T>
) : AbstractList<T?>() {
/**
@@ -49,7 +49,7 @@
*
* @see items
*/
- override val size: Int
+ public override val size: Int
get() = placeholdersBefore + items.size + placeholdersAfter
/**
diff --git a/paging/common/src/main/kotlin/androidx/paging/LoadState.kt b/paging/common/src/main/kotlin/androidx/paging/LoadState.kt
index d5ff053..11c5d59 100644
--- a/paging/common/src/main/kotlin/androidx/paging/LoadState.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/LoadState.kt
@@ -30,8 +30,8 @@
*
* @see LoadType
*/
-sealed class LoadState(
- val endOfPaginationReached: Boolean
+public sealed class LoadState(
+ public val endOfPaginationReached: Boolean
) {
/**
* Indicates the [PagingData] is not currently loading, and no error currently observed.
@@ -41,7 +41,7 @@
* should continue to make requests for additional data in this direction or if it should
* halt as the end of the dataset has been reached.
*/
- class NotLoading(
+ public class NotLoading(
endOfPaginationReached: Boolean
) : LoadState(endOfPaginationReached) {
override fun toString(): String {
@@ -66,7 +66,7 @@
/**
* Loading is in progress.
*/
- object Loading : LoadState(false) {
+ public object Loading : LoadState(false) {
override fun toString(): String {
return "Loading(endOfPaginationReached=$endOfPaginationReached)"
}
@@ -88,8 +88,8 @@
*
* @see androidx.paging.PagedList.retry
*/
- class Error(
- val error: Throwable
+ public class Error(
+ public val error: Throwable
) : LoadState(false) {
override fun equals(other: Any?): Boolean {
return other is Error &&
diff --git a/paging/common/src/main/kotlin/androidx/paging/LoadStates.kt b/paging/common/src/main/kotlin/androidx/paging/LoadStates.kt
index c474460..442148f 100644
--- a/paging/common/src/main/kotlin/androidx/paging/LoadStates.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/LoadStates.kt
@@ -22,16 +22,16 @@
/**
* Collection of pagination [LoadState]s - refresh, prepend, and append.
*/
-data class LoadStates(
+public data class LoadStates(
/** [LoadState] corresponding to [LoadType.REFRESH] loads. */
- val refresh: LoadState,
+ public val refresh: LoadState,
/** [LoadState] corresponding to [LoadType.PREPEND] loads. */
- val prepend: LoadState,
+ public val prepend: LoadState,
/** [LoadState] corresponding to [LoadType.APPEND] loads. */
- val append: LoadState
+ public val append: LoadState
) {
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- inline fun forEach(op: (LoadType, LoadState) -> Unit) {
+ public inline fun forEach(op: (LoadType, LoadState) -> Unit) {
op(LoadType.REFRESH, refresh)
op(LoadType.PREPEND, prepend)
op(LoadType.APPEND, append)
diff --git a/paging/common/src/main/kotlin/androidx/paging/LoadType.kt b/paging/common/src/main/kotlin/androidx/paging/LoadType.kt
index 435357f..02f7e64 100644
--- a/paging/common/src/main/kotlin/androidx/paging/LoadType.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/LoadType.kt
@@ -25,7 +25,7 @@
*
* @see LoadState
*/
-enum class LoadType {
+public enum class LoadType {
/**
* [PagingData] content being refreshed, which can be a result of [PagingSource]
* invalidation, refresh that may contain content updates, or the initial load.
diff --git a/paging/common/src/main/kotlin/androidx/paging/NullPaddedList.kt b/paging/common/src/main/kotlin/androidx/paging/NullPaddedList.kt
index 18dea6c..2d0659f 100644
--- a/paging/common/src/main/kotlin/androidx/paging/NullPaddedList.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/NullPaddedList.kt
@@ -26,10 +26,10 @@
* @suppress
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-interface NullPaddedList<T> {
- val placeholdersBefore: Int
- fun getFromStorage(localIndex: Int): T
- val placeholdersAfter: Int
- val size: Int
- val storageCount: Int
+public interface NullPaddedList<T> {
+ public val placeholdersBefore: Int
+ public fun getFromStorage(localIndex: Int): T
+ public val placeholdersAfter: Int
+ public val size: Int
+ public val storageCount: Int
}
\ No newline at end of file
diff --git a/paging/common/src/main/kotlin/androidx/paging/PageFetcher.kt b/paging/common/src/main/kotlin/androidx/paging/PageFetcher.kt
index ee5eda6..4d2345e 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PageFetcher.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PageFetcher.kt
@@ -66,13 +66,21 @@
pagingSource = generateNewPagingSource(previousPagingSource = pagingSource)
}
- var previousPagingState = previousGeneration?.snapshot?.refreshKeyInfo()
+ var previousPagingState = previousGeneration?.snapshot?.currentPagingState()
+
+ // If cached PagingState had pages loaded, but previous generation didn't, use
+ // the cached PagingState to handle cases where invalidation happens too quickly,
+ // so that getRefreshKey and remote refresh at least have some data to work with.
+ if (previousPagingState?.pages.isNullOrEmpty() &&
+ previousGeneration?.state?.pages?.isNotEmpty() == true
+ ) {
+ previousPagingState = previousGeneration.state
+ }
+
// If previous generation was invalidated before anchorPosition was established,
// re-use last PagingState that successfully loaded pages and has an anchorPosition.
// This prevents rapid invalidation from deleting the anchorPosition if the
- // previous generation didn't have time to load before getting invalidated. We
- // check for anchorPosition before overriding to prevent empty PagingState from
- // overriding a PagingState with pages loaded, but no anchorPosition.
+ // previous generation didn't have time to load before getting invalidated.
if (previousPagingState?.anchorPosition == null &&
previousGeneration?.state?.anchorPosition != null
) {
@@ -94,7 +102,8 @@
// initialization or PagingSource invalidation.
triggerRemoteRefresh = triggerRemoteRefresh,
remoteMediatorConnection = remoteMediatorAccessor,
- invalidate = this@PageFetcher::refresh
+ invalidate = this@PageFetcher::refresh,
+ previousPagingState = previousPagingState,
),
state = previousPagingState,
)
diff --git a/paging/common/src/main/kotlin/androidx/paging/PageFetcherSnapshot.kt b/paging/common/src/main/kotlin/androidx/paging/PageFetcherSnapshot.kt
index 0b70976..c849a44 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PageFetcherSnapshot.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PageFetcherSnapshot.kt
@@ -58,7 +58,8 @@
private val retryFlow: Flow<Unit>,
private val triggerRemoteRefresh: Boolean = false,
val remoteMediatorConnection: RemoteMediatorConnection<Key, Value>? = null,
- private val invalidate: () -> Unit = {}
+ private val previousPagingState: PagingState<Key, Value>? = null,
+ private val invalidate: () -> Unit = {},
) {
init {
require(config.jumpThreshold == COUNT_UNDEFINED || pagingSource.jumpingSupported) {
@@ -148,7 +149,9 @@
if (triggerRemoteRefresh) {
remoteMediatorConnection?.let {
- val pagingState = stateHolder.withLock { state -> state.currentPagingState(null) }
+ val pagingState = previousPagingState ?: stateHolder.withLock { state ->
+ state.currentPagingState(null)
+ }
it.requestLoad(REFRESH, pagingState)
}
}
@@ -193,17 +196,8 @@
pageEventChannelFlowJob.cancel()
}
- suspend fun refreshKeyInfo(): PagingState<Key, Value>? {
- return stateHolder.withLock { state ->
- lastHint?.let { lastHint ->
- if (state.pages.isEmpty()) {
- // Default to initialKey if no pages loaded.
- null
- } else {
- state.currentPagingState(lastHint)
- }
- }
- }
+ suspend fun currentPagingState(): PagingState<Key, Value> {
+ return stateHolder.withLock { state -> state.currentPagingState(lastHint) }
}
private fun CoroutineScope.startConsumingHints() {
diff --git a/paging/common/src/main/kotlin/androidx/paging/PageKeyedDataSource.kt b/paging/common/src/main/kotlin/androidx/paging/PageKeyedDataSource.kt
index ac773ac..e3d0fd8 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PageKeyedDataSource.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PageKeyedDataSource.kt
@@ -46,7 +46,9 @@
"androidx.paging.PagingSource"
)
)
-abstract class PageKeyedDataSource<Key : Any, Value : Any> : DataSource<Key, Value>(PAGE_KEYED) {
+public abstract class PageKeyedDataSource<Key : Any, Value : Any> : DataSource<Key, Value>(
+ PAGE_KEYED
+) {
/**
* Holder object for inputs to [loadInitial].
@@ -58,9 +60,9 @@
* @property placeholdersEnabled Defines whether placeholders are enabled, and whether the
* loaded total count will be ignored.
*/
- open class LoadInitialParams<Key : Any>(
- @JvmField val requestedLoadSize: Int,
- @JvmField val placeholdersEnabled: Boolean
+ public open class LoadInitialParams<Key : Any>(
+ @JvmField public val requestedLoadSize: Int,
+ @JvmField public val placeholdersEnabled: Boolean
)
/**
@@ -75,7 +77,10 @@
* Returned page can be of this size, but it may be altered if that is easier, e.g. a network
* data source where the backend defines page size.
*/
- open class LoadParams<Key : Any>(@JvmField val key: Key, @JvmField val requestedLoadSize: Int)
+ public open class LoadParams<Key : Any>(
+ @JvmField public val key: Key,
+ @JvmField public val requestedLoadSize: Int
+ )
/**
* Callback for [loadInitial] to return data and, optionally, position/count information.
@@ -95,7 +100,7 @@
* @param Key Type of data used to query pages.
* @param Value Type of items being loaded.
*/
- abstract class LoadInitialCallback<Key, Value> {
+ public abstract class LoadInitialCallback<Key, Value> {
/**
* Called to pass initial load state from a DataSource.
*
@@ -115,7 +120,7 @@
* Includes the number in the initial `data` parameter as well as any items that can be
* loaded in front or behind of `data`.
*/
- abstract fun onResult(
+ public abstract fun onResult(
data: List<Value>,
position: Int,
totalCount: Int,
@@ -138,7 +143,7 @@
* @param nextPageKey Key for page after the initial load result, or `null` if no more data
* can be loaded after.
*/
- abstract fun onResult(data: List<Value>, previousPageKey: Key?, nextPageKey: Key?)
+ public abstract fun onResult(data: List<Value>, previousPageKey: Key?, nextPageKey: Key?)
}
/**
@@ -153,7 +158,7 @@
* @param Key Type of data used to query pages.
* @param Value Type of items being loaded.
*/
- abstract class LoadCallback<Key, Value> {
+ public abstract class LoadCallback<Key, Value> {
/**
* Called to pass loaded data from a [DataSource].
*
@@ -173,7 +178,7 @@
* page in [loadAfter]), or `null` if there are no more pages to load in the current load
* direction.
*/
- abstract fun onResult(data: List<Value>, adjacentPageKey: Key?)
+ public abstract fun onResult(data: List<Value>, adjacentPageKey: Key?)
}
/**
@@ -270,7 +275,7 @@
* @param params Parameters for initial load, including requested load size.
* @param callback Callback that receives initial load data.
*/
- abstract fun loadInitial(
+ public abstract fun loadInitial(
params: LoadInitialParams<Key>,
callback: LoadInitialCallback<Key, Value>
)
@@ -293,7 +298,7 @@
* size.
* @param callback Callback that receives loaded data.
*/
- abstract fun loadBefore(params: LoadParams<Key>, callback: LoadCallback<Key, Value>)
+ public abstract fun loadBefore(params: LoadParams<Key>, callback: LoadCallback<Key, Value>)
/**
* Append page with the key specified by [LoadParams.key].
@@ -313,7 +318,7 @@
* size.
* @param callback Callback that receives loaded data.
*/
- abstract fun loadAfter(params: LoadParams<Key>, callback: LoadCallback<Key, Value>)
+ public abstract fun loadAfter(params: LoadParams<Key>, callback: LoadCallback<Key, Value>)
@Suppress("RedundantVisibilityModifier") // Metalava doesn't inherit visibility properly.
internal override fun getKeyInternal(item: Value): Key =
diff --git a/paging/common/src/main/kotlin/androidx/paging/PagedList.kt b/paging/common/src/main/kotlin/androidx/paging/PagedList.kt
index 2b22813..0dd12d2 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PagedList.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PagedList.kt
@@ -113,15 +113,16 @@
*
* @param T The type of the entries in the list.
*/
+@Suppress("DEPRECATION")
@Deprecated("PagedList is deprecated and has been replaced by PagingData")
-abstract class PagedList<T : Any> internal constructor(
+public abstract class PagedList<T : Any> internal constructor(
/**
* The [PagingSource] that provides data to this [PagedList].
*
* @suppress
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- open val pagingSource: PagingSource<*, T>,
+ public open val pagingSource: PagingSource<*, T>,
internal val coroutineScope: CoroutineScope,
internal val notifyDispatcher: CoroutineDispatcher,
internal val storage: PagedStorage<T>,
@@ -131,13 +132,13 @@
*
* @return the Config of this PagedList
*/
- val config: Config
+ public val config: Config
) : AbstractList<T>() {
/**
* @suppress
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- companion object {
+ public companion object {
/**
* Create a [PagedList] which loads data from the provided data source on a background
* thread, posting updates to the main thread.
@@ -157,10 +158,9 @@
*
* @suppress
*/
- @Suppress("DEPRECATION")
@JvmStatic
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- fun <K : Any, T : Any> create(
+ public fun <K : Any, T : Any> create(
pagingSource: PagingSource<K, T>,
initialPage: PagingSource.LoadResult.Page<K, T>?,
coroutineScope: CoroutineScope,
@@ -253,7 +253,7 @@
"longer supports constructing snapshots of loaded data manually.",
replaceWith = ReplaceWith("Pager.flow", "androidx.paging.Pager")
)
- class Builder<Key : Any, Value : Any> {
+ public class Builder<Key : Any, Value : Any> {
private val pagingSource: PagingSource<Key, Value>?
private var dataSource: DataSource<Key, Value>?
private val initialPage: PagingSource.LoadResult.Page<Key, Value>?
@@ -272,7 +272,7 @@
* @param config [PagedList.Config] that defines how the [PagedList] loads data from its
* [DataSource].
*/
- constructor(dataSource: DataSource<Key, Value>, config: Config) {
+ public constructor(dataSource: DataSource<Key, Value>, config: Config) {
this.pagingSource = null
this.dataSource = dataSource
this.initialPage = null
@@ -292,7 +292,7 @@
* @param pageSize Size of loaded pages when the [PagedList] loads data from its
* [DataSource].
*/
- constructor(dataSource: DataSource<Key, Value>, pageSize: Int) : this(
+ public constructor(dataSource: DataSource<Key, Value>, pageSize: Int) : this(
dataSource = dataSource,
config = Config(pageSize)
)
@@ -306,7 +306,7 @@
* @param config [PagedList.Config] that defines how the [PagedList] loads data from its
* [PagingSource].
*/
- constructor(
+ public constructor(
pagingSource: PagingSource<Key, Value>,
initialPage: PagingSource.LoadResult.Page<Key, Value>,
config: Config
@@ -335,7 +335,7 @@
* @param pageSize Size of loaded pages when the [PagedList] loads data from its
* [PagingSource].
*/
- constructor(
+ public constructor(
pagingSource: PagingSource<Key, Value>,
initialPage: PagingSource.LoadResult.Page<Key, Value>,
pageSize: Int
@@ -357,7 +357,9 @@
* @param coroutineScope
* @return this
*/
- fun setCoroutineScope(coroutineScope: CoroutineScope) = this.apply {
+ public fun setCoroutineScope(
+ coroutineScope: CoroutineScope
+ ): Builder<Key, Value> = apply {
this.coroutineScope = coroutineScope
}
@@ -376,7 +378,9 @@
"kotlinx.coroutines.asCoroutineDispatcher"
)
)
- fun setNotifyExecutor(notifyExecutor: Executor) = apply {
+ public fun setNotifyExecutor(
+ notifyExecutor: Executor
+ ): Builder<Key, Value> = apply {
this.notifyDispatcher = notifyExecutor.asCoroutineDispatcher()
}
@@ -387,7 +391,9 @@
* where [PagedList.Callback] calls are dispatched. Generally, this is the ui/main thread.
* @return this
*/
- fun setNotifyDispatcher(notifyDispatcher: CoroutineDispatcher) = apply {
+ public fun setNotifyDispatcher(
+ notifyDispatcher: CoroutineDispatcher
+ ): Builder<Key, Value> = apply {
this.notifyDispatcher = notifyDispatcher
}
@@ -409,7 +415,9 @@
"kotlinx.coroutines.asCoroutineDispatcher"
)
)
- fun setFetchExecutor(fetchExecutor: Executor) = apply {
+ public fun setFetchExecutor(
+ fetchExecutor: Executor
+ ): Builder<Key, Value> = apply {
this.fetchDispatcher = fetchExecutor.asCoroutineDispatcher()
}
@@ -423,7 +431,9 @@
* generally a background thread pool for e.g. I/O or network loading.
* @return this
*/
- fun setFetchDispatcher(fetchDispatcher: CoroutineDispatcher) = apply {
+ public fun setFetchDispatcher(
+ fetchDispatcher: CoroutineDispatcher
+ ): Builder<Key, Value> = apply {
this.fetchDispatcher = fetchDispatcher
}
@@ -435,7 +445,9 @@
* @param boundaryCallback [BoundaryCallback] for listening to out-of-data events.
* @return this
*/
- fun setBoundaryCallback(boundaryCallback: BoundaryCallback<Value>?) = apply {
+ public fun setBoundaryCallback(
+ boundaryCallback: BoundaryCallback<Value>?
+ ): Builder<Key, Value> = apply {
this.boundaryCallback = boundaryCallback
}
@@ -445,7 +457,9 @@
* @param initialKey Key the [PagingSource] should load around as part of initialization.
* @return this
*/
- fun setInitialKey(initialKey: Key?) = apply {
+ public fun setInitialKey(
+ initialKey: Key?
+ ): Builder<Key, Value> = apply {
this.initialKey = initialKey
}
@@ -473,8 +487,7 @@
*
* @return The newly constructed [PagedList]
*/
- @Suppress("DEPRECATION")
- fun build(): PagedList<Value> {
+ public fun build(): PagedList<Value> {
val fetchDispatcher = fetchDispatcher ?: Dispatchers.IO
val pagingSource = pagingSource ?: dataSource?.let { dataSource ->
LegacyPagingSource(
@@ -509,7 +522,7 @@
* the dispatcher defined by [PagedList.Builder.setNotifyDispatcher], which is generally the
* main/UI thread.
*/
- abstract class Callback {
+ public abstract class Callback {
/**
* Called when null padding items have been loaded to signal newly available data, or when
* data that hasn't been used in a while has been dropped, and swapped back to null.
@@ -518,7 +531,7 @@
* (including padded nulls).
* @param count Number of items loaded.
*/
- abstract fun onChanged(position: Int, count: Int)
+ public abstract fun onChanged(position: Int, count: Int)
/**
* Called when new items have been loaded at the end or beginning of the list.
@@ -527,7 +540,7 @@
* `size - 1`.
* @param count Number of items loaded.
*/
- abstract fun onInserted(position: Int, count: Int)
+ public abstract fun onInserted(position: Int, count: Int)
/**
* Called when items have been removed at the end or beginning of the list, and have not
@@ -537,7 +550,7 @@
* `size - 1`.
* @param count Number of items loaded.
*/
- abstract fun onRemoved(position: Int, count: Int)
+ public abstract fun onRemoved(position: Int, count: Int)
}
/**
@@ -547,12 +560,12 @@
* [setPageSize][PagedList.Config.Builder.setPageSize], which defines number of items loaded at
* a time.
*/
- class Config internal constructor(
+ public class Config internal constructor(
/**
* Size of each page loaded by the PagedList.
*/
@JvmField
- val pageSize: Int,
+ public val pageSize: Int,
/**
* Prefetch distance which defines how far ahead to load.
*
@@ -562,18 +575,18 @@
* @see PagedList.loadAround
*/
@JvmField
- val prefetchDistance: Int,
+ public val prefetchDistance: Int,
/**
* Defines whether the [PagedList] may display null placeholders, if the [PagingSource]
* provides them.
*/
@JvmField
- val enablePlaceholders: Boolean,
+ public val enablePlaceholders: Boolean,
/**
* Size hint for initial load of PagedList, often larger than a regular page.
*/
@JvmField
- val initialLoadSizeHint: Int,
+ public val initialLoadSizeHint: Int,
/**
* Defines the maximum number of items that may be loaded into this pagedList before pages
* should be dropped.
@@ -584,14 +597,14 @@
* @see PagedList.Config.Builder.setMaxSize
*/
@JvmField
- val maxSize: Int
+ public val maxSize: Int
) {
/**
* Builder class for [PagedList.Config].
*
* You must at minimum specify page size with [setPageSize].
*/
- class Builder {
+ public class Builder {
private var pageSize = -1
private var prefetchDistance = -1
private var initialLoadSizeHint = -1
@@ -618,7 +631,9 @@
*
* @throws IllegalArgumentException if pageSize is < `1`.
*/
- fun setPageSize(@IntRange(from = 1) pageSize: Int) = apply {
+ public fun setPageSize(
+ @IntRange(from = 1) pageSize: Int
+ ): Builder = apply {
if (pageSize < 1) {
throw IllegalArgumentException("Page size must be a positive number")
}
@@ -640,7 +655,9 @@
* @param prefetchDistance Distance the [PagedList] should prefetch.
* @return this
*/
- fun setPrefetchDistance(@IntRange(from = 0) prefetchDistance: Int) = apply {
+ public fun setPrefetchDistance(
+ @IntRange(from = 0) prefetchDistance: Int
+ ): Builder = apply {
this.prefetchDistance = prefetchDistance
}
@@ -671,7 +688,9 @@
* @param enablePlaceholders `false` if null placeholders should be disabled.
* @return this
*/
- fun setEnablePlaceholders(enablePlaceholders: Boolean) = apply {
+ public fun setEnablePlaceholders(
+ enablePlaceholders: Boolean
+ ): Builder = apply {
this.enablePlaceholders = enablePlaceholders
}
@@ -686,7 +705,9 @@
* @param initialLoadSizeHint Number of items to load while initializing the [PagedList]
* @return this
*/
- fun setInitialLoadSizeHint(@IntRange(from = 1) initialLoadSizeHint: Int) = apply {
+ public fun setInitialLoadSizeHint(
+ @IntRange(from = 1) initialLoadSizeHint: Int
+ ): Builder = apply {
this.initialLoadSizeHint = initialLoadSizeHint
}
@@ -720,7 +741,7 @@
* @see Config.MAX_SIZE_UNBOUNDED
* @see Config.maxSize
*/
- fun setMaxSize(@IntRange(from = 2) maxSize: Int) = apply {
+ public fun setMaxSize(@IntRange(from = 2) maxSize: Int): Builder = apply {
this.maxSize = maxSize
}
@@ -734,7 +755,7 @@
* @throws IllegalArgumentException if maximum size is less than pageSize +
* 2*prefetchDistance
*/
- fun build(): Config {
+ public fun build(): Config {
if (prefetchDistance < 0) {
prefetchDistance = pageSize
}
@@ -840,11 +861,11 @@
* @param T Type loaded by the [PagedList].
*/
@MainThread
- abstract class BoundaryCallback<T : Any> {
+ public abstract class BoundaryCallback<T : Any> {
/**
* Called when zero items are returned from an initial load of the PagedList's data source.
*/
- open fun onZeroItemsLoaded() {}
+ public open fun onZeroItemsLoaded() {}
/**
* Called when the item at the front of the PagedList has been loaded, and access has
@@ -854,7 +875,7 @@
*
* @param itemAtFront The first item of PagedList
*/
- open fun onItemAtFrontLoaded(itemAtFront: T) {}
+ public open fun onItemAtFrontLoaded(itemAtFront: T) {}
/**
* Called when the item at the end of the PagedList has been loaded, and access has
@@ -864,19 +885,19 @@
*
* @param itemAtEnd The first item of [PagedList]
*/
- open fun onItemAtEndLoaded(itemAtEnd: T) {}
+ public open fun onItemAtEndLoaded(itemAtEnd: T) {}
}
/**
* @suppress
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- abstract class LoadStateManager {
- var refreshState: LoadState = LoadState.NotLoading.Incomplete
- var startState: LoadState = LoadState.NotLoading.Incomplete
- var endState: LoadState = LoadState.NotLoading.Incomplete
+ public abstract class LoadStateManager {
+ public var refreshState: LoadState = LoadState.NotLoading.Incomplete
+ public var startState: LoadState = LoadState.NotLoading.Incomplete
+ public var endState: LoadState = LoadState.NotLoading.Incomplete
- fun setState(type: LoadType, state: LoadState) {
+ public fun setState(type: LoadType, state: LoadState) {
// deduplicate signals
when (type) {
LoadType.REFRESH -> {
@@ -900,9 +921,9 @@
* @suppress
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // protected otherwise.
- abstract fun onStateChanged(type: LoadType, state: LoadState)
+ public abstract fun onStateChanged(type: LoadType, state: LoadState)
- fun dispatchCurrentLoadState(callback: (LoadType, LoadState) -> Unit) {
+ public fun dispatchCurrentLoadState(callback: (LoadType, LoadState) -> Unit) {
callback(LoadType.REFRESH, refreshState)
callback(LoadType.PREPEND, startState)
callback(LoadType.APPEND, endState)
@@ -913,7 +934,7 @@
* @suppress
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // protected otherwise
- fun getNullPaddedList(): NullPaddedList<T> = storage
+ public fun getNullPaddedList(): NullPaddedList<T> = storage
internal var refreshRetryCallback: Runnable? = null
@@ -925,7 +946,7 @@
* @suppress
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- fun lastLoad(): Int = storage.lastLoadAroundIndex
+ public fun lastLoad(): Int = storage.lastLoadAroundIndex
internal val requiredRemainder = config.prefetchDistance * 2 + config.pageSize
@@ -940,7 +961,7 @@
*
* @see loadedCount
*/
- override val size
+ override val size: Int
get() = storage.size
/**
@@ -952,7 +973,7 @@
"offers indirect ways of controlling fetch ('loadAround()', 'retry()') so that " +
"you should not need to access the DataSource/PagingSource."
)
- val dataSource: DataSource<*, T>
+ public val dataSource: DataSource<*, T>
@Suppress("DocumentExceptions")
get() {
val pagingSource = pagingSource
@@ -976,7 +997,7 @@
*
* @return Key of position most recently passed to [loadAround].
*/
- abstract val lastKey: Any?
+ public abstract val lastKey: Any?
/**
* True if the [PagedList] has detached the [PagingSource] it was loading from, and will no
@@ -986,19 +1007,19 @@
*
* @return `true` if the data source is detached.
*/
- abstract val isDetached: Boolean
+ public abstract val isDetached: Boolean
/**
* @suppress
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
- abstract fun dispatchCurrentLoadState(callback: (LoadType, LoadState) -> Unit)
+ public abstract fun dispatchCurrentLoadState(callback: (LoadType, LoadState) -> Unit)
/**
* @suppress
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
- abstract fun loadAroundInternal(index: Int)
+ public abstract fun loadAroundInternal(index: Int)
/**
* Detach the [PagedList] from its [PagingSource], and attempt to load no more data.
@@ -1007,7 +1028,7 @@
* signal to stop loading. The [PagedList] will continue to present existing data, but will not
* initiate new loads.
*/
- abstract fun detach()
+ public abstract fun detach()
/**
* Returns the number of items loaded in the [PagedList].
@@ -1021,7 +1042,7 @@
*
* @see size
*/
- val loadedCount
+ public val loadedCount: Int
get() = storage.storageCount
/**
@@ -1034,7 +1055,7 @@
*
* @return `true` if the [PagedList] is immutable.
*/
- open val isImmutable
+ public open val isImmutable: Boolean
get() = isDetached
/**
@@ -1046,14 +1067,14 @@
* If placeholders are enabled, this value is always `0`, since `get(i)` will return either
* the data in its original index, or null if it is not loaded.
*/
- val positionOffset: Int
+ public val positionOffset: Int
get() = storage.positionOffset
/**
* @suppress
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- open fun setInitialLoadState(loadType: LoadType, loadState: LoadState) {
+ public open fun setInitialLoadState(loadType: LoadType, loadState: LoadState) {
}
/**
@@ -1069,13 +1090,13 @@
* @see addWeakLoadStateListener
* @see removeWeakLoadStateListener
*/
- open fun retry() {}
+ public open fun retry() {}
/**
* @suppress
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
- fun setRetryCallback(refreshRetryCallback: Runnable?) {
+ public fun setRetryCallback(refreshRetryCallback: Runnable?) {
this.refreshRetryCallback = refreshRetryCallback
}
@@ -1095,7 +1116,7 @@
*
* @see size
*/
- override fun get(index: Int) = storage[index]
+ public override fun get(index: Int): T? = storage[index]
/**
* Load adjacent items to passed index.
@@ -1104,7 +1125,7 @@
*
* @throws IndexOutOfBoundsException if index is not within bounds.
*/
- fun loadAround(index: Int) {
+ public fun loadAround(index: Int) {
if (index < 0 || index >= size) {
throw IndexOutOfBoundsException("Index: $index, Size: $size")
}
@@ -1120,7 +1141,7 @@
*
* @return Immutable snapshot of [PagedList] data.
*/
- fun snapshot(): List<T> = when {
+ public fun snapshot(): List<T> = when {
isImmutable -> this
else -> SnapshotPagedList(this)
}
@@ -1132,7 +1153,7 @@
*
* @see removeWeakLoadStateListener
*/
- fun addWeakLoadStateListener(listener: (LoadType, LoadState) -> Unit) {
+ public fun addWeakLoadStateListener(listener: (LoadType, LoadState) -> Unit) {
// Clean up any empty weak refs.
loadStateListeners.removeAll { it.get() == null }
@@ -1148,7 +1169,7 @@
*
* @see addWeakLoadStateListener
*/
- fun removeWeakLoadStateListener(listener: (LoadType, LoadState) -> Unit) {
+ public fun removeWeakLoadStateListener(listener: (LoadType, LoadState) -> Unit) {
loadStateListeners.removeAll { it.get() == null || it.get() === listener }
}
@@ -1178,7 +1199,7 @@
"tracked by attaching a Callback to the PagedList that is mutating, and tracking " +
"changes since calling PagedList.snapshot()."
)
- fun addWeakCallback(previousSnapshot: List<T>?, callback: Callback) {
+ public fun addWeakCallback(previousSnapshot: List<T>?, callback: Callback) {
if (previousSnapshot != null && previousSnapshot !== this) {
dispatchNaiveUpdatesSinceSnapshot(size, previousSnapshot.size, callback)
}
@@ -1198,7 +1219,7 @@
* @see removeWeakCallback
*/
@Suppress("RegistrationName")
- fun addWeakCallback(callback: Callback) {
+ public fun addWeakCallback(callback: Callback) {
// first, clean up any empty weak refs
callbacks.removeAll { it.get() == null }
@@ -1214,7 +1235,7 @@
* @see addWeakCallback
*/
@Suppress("RegistrationName")
- fun removeWeakCallback(callback: Callback) {
+ public fun removeWeakCallback(callback: Callback) {
callbacks.removeAll { it.get() == null || it.get() === callback }
}
@@ -1227,7 +1248,7 @@
* @suppress
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
- fun notifyChanged(position: Int, count: Int) {
+ public fun notifyChanged(position: Int, count: Int) {
if (count == 0) return
callbacks.reversed().forEach { it.get()?.onChanged(position, count) }
}
@@ -1236,7 +1257,7 @@
* @suppress
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
- fun notifyRemoved(position: Int, count: Int) {
+ public fun notifyRemoved(position: Int, count: Int) {
if (count == 0) return
callbacks.reversed().forEach { it.get()?.onRemoved(position, count) }
}
@@ -1262,7 +1283,7 @@
)
@JvmSynthetic
@Deprecated("DataSource is deprecated and has been replaced by PagingSource")
-fun <Key : Any, Value : Any> PagedList(
+public fun <Key : Any, Value : Any> PagedList(
dataSource: DataSource<Key, Value>,
config: PagedList.Config,
notifyExecutor: Executor,
@@ -1270,7 +1291,6 @@
boundaryCallback: PagedList.BoundaryCallback<Value>? = null,
initialKey: Key? = null
): PagedList<Value> {
- @Suppress("DEPRECATION")
return PagedList.Builder(dataSource, config)
.setNotifyExecutor(notifyExecutor)
.setFetchExecutor(fetchExecutor)
diff --git a/paging/common/src/main/kotlin/androidx/paging/PagedListConfig.kt b/paging/common/src/main/kotlin/androidx/paging/PagedListConfig.kt
index 77e3f62..0e4645f 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PagedListConfig.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PagedListConfig.kt
@@ -31,7 +31,7 @@
"DEPRECATION"
)
@JvmSynthetic
-fun Config(
+public fun Config(
pageSize: Int,
prefetchDistance: Int = pageSize,
enablePlaceholders: Boolean = true,
diff --git a/paging/common/src/main/kotlin/androidx/paging/Pager.kt b/paging/common/src/main/kotlin/androidx/paging/Pager.kt
index 8f2483d..3513a38 100644
--- a/paging/common/src/main/kotlin/androidx/paging/Pager.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/Pager.kt
@@ -37,7 +37,7 @@
* RxJava support is available as extension properties provided by the
* `androidx.paging:paging-rxjava2` artifact.
*/
-class Pager<Key : Any, Value : Any>
+public class Pager<Key : Any, Value : Any>
// Experimental usage is propagated to public API via constructor argument.
@ExperimentalPagingApi constructor(
config: PagingConfig,
@@ -48,7 +48,7 @@
// Experimental usage is internal, so opt-in is allowed here.
@JvmOverloads
@OptIn(ExperimentalPagingApi::class)
- constructor(
+ public constructor(
config: PagingConfig,
initialKey: Key? = null,
pagingSourceFactory: () -> PagingSource<Key, Value>
@@ -59,7 +59,7 @@
* invalidated by [PagingSource.invalidate] or calls to [AsyncPagingDataDiffer.refresh] or
* [PagingDataAdapter.refresh].
*/
- val flow: Flow<PagingData<Value>> = PageFetcher(
+ public val flow: Flow<PagingData<Value>> = PageFetcher(
pagingSourceFactory = if (
pagingSourceFactory is SuspendingPagingSourceFactory<Key, Value>
) {
diff --git a/paging/common/src/main/kotlin/androidx/paging/PagingConfig.kt b/paging/common/src/main/kotlin/androidx/paging/PagingConfig.kt
index 14b74ea..5b091ac 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PagingConfig.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PagingConfig.kt
@@ -24,7 +24,7 @@
* An object used to configure loading behavior within a [Pager], as it loads content from a
* [PagingSource].
*/
-class PagingConfig @JvmOverloads constructor(
+public class PagingConfig @JvmOverloads public constructor(
/**
* Defines the number of items loaded at once from the [PagingSource].
*
@@ -45,7 +45,7 @@
* [Page][PagingSource.LoadResult.Page].
*/
@JvmField
- val pageSize: Int,
+ public val pageSize: Int,
/**
* Prefetch distance which defines how far from the edge of loaded content an access must be to
@@ -61,7 +61,7 @@
*/
@JvmField
@IntRange(from = 0)
- val prefetchDistance: Int = pageSize,
+ public val prefetchDistance: Int = pageSize,
/**
* Defines whether [PagingData] may display `null` placeholders, if the [PagingSource]
@@ -76,7 +76,7 @@
* 2) [enablePlaceholders] is set to `true`
*/
@JvmField
- val enablePlaceholders: Boolean = true,
+ public val enablePlaceholders: Boolean = true,
/**
* Defines requested load size for initial load from [PagingSource], typically larger than
@@ -89,7 +89,7 @@
*/
@JvmField
@IntRange(from = 1)
- val initialLoadSize: Int = pageSize * DEFAULT_INITIAL_PAGE_MULTIPLIER,
+ public val initialLoadSize: Int = pageSize * DEFAULT_INITIAL_PAGE_MULTIPLIER,
/**
* Defines the maximum number of items that may be loaded into [PagingData] before pages should
* be dropped.
@@ -115,7 +115,7 @@
*/
@JvmField
@IntRange(from = 2)
- val maxSize: Int = MAX_SIZE_UNBOUNDED,
+ public val maxSize: Int = MAX_SIZE_UNBOUNDED,
/**
* Defines a threshold for the number of items scrolled outside the bounds of loaded items
@@ -131,7 +131,7 @@
* @see PagingSource.jumpingSupported
*/
@JvmField
- val jumpThreshold: Int = COUNT_UNDEFINED
+ public val jumpThreshold: Int = COUNT_UNDEFINED
) {
init {
if (!enablePlaceholders && prefetchDistance == 0) {
@@ -154,13 +154,13 @@
}
}
- companion object {
+ public companion object {
/**
* When [maxSize] is set to [MAX_SIZE_UNBOUNDED], the maximum number of items loaded is
* unbounded, and pages will never be dropped.
*/
@Suppress("MinMaxConstant")
- const val MAX_SIZE_UNBOUNDED = Int.MAX_VALUE
+ public const val MAX_SIZE_UNBOUNDED: Int = Int.MAX_VALUE
internal const val DEFAULT_INITIAL_PAGE_MULTIPLIER = 3
}
}
\ No newline at end of file
diff --git a/paging/common/src/main/kotlin/androidx/paging/PagingData.kt b/paging/common/src/main/kotlin/androidx/paging/PagingData.kt
index 7a320d3..d408608 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PagingData.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PagingData.kt
@@ -25,12 +25,12 @@
* Each refresh of data (generally either pushed by local storage, or pulled from the network)
* will have a separate corresponding [PagingData].
*/
-class PagingData<T : Any> internal constructor(
+public class PagingData<T : Any> internal constructor(
internal val flow: Flow<PageEvent<T>>,
internal val receiver: UiReceiver
) {
- companion object {
+ public companion object {
internal val NOOP_RECEIVER = object : UiReceiver {
override fun accessHint(viewportHint: ViewportHint) {}
@@ -51,7 +51,7 @@
*/
@Suppress("UNCHECKED_CAST")
@JvmStatic // Convenience for Java developers.
- fun <T : Any> empty() = EMPTY as PagingData<T>
+ public fun <T : Any> empty(): PagingData<T> = EMPTY as PagingData<T>
/**
* Create a [PagingData] that immediately displays a static list of items when submitted to
@@ -60,7 +60,7 @@
* @param data Static list of [T] to display.
*/
@JvmStatic // Convenience for Java developers.
- fun <T : Any> from(data: List<T>) = PagingData(
+ public fun <T : Any> from(data: List<T>): PagingData<T> = PagingData(
flow = flowOf(
PageEvent.Insert.Refresh(
pages = listOf(TransformablePage(originalPageOffset = 0, data = data)),
diff --git a/paging/common/src/main/kotlin/androidx/paging/PagingDataDiffer.kt b/paging/common/src/main/kotlin/androidx/paging/PagingDataDiffer.kt
index fd24eba..7f6b49e 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PagingDataDiffer.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PagingDataDiffer.kt
@@ -33,7 +33,7 @@
/** @suppress */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-abstract class PagingDataDiffer<T : Any>(
+public abstract class PagingDataDiffer<T : Any>(
private val differCallback: DifferCallback,
private val mainDispatcher: CoroutineDispatcher = Dispatchers.Main
) {
@@ -112,7 +112,7 @@
* result between [previousList] and [newList]. Null if [newList] or [previousList] lists are
* empty, where it does not make sense to transform [lastAccessedIndex].
*/
- abstract suspend fun presentNewList(
+ public abstract suspend fun presentNewList(
previousList: NullPaddedList<T>,
newList: NullPaddedList<T>,
newCombinedLoadStates: CombinedLoadStates,
@@ -120,106 +120,112 @@
onListPresentable: () -> Unit,
): Int?
- open fun postEvents(): Boolean = false
+ public open fun postEvents(): Boolean = false
- suspend fun collectFrom(pagingData: PagingData<T>) = collectFromRunner.runInIsolation {
- receiver = pagingData.receiver
+ public suspend fun collectFrom(pagingData: PagingData<T>) {
+ collectFromRunner.runInIsolation {
+ receiver = pagingData.receiver
- // TODO: Validate only empty pages between separator pages and its dependent pages.
- pagingData.flow.collect { event ->
- withContext<Unit>(mainDispatcher) {
- if (event is PageEvent.Insert && event.loadType == REFRESH) {
- lastAccessedIndexUnfulfilled = false
-
- val newPresenter = PagePresenter(event)
- var >
- val transformedLastAccessedIndex = presentNewList(
- previousList = presenter,
- newList = newPresenter,
- newCombinedLoadStates = event.combinedLoadStates,
- lastAccessedIndex = lastAccessedIndex,
- >
- presenter = newPresenter
- >
- }
- )
- check(onListPresentableCalled) {
- "Missing call to onListPresentable after new list was presented. If you " +
- "are seeing this exception, it is generally an indication of an " +
- "issue with Paging. Please file a bug so we can fix it at: " +
- "https://issuetracker.google.com/issues/new?component=413106"
- }
-
- // Dispatch LoadState updates as soon as we are done diffing, but after setting
- // presenter.
- dispatchLoadStates(event.combinedLoadStates)
-
- if (transformedLastAccessedIndex == null) {
- // Send an initialize hint in case the new list is empty, which would
- // prevent a ViewportHint.Access from ever getting sent since there are
- // no items to bind from initial load.
- receiver?.accessHint(newPresenter.initializeHint())
- } else {
- // Transform the last loadAround index from the old list to the new list
- // by passing it through the DiffResult, and pass it forward as a
- // ViewportHint within the new list to the next generation of Pager.
- // This ensures prefetch distance for the last ViewportHint from the old
- // list is respected in the new list, even if invalidation interrupts
- // the prepend / append load that would have fulfilled it in the old
- // list.
- lastAccessedIndex = transformedLastAccessedIndex
- receiver?.accessHint(
- newPresenter.accessHintForPresenterIndex(transformedLastAccessedIndex)
- )
- }
- } else {
- if (postEvents()) {
- yield()
- }
-
- // Send event to presenter to be shown to the UI.
- presenter.processEvent(event, processPageEventCallback)
-
- // Reset lastAccessedIndexUnfulfilled if a page is dropped, to avoid infinite
- // loops when maxSize is insufficiently large.
- if (event is PageEvent.Drop) {
+ // TODO: Validate only empty pages between separator pages and its dependent pages.
+ pagingData.flow.collect { event ->
+ withContext<Unit>(mainDispatcher) {
+ if (event is PageEvent.Insert && event.loadType == REFRESH) {
lastAccessedIndexUnfulfilled = false
- }
- // If index points to a placeholder after transformations, resend it unless
- // there are no more items to load.
- if (event is PageEvent.Insert) {
- val prependDone =
- event.combinedLoadStates.prepend.endOfPaginationReached
- val appendDone = event.combinedLoadStates.append.endOfPaginationReached
- val canContinueLoading = !(event.loadType == PREPEND && prependDone) &&
- !(event.loadType == APPEND && appendDone)
+ val newPresenter = PagePresenter(event)
+ var >
+ val transformedLastAccessedIndex = presentNewList(
+ previousList = presenter,
+ newList = newPresenter,
+ newCombinedLoadStates = event.combinedLoadStates,
+ lastAccessedIndex = lastAccessedIndex,
+ >
+ presenter = newPresenter
+ >
+ }
+ )
+ check(onListPresentableCalled) {
+ "Missing call to onListPresentable after new list was presented. If " +
+ "you are seeing this exception, it is generally an indication of " +
+ "an issue with Paging. Please file a bug so we can fix it at: " +
+ "https://issuetracker.google.com/issues/new?component=413106"
+ }
- /**
- * If the insert is empty due to aggressive filtering, another hint must be
- * sent to fetcher-side to notify that PagingDataDiffer received the page,
- * since fetcher estimates prefetchDistance based on page indices presented
- * by PagingDataDiffer and we cannot rely on a new item being bound to
- * trigger another hint since the presented page is empty.
- */
- val emptyInsert = event.pages.all { it.data.isEmpty() }
- if (!canContinueLoading) {
- // Reset lastAccessedIndexUnfulfilled since endOfPaginationReached
- // means there are no more pages to load that could fulfill this index.
- lastAccessedIndexUnfulfilled = false
- } else if (lastAccessedIndexUnfulfilled || emptyInsert) {
- val shouldResendHint = emptyInsert ||
- lastAccessedIndex < presenter.placeholdersBefore ||
- lastAccessedIndex > presenter.placeholdersBefore +
- presenter.storageCount
+ // Dispatch LoadState updates as soon as we are done diffing, but after
+ // setting presenter.
+ dispatchLoadStates(event.combinedLoadStates)
- if (shouldResendHint) {
- receiver?.accessHint(
- presenter.accessHintForPresenterIndex(lastAccessedIndex)
+ if (transformedLastAccessedIndex == null) {
+ // Send an initialize hint in case the new list is empty, which would
+ // prevent a ViewportHint.Access from ever getting sent since there are
+ // no items to bind from initial load.
+ receiver?.accessHint(newPresenter.initializeHint())
+ } else {
+ // Transform the last loadAround index from the old list to the new list
+ // by passing it through the DiffResult, and pass it forward as a
+ // ViewportHint within the new list to the next generation of Pager.
+ // This ensures prefetch distance for the last ViewportHint from the old
+ // list is respected in the new list, even if invalidation interrupts
+ // the prepend / append load that would have fulfilled it in the old
+ // list.
+ lastAccessedIndex = transformedLastAccessedIndex
+ receiver?.accessHint(
+ newPresenter.accessHintForPresenterIndex(
+ transformedLastAccessedIndex
)
- } else {
- // lastIndex fulfilled, so reset lastAccessedIndexUnfulfilled.
+ )
+ }
+ } else {
+ if (postEvents()) {
+ yield()
+ }
+
+ // Send event to presenter to be shown to the UI.
+ presenter.processEvent(event, processPageEventCallback)
+
+ // Reset lastAccessedIndexUnfulfilled if a page is dropped, to avoid
+ // infinite loops when maxSize is insufficiently large.
+ if (event is PageEvent.Drop) {
+ lastAccessedIndexUnfulfilled = false
+ }
+
+ // If index points to a placeholder after transformations, resend it unless
+ // there are no more items to load.
+ if (event is PageEvent.Insert) {
+ val prependDone =
+ event.combinedLoadStates.prepend.endOfPaginationReached
+ val appendDone = event.combinedLoadStates.append.endOfPaginationReached
+ val canContinueLoading = !(event.loadType == PREPEND && prependDone) &&
+ !(event.loadType == APPEND && appendDone)
+
+ /**
+ * If the insert is empty due to aggressive filtering, another hint
+ * must be sent to fetcher-side to notify that PagingDataDiffer
+ * received the page, since fetcher estimates prefetchDistance based on
+ * page indices presented by PagingDataDiffer and we cannot rely on a
+ * new item being bound to trigger another hint since the presented
+ * page is empty.
+ */
+ val emptyInsert = event.pages.all { it.data.isEmpty() }
+ if (!canContinueLoading) {
+ // Reset lastAccessedIndexUnfulfilled since endOfPaginationReached
+ // means there are no more pages to load that could fulfill this
+ // index.
lastAccessedIndexUnfulfilled = false
+ } else if (lastAccessedIndexUnfulfilled || emptyInsert) {
+ val shouldResendHint = emptyInsert ||
+ lastAccessedIndex < presenter.placeholdersBefore ||
+ lastAccessedIndex > presenter.placeholdersBefore +
+ presenter.storageCount
+
+ if (shouldResendHint) {
+ receiver?.accessHint(
+ presenter.accessHintForPresenterIndex(lastAccessedIndex)
+ )
+ } else {
+ // lastIndex fulfilled, so reset lastAccessedIndexUnfulfilled.
+ lastAccessedIndexUnfulfilled = false
+ }
}
}
}
@@ -235,7 +241,7 @@
* @param index Index of the presented item to return, including placeholders.
* @return The presented item at position [index], `null` if it is a placeholder.
*/
- operator fun get(@IntRange(from = 0) index: Int): T? {
+ public operator fun get(@IntRange(from = 0) index: Int): T? {
lastAccessedIndexUnfulfilled = true
lastAccessedIndex = index
@@ -250,7 +256,7 @@
* @param index Index of the presented item to return, including placeholders.
* @return The presented item at position [index], `null` if it is a placeholder
*/
- fun peek(@IntRange(from = 0) index: Int): T? {
+ public fun peek(@IntRange(from = 0) index: Int): T? {
return presenter.get(index)
}
@@ -258,7 +264,7 @@
* Returns a new [ItemSnapshotList] representing the currently presented items, including any
* placeholders if they are enabled.
*/
- fun snapshot(): ItemSnapshotList<T> = presenter.snapshot()
+ public fun snapshot(): ItemSnapshotList<T> = presenter.snapshot()
/**
* Retry any failed load requests that would result in a [LoadState.Error] update to this
@@ -271,7 +277,7 @@
* * [PagingSource.load] returning [PagingSource.LoadResult.Error]
* * [RemoteMediator.load] returning [RemoteMediator.MediatorResult.Error]
*/
- fun retry() {
+ public fun retry() {
receiver?.retry()
}
@@ -291,14 +297,14 @@
*
* @sample androidx.paging.samples.refreshSample
*/
- fun refresh() {
+ public fun refresh() {
receiver?.refresh()
}
/**
* @return Total number of presented items, including placeholders.
*/
- val size: Int
+ public val size: Int
get() = presenter.size
private val _combinedLoadState = MutableStateFlow(combinedLoadStates.snapshot())
@@ -312,7 +318,7 @@
*
* @sample androidx.paging.samples.loadStateFlowSample
*/
- val loadStateFlow: Flow<CombinedLoadStates>
+ public val loadStateFlow: Flow<CombinedLoadStates>
get() = _combinedLoadState
init {
@@ -333,7 +339,7 @@
*
* @sample androidx.paging.samples.addLoadStateListenerSample
*/
- fun addLoadStateListener(listener: (CombinedLoadStates) -> Unit) {
+ public fun addLoadStateListener(listener: (CombinedLoadStates) -> Unit) {
// Note: Important to add the listener first before sending off events, in case the
// callback triggers removal, which could lead to a leak if the listener is added
// afterwards.
@@ -347,7 +353,7 @@
* @param listener Previously registered listener.
* @see addLoadStateListener
*/
- fun removeLoadStateListener(listener: (CombinedLoadStates) -> Unit) {
+ public fun removeLoadStateListener(listener: (CombinedLoadStates) -> Unit) {
loadStateListeners.remove(listener)
}
}
@@ -362,8 +368,8 @@
* @suppress
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-interface DifferCallback {
- fun onChanged(position: Int, count: Int)
- fun onInserted(position: Int, count: Int)
- fun onRemoved(position: Int, count: Int)
+public interface DifferCallback {
+ public fun onChanged(position: Int, count: Int)
+ public fun onInserted(position: Int, count: Int)
+ public fun onRemoved(position: Int, count: Int)
}
diff --git a/paging/common/src/main/kotlin/androidx/paging/PagingDataTransforms.kt b/paging/common/src/main/kotlin/androidx/paging/PagingDataTransforms.kt
index 836b86a..c867379 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PagingDataTransforms.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PagingDataTransforms.kt
@@ -38,7 +38,7 @@
*/
@CheckResult
@JvmSynthetic
-fun <T : Any, R : Any> PagingData<T>.map(
+public fun <T : Any, R : Any> PagingData<T>.map(
transform: suspend (T) -> R
): PagingData<R> = transform { it.map(transform) }
@@ -49,7 +49,7 @@
* @see PagingData.map
*/
@CheckResult
-fun <T : Any, R : Any> PagingData<T>.map(
+public fun <T : Any, R : Any> PagingData<T>.map(
executor: Executor,
transform: (T) -> R,
): PagingData<R> = transform { event ->
@@ -64,7 +64,7 @@
*/
@CheckResult
@JvmSynthetic
-fun <T : Any, R : Any> PagingData<T>.flatMap(
+public fun <T : Any, R : Any> PagingData<T>.flatMap(
transform: suspend (T) -> Iterable<R>
): PagingData<R> = transform { it.flatMap(transform) }
@@ -75,7 +75,7 @@
* @see flatMap
*/
@CheckResult
-fun <T : Any, R : Any> PagingData<T>.flatMap(
+public fun <T : Any, R : Any> PagingData<T>.flatMap(
executor: Executor,
transform: (T) -> Iterable<R>
): PagingData<R> = transform { event ->
@@ -89,7 +89,7 @@
*/
@CheckResult
@JvmSynthetic
-fun <T : Any> PagingData<T>.filter(
+public fun <T : Any> PagingData<T>.filter(
predicate: suspend (T) -> Boolean
): PagingData<T> = transform { it.filter(predicate) }
@@ -100,7 +100,7 @@
*/
@CheckResult
@JvmName("filter")
-fun <T : Any> PagingData<T>.filter(
+public fun <T : Any> PagingData<T>.filter(
executor: Executor,
predicate: (T) -> Boolean
): PagingData<T> = transform { event ->
@@ -130,7 +130,7 @@
*/
@CheckResult
@JvmSynthetic
-fun <T : R, R : Any> PagingData<T>.insertSeparators(
+public fun <T : R, R : Any> PagingData<T>.insertSeparators(
terminalSeparatorType: TerminalSeparatorType = FULLY_COMPLETE,
generator: suspend (T?, T?) -> R?,
): PagingData<R> {
@@ -252,7 +252,7 @@
*/
@CheckResult
@JvmOverloads
-fun <R : Any, T : R> PagingData<T>.insertSeparators(
+public fun <R : Any, T : R> PagingData<T>.insertSeparators(
terminalSeparatorType: TerminalSeparatorType = FULLY_COMPLETE,
executor: Executor,
generator: (T?, T?) -> R?,
@@ -287,10 +287,10 @@
*/
@CheckResult
@JvmOverloads
-fun <T : Any> PagingData<T>.insertHeaderItem(
+public fun <T : Any> PagingData<T>.insertHeaderItem(
terminalSeparatorType: TerminalSeparatorType = FULLY_COMPLETE,
item: T,
-) = insertSeparators(terminalSeparatorType) { before, _ ->
+): PagingData<T> = insertSeparators(terminalSeparatorType) { before, _ ->
if (before == null) item else null
}
@@ -317,9 +317,9 @@
*/
@CheckResult
@JvmOverloads
-fun <T : Any> PagingData<T>.insertFooterItem(
+public fun <T : Any> PagingData<T>.insertFooterItem(
terminalSeparatorType: TerminalSeparatorType = FULLY_COMPLETE,
item: T,
-) = insertSeparators(terminalSeparatorType) { _, after ->
+): PagingData<T> = insertSeparators(terminalSeparatorType) { _, after ->
if (after == null) item else null
}
diff --git a/paging/common/src/main/kotlin/androidx/paging/PagingSource.kt b/paging/common/src/main/kotlin/androidx/paging/PagingSource.kt
index bfeaf97..05aa243 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PagingSource.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PagingSource.kt
@@ -23,17 +23,16 @@
import java.util.concurrent.CopyOnWriteArrayList
import java.util.concurrent.atomic.AtomicBoolean
-/**
- * @suppress
- */
+/** @suppress */
@Suppress("DEPRECATION")
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-fun <Key : Any> PagedList.Config.toRefreshLoadParams(key: Key?): PagingSource.LoadParams<Key> =
- PagingSource.LoadParams.Refresh(
- key,
- initialLoadSizeHint,
- enablePlaceholders,
- )
+public fun <Key : Any> PagedList.Config.toRefreshLoadParams(
+ key: Key?
+): PagingSource.LoadParams<Key> = PagingSource.LoadParams.Refresh(
+ key,
+ initialLoadSizeHint,
+ enablePlaceholders,
+)
/**
* Base class for an abstraction of pageable static data from some source, where loading pages
@@ -81,25 +80,25 @@
*
* @see Pager
*/
-abstract class PagingSource<Key : Any, Value : Any> {
+public abstract class PagingSource<Key : Any, Value : Any> {
/**
* Params for a load request on a [PagingSource] from [PagingSource.load].
*/
- sealed class LoadParams<Key : Any> constructor(
+ public sealed class LoadParams<Key : Any> constructor(
/**
* Requested number of items to load.
*
* Note: It is valid for [PagingSource.load] to return a [LoadResult] that has a different
* number of items than the requested load size.
*/
- val loadSize: Int,
+ public val loadSize: Int,
/**
* From [PagingConfig.enablePlaceholders], true if placeholders are enabled and the load
* request for this [LoadParams] should populate [LoadResult.Page.itemsBefore] and
* [LoadResult.Page.itemsAfter] if possible.
*/
- val placeholdersEnabled: Boolean,
+ public val placeholdersEnabled: Boolean,
) {
/**
* Key for the page to be loaded.
@@ -116,13 +115,13 @@
* * [Prepend] - [LoadResult.Page.prevKey] of the loaded page at the front of the list.
* * [Append] - [LoadResult.Page.nextKey] of the loaded page at the end of the list.
*/
- abstract val key: Key?
+ public abstract val key: Key?
/**
* Params for an initial load request on a [PagingSource] from [PagingSource.load] or a
* refresh triggered by [invalidate].
*/
- class Refresh<Key : Any> constructor(
+ public class Refresh<Key : Any> constructor(
override val key: Key?,
loadSize: Int,
placeholdersEnabled: Boolean,
@@ -135,7 +134,7 @@
* Params to load a page of data from a [PagingSource] via [PagingSource.load] to be
* appended to the end of the list.
*/
- class Append<Key : Any> constructor(
+ public class Append<Key : Any> constructor(
override val key: Key,
loadSize: Int,
placeholdersEnabled: Boolean,
@@ -148,7 +147,7 @@
* Params to load a page of data from a [PagingSource] via [PagingSource.load] to be
* prepended to the start of the list.
*/
- class Prepend<Key : Any> constructor(
+ public class Prepend<Key : Any> constructor(
override val key: Key,
loadSize: Int,
placeholdersEnabled: Boolean,
@@ -190,7 +189,7 @@
/**
* Result of a load request from [PagingSource.load].
*/
- sealed class LoadResult<Key : Any, Value : Any> {
+ public sealed class LoadResult<Key : Any, Value : Any> {
/**
* Error result object for [PagingSource.load].
*
@@ -200,7 +199,7 @@
*
* @sample androidx.paging.samples.pageKeyedPagingSourceSample
*/
- data class Error<Key : Any, Value : Any>(
+ public data class Error<Key : Any, Value : Any>(
val throwable: Throwable
) : LoadResult<Key, Value>()
@@ -210,7 +209,7 @@
* @sample androidx.paging.samples.pageKeyedPage
* @sample androidx.paging.samples.pageIndexedPage
*/
- data class Page<Key : Any, Value : Any> constructor(
+ public data class Page<Key : Any, Value : Any> constructor(
/**
* Loaded data
*/
@@ -245,7 +244,7 @@
* @param nextKey [Key] for next page if more data can be loaded in that direction,
* `null` otherwise.
*/
- constructor(
+ public constructor(
data: List<Value>,
prevKey: Key?,
nextKey: Key?
@@ -261,8 +260,8 @@
}
}
- companion object {
- const val COUNT_UNDEFINED = Int.MIN_VALUE
+ public companion object {
+ public const val COUNT_UNDEFINED: Int = Int.MIN_VALUE
@Suppress("MemberVisibilityCanBePrivate") // Prevent synthetic accessor generation.
internal val EMPTY = Page(emptyList(), null, null, 0, 0)
@@ -286,14 +285,14 @@
*
* @see [PagingConfig.jumpThreshold]
*/
- open val jumpingSupported: Boolean
+ public open val jumpingSupported: Boolean
get() = false
/**
* `true` if this [PagingSource] expects to re-use keys to load distinct pages
* without a call to [invalidate], `false` otherwise.
*/
- open val keyReuseSupported: Boolean
+ public open val keyReuseSupported: Boolean
get() = false
@VisibleForTesting
@@ -305,7 +304,7 @@
* Whether this [PagingSource] has been invalidated, which should happen when the data this
* [PagingSource] represents changes since it was first instantiated.
*/
- val invalid: Boolean
+ public val invalid: Boolean
get() = _invalid.get()
/**
@@ -314,7 +313,7 @@
* This method is idempotent. i.e., If [invalidate] has already been called, subsequent calls to
* this method should have no effect.
*/
- fun invalidate() {
+ public fun invalidate() {
if (_invalid.compareAndSet(false, true)) {
onInvalidatedCallbacks.forEach { it.invoke() }
}
@@ -331,7 +330,7 @@
* @param onInvalidatedCallback The callback that will be invoked on thread that invalidates the
* [PagingSource].
*/
- fun registerInvalidatedCallback(onInvalidatedCallback: () -> Unit) {
+ public fun registerInvalidatedCallback(onInvalidatedCallback: () -> Unit) {
onInvalidatedCallbacks.add(onInvalidatedCallback)
}
@@ -340,7 +339,7 @@
*
* @param onInvalidatedCallback The previously added callback.
*/
- fun unregisterInvalidatedCallback(onInvalidatedCallback: () -> Unit) {
+ public fun unregisterInvalidatedCallback(onInvalidatedCallback: () -> Unit) {
onInvalidatedCallbacks.remove(onInvalidatedCallback)
}
@@ -349,7 +348,7 @@
*
* Implement this method to trigger your async load (e.g. from database or network).
*/
- abstract suspend fun load(params: LoadParams<Key>): LoadResult<Key, Value>
+ public abstract suspend fun load(params: LoadParams<Key>): LoadResult<Key, Value>
/**
* Provide a [Key] used for the initial [load] for the next [PagingSource] due to invalidation
@@ -376,5 +375,5 @@
* user's current viewport. If the correct [Key] cannot be determined, `null` can be returned
* to allow [load] decide what default key to use.
*/
- abstract fun getRefreshKey(state: PagingState<Key, Value>): Key?
+ public abstract fun getRefreshKey(state: PagingState<Key, Value>): Key?
}
diff --git a/paging/common/src/main/kotlin/androidx/paging/PagingState.kt b/paging/common/src/main/kotlin/androidx/paging/PagingState.kt
index df2899d..f599b9bf 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PagingState.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PagingState.kt
@@ -23,22 +23,22 @@
* Snapshot state of Paging system including the loaded [pages], the last accessed [anchorPosition],
* and the [config] used.
*/
-class PagingState<Key : Any, Value : Any> constructor(
+public class PagingState<Key : Any, Value : Any> constructor(
/**
* Loaded pages of data in the list.
*/
- val pages: List<Page<Key, Value>>,
+ public val pages: List<Page<Key, Value>>,
/**
* Most recently accessed index in the list, including placeholders.
*
* `null` if no access in the [PagingData] has been made yet. E.g., if this snapshot was
* generated before or during the first load.
*/
- val anchorPosition: Int?,
+ public val anchorPosition: Int?,
/**
* [PagingConfig] that was given when initializing the [PagingData] stream.
*/
- val config: PagingConfig,
+ public val config: PagingConfig,
/**
* Number of placeholders before the first loaded item if placeholders are enabled, otherwise 0.
*/
@@ -70,7 +70,7 @@
* @return The closest loaded [Value] in [pages] to the provided [anchorPosition]. `null` if
* all loaded [pages] are empty.
*/
- fun closestItemToPosition(anchorPosition: Int): Value? {
+ public fun closestItemToPosition(anchorPosition: Int): Value? {
if (pages.all { it.data.isEmpty() }) return null
anchorPositionToPagedIndices(anchorPosition) { pageIndex, index ->
@@ -97,7 +97,7 @@
* @return The closest loaded [Value] in [pages] to the provided [anchorPosition]. `null` if
* all loaded [pages] are empty.
*/
- fun closestPageToPosition(anchorPosition: Int): Page<Key, Value>? {
+ public fun closestPageToPosition(anchorPosition: Int): Page<Key, Value>? {
if (pages.all { it.data.isEmpty() }) return null
anchorPositionToPagedIndices(anchorPosition) { pageIndex, index ->
@@ -112,19 +112,23 @@
* @return `true` if all loaded pages are empty or no pages were loaded when this [PagingState]
* was created, `false` otherwise.
*/
- fun isEmpty() = pages.all { it.data.isEmpty() }
+ public fun isEmpty(): Boolean = pages.all { it.data.isEmpty() }
/**
* @return The first loaded item in the list or `null` if all loaded pages are empty or no pages
* were loaded when this [PagingState] was created.
*/
- fun firstItemOrNull(): Value? = pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
+ public fun firstItemOrNull(): Value? {
+ return pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
+ }
/**
* @return The last loaded item in the list or `null` if all loaded pages are empty or no pages
* were loaded when this [PagingState] was created.
*/
- fun lastItemOrNull(): Value? = pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()
+ public fun lastItemOrNull(): Value? {
+ return pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()
+ }
override fun toString(): String {
return "PagingState(pages=$pages, anchorPosition=$anchorPosition, config=$config, " +
diff --git a/paging/common/src/main/kotlin/androidx/paging/PositionalDataSource.kt b/paging/common/src/main/kotlin/androidx/paging/PositionalDataSource.kt
index f8ee981..3e36a62 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PositionalDataSource.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PositionalDataSource.kt
@@ -53,12 +53,12 @@
"androidx.paging.PagingSource"
)
)
-abstract class PositionalDataSource<T : Any> : DataSource<Int, T>(POSITIONAL) {
+public abstract class PositionalDataSource<T : Any> : DataSource<Int, T>(POSITIONAL) {
/**
* Holder object for inputs to [loadInitial].
*/
- open class LoadInitialParams(
+ public open class LoadInitialParams(
/**
* Initial load position requested.
*
@@ -66,27 +66,27 @@
* before you execute your load.
*/
@JvmField
- val requestedStartPosition: Int,
+ public val requestedStartPosition: Int,
/**
* Requested number of items to load.
*
* Note that this may be larger than available data.
*/
@JvmField
- val requestedLoadSize: Int,
+ public val requestedLoadSize: Int,
/**
* Defines page size acceptable for return values.
*
* List of items passed to the callback must be an integer multiple of page size.
*/
@JvmField
- val pageSize: Int,
+ public val pageSize: Int,
/**
* Defines whether placeholders are enabled, and whether the loaded total count will be
* ignored.
*/
@JvmField
- val placeholdersEnabled: Boolean
+ public val placeholdersEnabled: Boolean
) {
init {
check(requestedStartPosition >= 0) {
@@ -104,21 +104,21 @@
/**
* Holder object for inputs to [loadRange].
*/
- open class LoadRangeParams(
+ public open class LoadRangeParams(
/**
* START position of data to load.
*
* Returned data must start at this position.
*/
@JvmField
- val startPosition: Int,
+ public val startPosition: Int,
/**
* Number of items to load.
*
* Returned data must be of this size, unless at end of the list.
*/
@JvmField
- val loadSize: Int
+ public val loadSize: Int
)
/**
@@ -132,7 +132,7 @@
*
* @param T Type of items being loaded.
*/
- abstract class LoadInitialCallback<T> {
+ public abstract class LoadInitialCallback<T> {
/**
* Called to pass initial load state from a DataSource.
*
@@ -150,7 +150,7 @@
* Includes the number in the initial [data] parameter as well as any items that can be
* loaded in front or behind of [data].
*/
- abstract fun onResult(data: List<T>, position: Int, totalCount: Int)
+ public abstract fun onResult(data: List<T>, position: Int, totalCount: Int)
/**
* Called to pass initial load state from a DataSource without total count, when
@@ -168,7 +168,7 @@
* @param position Position of the item at the front of the list. If there are N items
* before the items in data that can be provided by this [DataSource], pass N.
*/
- abstract fun onResult(data: List<T>, position: Int)
+ public abstract fun onResult(data: List<T>, position: Int)
}
/**
@@ -182,21 +182,21 @@
*
* @param T Type of items being loaded.
*/
- abstract class LoadRangeCallback<T> {
+ public abstract class LoadRangeCallback<T> {
/**
* Called to pass loaded data from [loadRange].
*
* @param data List of items loaded from the [DataSource]. Must be same size as requested,
* unless at end of list.
*/
- abstract fun onResult(data: List<T>)
+ public abstract fun onResult(data: List<T>)
}
/**
* @suppress
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
- companion object {
+ public companion object {
/**
* Helper for computing an initial position in [loadInitial] when total data set size can be
* computed ahead of loading.
@@ -240,7 +240,7 @@
* @see [computeInitialLoadSize]
*/
@JvmStatic
- fun computeInitialLoadPosition(params: LoadInitialParams, totalCount: Int): Int {
+ public fun computeInitialLoadPosition(params: LoadInitialParams, totalCount: Int): Int {
val position = params.requestedStartPosition
val initialLoadSize = params.requestedLoadSize
val pageSize = params.pageSize
@@ -302,11 +302,11 @@
* @see [computeInitialLoadPosition]
*/
@JvmStatic
- fun computeInitialLoadSize(
+ public fun computeInitialLoadSize(
params: LoadInitialParams,
initialLoadPosition: Int,
totalCount: Int
- ) = minOf(totalCount - initialLoadPosition, params.requestedLoadSize)
+ ): Int = minOf(totalCount - initialLoadPosition, params.requestedLoadSize)
}
@Suppress("RedundantVisibilityModifier") // Metalava doesn't inherit visibility properly.
@@ -462,7 +462,7 @@
* set size.
*/
@WorkerThread
- abstract fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback<T>)
+ public abstract fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback<T>)
/**
* Called to load a range of data from the DataSource.
@@ -477,7 +477,7 @@
* @param callback Callback that receives loaded data.
*/
@WorkerThread
- abstract fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<T>)
+ public abstract fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<T>)
@Suppress("RedundantVisibilityModifier") // Metalava doesn't inherit visibility properly.
internal override val isContiguous = false
diff --git a/paging/common/src/main/kotlin/androidx/paging/RemoteMediator.kt b/paging/common/src/main/kotlin/androidx/paging/RemoteMediator.kt
index 706f33e..492b88c 100644
--- a/paging/common/src/main/kotlin/androidx/paging/RemoteMediator.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/RemoteMediator.kt
@@ -40,7 +40,7 @@
* @sample androidx.paging.samples.remoteMediatorPageKeyedSample
*/
@ExperimentalPagingApi
-abstract class RemoteMediator<Key : Any, Value : Any> {
+public abstract class RemoteMediator<Key : Any, Value : Any> {
/**
* Callback triggered when Paging needs to request more data from a remote source due to any of
* the following events:
@@ -85,7 +85,10 @@
* @return [MediatorResult] signifying what [LoadState] to be passed to the UI, and whether
* there's more data available.
*/
- abstract suspend fun load(loadType: LoadType, state: PagingState<Key, Value>): MediatorResult
+ public abstract suspend fun load(
+ loadType: LoadType,
+ state: PagingState<Key, Value>
+ ): MediatorResult
/**
* Callback fired during initialization of a [PagingData] stream, before initial load.
@@ -101,16 +104,16 @@
* * [SKIP_INITIAL_REFRESH] to wait for a refresh request from the UI before dispatching [load]
* asynchronously with load type [REFRESH].
*/
- open suspend fun initialize(): InitializeAction = LAUNCH_INITIAL_REFRESH
+ public open suspend fun initialize(): InitializeAction = LAUNCH_INITIAL_REFRESH
/**
* Return type of [load], which determines [LoadState].
*/
- sealed class MediatorResult {
+ public sealed class MediatorResult {
/**
* Recoverable error that can be retried, sets the [LoadState] to [LoadState.Error].
*/
- class Error(val throwable: Throwable) : MediatorResult()
+ public class Error(public val throwable: Throwable) : MediatorResult()
/**
* Success signaling that [LoadState] should be set to [LoadState.NotLoading] if
@@ -121,15 +124,15 @@
* [PagingSource.invalidate] to allow [androidx.paging.PagingDataAdapter] to pick up new
* items found by [load].
*/
- class Success(
- @get:JvmName("endOfPaginationReached") val endOfPaginationReached: Boolean
+ public class Success(
+ @get:JvmName("endOfPaginationReached") public val endOfPaginationReached: Boolean
) : MediatorResult()
}
/**
* Return type of [initialize], which signals the action to take after [initialize] completes.
*/
- enum class InitializeAction {
+ public enum class InitializeAction {
/**
* Immediately dispatch a [load] asynchronously with load type [REFRESH], to update
* paginated content when the stream is initialized.
diff --git a/paging/common/src/main/kotlin/androidx/paging/Separators.kt b/paging/common/src/main/kotlin/androidx/paging/Separators.kt
index 87440c4..c9c3731 100644
--- a/paging/common/src/main/kotlin/androidx/paging/Separators.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/Separators.kt
@@ -32,7 +32,7 @@
* Mode for configuring when terminal separators (header and footer) would be displayed by the
* [insertSeparators], [insertHeaderItem] or [insertFooterItem] operators on [PagingData].
*/
-enum class TerminalSeparatorType {
+public enum class TerminalSeparatorType {
/**
* Show terminal separators (header and footer) when both [PagingSource] and [RemoteMediator]
* reaches the end of pagination.
diff --git a/paging/common/src/test/kotlin/androidx/paging/PageFetcherSnapshotTest.kt b/paging/common/src/test/kotlin/androidx/paging/PageFetcherSnapshotTest.kt
index 1696612..16dd201 100644
--- a/paging/common/src/test/kotlin/androidx/paging/PageFetcherSnapshotTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/PageFetcherSnapshotTest.kt
@@ -29,6 +29,7 @@
import androidx.paging.PageEvent.Insert.Companion.Refresh
import androidx.paging.PageEvent.LoadStateUpdate
import androidx.paging.PagingSource.LoadResult.Page
+import androidx.paging.RemoteMediatorMock.LoadEvent
import androidx.paging.TestPagingSource.Companion.LOAD_ERROR
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CompletableDeferred
@@ -62,7 +63,6 @@
import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
import kotlin.test.assertNotNull
-import kotlin.test.assertNull
import kotlin.test.assertTrue
import kotlin.test.fail
@@ -1870,14 +1870,7 @@
}
@Test
- fun refreshKeyInfo_nullHint() = testScope.runBlockingTest {
- val pagingSource = pagingSourceFactory()
- val pager = PageFetcherSnapshot(50, pagingSource, config, retryFlow = retryBus.flow)
- assertNull(pager.refreshKeyInfo())
- }
-
- @Test
- fun refreshKeyInfo_pagesEmpty() = testScope.runBlockingTest {
+ fun currentPagingState_pagesEmptyWithHint() = testScope.runBlockingTest {
pauseDispatcher {
val pagingSource = pagingSourceFactory()
val pager = PageFetcherSnapshot(50, pagingSource, config, retryFlow = retryBus.flow)
@@ -1891,12 +1884,53 @@
originalPageOffsetLast = 0
)
)
- assertNull(pager.refreshKeyInfo())
+ assertThat(pager.currentPagingState()).isEqualTo(
+ PagingState<Int, Int>(
+ pages = listOf(),
+ anchorPosition = 0,
+ config = config,
+ leadingPlaceholderCount = 0
+ )
+ )
}
}
+ /**
+ * Verify we re-use previous PagingState for remote refresh if there are no pages loaded.
+ */
@Test
- fun refreshKeyInfo_loadedIndex() = testScope.runBlockingTest {
+ fun currentPagingState_ignoredOnEmptyPages() = testScope.runBlockingTest {
+ val remoteMediator = RemoteMediatorMock()
+ val pagingSource = pagingSourceFactory()
+ val pager = PageFetcherSnapshot(
+ initialKey = 50,
+ pagingSource = pagingSource,
+ config = config,
+ retryFlow = retryBus.flow,
+ remoteMediatorConnection = RemoteMediatorAccessor(testScope, remoteMediator)
+ )
+ pager.accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 0,
+ presentedItemsBefore = 0,
+ presentedItemsAfter = 1,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0
+ )
+ )
+ assertThat(pager.currentPagingState()).isEqualTo(
+ PagingState<Int, Int>(
+ pages = listOf(),
+ anchorPosition = 0,
+ config = config,
+ leadingPlaceholderCount = 0
+ )
+ )
+ }
+
+ @Test
+ fun currentPagingState_loadedIndex() = testScope.runBlockingTest {
pauseDispatcher {
val pagingSource = pagingSourceFactory()
val pager = PageFetcherSnapshot(50, pagingSource, config, retryFlow = retryBus.flow)
@@ -1915,17 +1949,17 @@
)
)
- val refreshKeyInfo = pager.refreshKeyInfo()
- assertNotNull(refreshKeyInfo)
- assertEquals(51, refreshKeyInfo.anchorPosition)
+ val pagingState = pager.currentPagingState()
+ assertNotNull(pagingState)
+ assertEquals(51, pagingState.anchorPosition)
// Assert from anchorPosition in placeholdersBefore
- assertEquals(50, refreshKeyInfo.closestItemToPosition(10))
+ assertEquals(50, pagingState.closestItemToPosition(10))
// Assert from anchorPosition in loaded indices
- assertEquals(50, refreshKeyInfo.closestItemToPosition(50))
- assertEquals(51, refreshKeyInfo.closestItemToPosition(51))
+ assertEquals(50, pagingState.closestItemToPosition(50))
+ assertEquals(51, pagingState.closestItemToPosition(51))
// Assert from anchorPosition in placeholdersAfter
- assertEquals(51, refreshKeyInfo.closestItemToPosition(90))
+ assertEquals(51, pagingState.closestItemToPosition(90))
val loadedPage = Page(
data = listOf(50, 51),
@@ -1934,20 +1968,20 @@
itemsBefore = 50,
itemsAfter = 48
)
- assertEquals(listOf(loadedPage), refreshKeyInfo.pages)
+ assertEquals(listOf(loadedPage), pagingState.pages)
// Assert from anchorPosition in placeholdersBefore
- assertEquals(loadedPage, refreshKeyInfo.closestPageToPosition(10))
+ assertEquals(loadedPage, pagingState.closestPageToPosition(10))
// Assert from anchorPosition in loaded indices
- assertEquals(loadedPage, refreshKeyInfo.closestPageToPosition(50))
- assertEquals(loadedPage, refreshKeyInfo.closestPageToPosition(51))
+ assertEquals(loadedPage, pagingState.closestPageToPosition(50))
+ assertEquals(loadedPage, pagingState.closestPageToPosition(51))
// Assert from anchorPosition in placeholdersAfter
- assertEquals(loadedPage, refreshKeyInfo.closestPageToPosition(90))
+ assertEquals(loadedPage, pagingState.closestPageToPosition(90))
}
}
}
@Test
- fun refreshKeyInfo_placeholdersBefore() = testScope.runBlockingTest {
+ fun currentPagingState_placeholdersBefore() = testScope.runBlockingTest {
pauseDispatcher {
val pagingSource = pagingSourceFactory()
val pager = PageFetcherSnapshot(50, pagingSource, config, retryFlow = retryBus.flow)
@@ -1966,9 +2000,9 @@
)
)
- val refreshKeyInfo = pager.refreshKeyInfo()
- assertNotNull(refreshKeyInfo)
- assertEquals(10, refreshKeyInfo.anchorPosition)
+ val pagingState = pager.currentPagingState()
+ assertNotNull(pagingState)
+ assertEquals(10, pagingState.anchorPosition)
assertEquals(
listOf(
Page(
@@ -1979,16 +2013,16 @@
itemsAfter = 48
)
),
- refreshKeyInfo.pages
+ pagingState.pages
)
// Assert from anchorPosition in placeholdersBefore
- assertEquals(50, refreshKeyInfo.closestItemToPosition(10))
+ assertEquals(50, pagingState.closestItemToPosition(10))
// Assert from anchorPosition in loaded indices
- assertEquals(50, refreshKeyInfo.closestItemToPosition(50))
- assertEquals(51, refreshKeyInfo.closestItemToPosition(51))
+ assertEquals(50, pagingState.closestItemToPosition(50))
+ assertEquals(51, pagingState.closestItemToPosition(51))
// Assert from anchorPosition in placeholdersAfter
- assertEquals(51, refreshKeyInfo.closestItemToPosition(90))
+ assertEquals(51, pagingState.closestItemToPosition(90))
val loadedPage = Page(
data = listOf(50, 51),
@@ -1998,18 +2032,18 @@
itemsAfter = 48
)
// Assert from anchorPosition in placeholdersBefore
- assertEquals(loadedPage, refreshKeyInfo.closestPageToPosition(10))
+ assertEquals(loadedPage, pagingState.closestPageToPosition(10))
// Assert from anchorPosition in loaded indices
- assertEquals(loadedPage, refreshKeyInfo.closestPageToPosition(50))
- assertEquals(loadedPage, refreshKeyInfo.closestPageToPosition(51))
+ assertEquals(loadedPage, pagingState.closestPageToPosition(50))
+ assertEquals(loadedPage, pagingState.closestPageToPosition(51))
// Assert from anchorPosition in placeholdersAfter
- assertEquals(loadedPage, refreshKeyInfo.closestPageToPosition(90))
+ assertEquals(loadedPage, pagingState.closestPageToPosition(90))
}
}
}
@Test
- fun pageFetcherSnapshot_currentPagingState() = testScope.runBlockingTest {
+ fun currentPagingState_noHint() = testScope.runBlockingTest {
val pager = PageFetcherSnapshot(
initialKey = 50,
pagingSource = TestPagingSource(loadDelay = 100),
@@ -2017,7 +2051,14 @@
retryFlow = retryBus.flow
)
- assertEquals(null, pager.refreshKeyInfo())
+ assertThat(pager.currentPagingState()).isEqualTo(
+ PagingState<Int, Int>(
+ pages = listOf(),
+ anchorPosition = null,
+ config = config,
+ leadingPlaceholderCount = 0,
+ )
+ )
}
@Test
@@ -2238,6 +2279,71 @@
}
@Test
+ fun remoteMediator_remoteRefreshCachesPreviousPagingState() = testScope.runBlockingTest {
+ @OptIn(ExperimentalPagingApi::class)
+ val remoteMediator = RemoteMediatorMock().apply {
+ initializeResult = RemoteMediator.InitializeAction.LAUNCH_INITIAL_REFRESH
+ loadCallback = { _, _ -> RemoteMediator.MediatorResult.Success(true) }
+ }
+
+ val config = PagingConfig(
+ pageSize = 1,
+ prefetchDistance = 2,
+ enablePlaceholders = true,
+ initialLoadSize = 1,
+ maxSize = 5
+ )
+ val pager = PageFetcher(
+ initialKey = 0,
+ pagingSourceFactory = { TestPagingSource(items = listOf(0)) },
+ config = config,
+ remoteMediator = remoteMediator
+ )
+
+ val state = collectFetcherState(pager)
+
+ // Let the initial page load; loaded data should be [0]
+ advanceUntilIdle()
+ assertThat(remoteMediator.newLoadEvents).containsExactly(
+ LoadEvent<Int, Int>(
+ loadType = REFRESH,
+ state = PagingState<Int, Int>(
+ pages = listOf(),
+ anchorPosition = null,
+ config = config,
+ leadingPlaceholderCount = 0,
+ ),
+ )
+ )
+
+ // Explicit call to refresh, which should trigger remote refresh with cached PagingState.
+ pager.refresh()
+ advanceUntilIdle()
+
+ assertThat(remoteMediator.newLoadEvents).containsExactly(
+ LoadEvent<Int, Int>(
+ loadType = REFRESH,
+ state = PagingState<Int, Int>(
+ pages = listOf(
+ Page(
+ data = listOf(0),
+ prevKey = null,
+ nextKey = null,
+ itemsBefore = 0,
+ itemsAfter = 0,
+ ),
+ ),
+ anchorPosition = null,
+ config = config,
+ leadingPlaceholderCount = 0,
+ ),
+ )
+ )
+
+ state.job.cancel()
+ }
+
+ @Test
fun remoteMediator_remoteRefreshEndOfPaginationReached() = testScope.runBlockingTest {
@OptIn(ExperimentalPagingApi::class)
val remoteMediator = RemoteMediatorMock().apply {
@@ -2963,10 +3069,12 @@
jumpThreshold = 10
)
var didJump = false
- val pager = PageFetcherSnapshot(
+ val pager = PageFetcherSnapshot<Int, Int>(
initialKey = 50,
pagingSource = pagingSourceFactory(),
- config = config, retryFlow = retryBus.flow
+ config = config,
+ retryFlow = retryBus.flow,
+ previousPagingState = null,
) {
didJump = true
}
diff --git a/paging/common/src/test/kotlin/androidx/paging/PageFetcherTest.kt b/paging/common/src/test/kotlin/androidx/paging/PageFetcherTest.kt
index bad2b72..b095750 100644
--- a/paging/common/src/test/kotlin/androidx/paging/PageFetcherTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/PageFetcherTest.kt
@@ -504,7 +504,6 @@
}
}
- @ExperimentalStdlibApi
@Test
fun pagingSourceInvalidBeforeCallbackAdded() = testScope.runBlockingTest {
var invalidatesFromAdapter = 0
@@ -523,11 +522,11 @@
}
}
+ @OptIn(ExperimentalStdlibApi::class)
val job = launch {
pager.flow.collectLatest { pagingData ->
- TestPagingDataDiffer<Int>(
- testScope.coroutineContext[CoroutineDispatcher.Key]!!
- ).collectFrom(pagingData)
+ TestPagingDataDiffer<Int>(testScope.coroutineContext[CoroutineDispatcher]!!)
+ .collectFrom(pagingData)
}
}
@@ -539,6 +538,208 @@
job.cancel()
}
+ @OptIn(ExperimentalPagingApi::class)
+ @Test
+ fun cachesPreviousPagingStateOnEmptyPages() = testScope.runBlockingTest {
+ val config = PagingConfig(
+ pageSize = 1,
+ prefetchDistance = 1,
+ enablePlaceholders = true,
+ initialLoadSize = 3,
+ )
+
+ val remoteMediator = RemoteMediatorMock().apply {
+ initializeResult = LAUNCH_INITIAL_REFRESH
+ }
+ val pageFetcher = PageFetcher(
+ pagingSourceFactory = suspend {
+ TestPagingSource(loadDelay = 1000)
+ },
+ initialKey = 50,
+ config = config,
+ remoteMediator = remoteMediator,
+ )
+
+ var receiver: UiReceiver? = null
+ val job = launch() {
+ pageFetcher.flow.collectLatest {
+ receiver = it.receiver
+ it.flow.collect { }
+ }
+ }
+
+ // Allow initial load to finish, so PagingState has non-zero pages.
+ advanceUntilIdle()
+
+ // Verify remote refresh is called with initial empty case.
+ assertThat(remoteMediator.newLoadEvents).containsExactly(
+ RemoteMediatorMock.LoadEvent(
+ loadType = REFRESH,
+ state = PagingState(
+ pages = listOf(),
+ anchorPosition = null,
+ config = config,
+ leadingPlaceholderCount = 0,
+ ),
+ )
+ )
+
+ // Trigger refresh, instantiating second generation.
+ pageFetcher.refresh()
+
+ // Allow remote refresh to get triggered, but do not let paging source complete initial load
+ // for second generation.
+ advanceTimeBy(500)
+
+ // Verify remote refresh is called with PagingState from first generation.
+ val pagingState = PagingState(
+ pages = listOf(
+ PagingSource.LoadResult.Page(
+ data = listOf(50, 51, 52),
+ prevKey = 49,
+ nextKey = 53,
+ itemsBefore = 50,
+ itemsAfter = 47,
+ )
+ ),
+ anchorPosition = null,
+ config = config,
+ leadingPlaceholderCount = 50,
+ )
+ assertThat(remoteMediator.newLoadEvents).containsExactly(
+ RemoteMediatorMock.LoadEvent(loadType = REFRESH, state = pagingState)
+ )
+
+ // Trigger a hint, which would normally populate anchorPosition. In real world scenario,
+ // this would happen as a result of UI still presenting first generation since second
+ // generation never finished loading yet.
+ receiver?.accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 0,
+ presentedItemsBefore = 0,
+ presentedItemsAfter = 2,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0,
+ )
+ )
+
+ // Trigger refresh instantiating third generation before second has a chance to complete
+ // initial load.
+ pageFetcher.refresh()
+
+ // Wait for all non-canceled loads to complete.
+ advanceUntilIdle()
+
+ // Verify remote refresh is called with PagingState from first generation, since second
+ // generation never loaded any pages.
+ assertThat(remoteMediator.newLoadEvents).containsExactly(
+ RemoteMediatorMock.LoadEvent(loadType = REFRESH, state = pagingState)
+ )
+
+ job.cancel()
+ }
+
+ @OptIn(ExperimentalPagingApi::class)
+ @Test
+ fun cachesPreviousPagingStateOnNullHint() = testScope.runBlockingTest {
+ val config = PagingConfig(
+ pageSize = 1,
+ prefetchDistance = 1,
+ enablePlaceholders = true,
+ initialLoadSize = 3,
+ )
+
+ val remoteMediator = RemoteMediatorMock().apply {
+ initializeResult = LAUNCH_INITIAL_REFRESH
+ }
+ val pageFetcher = PageFetcher(
+ pagingSourceFactory = suspend {
+ TestPagingSource(loadDelay = 1000)
+ },
+ initialKey = 50,
+ config = config,
+ remoteMediator = remoteMediator,
+ )
+
+ var receiver: UiReceiver? = null
+ val job = launch() {
+ pageFetcher.flow.collectLatest {
+ receiver = it.receiver
+ it.flow.collect { }
+ }
+ }
+
+ // Allow initial load to finish, so PagingState has non-zero pages.
+ advanceUntilIdle()
+
+ // Trigger a hint to populate anchorPosition, this should cause PageFetcher to cache this
+ // PagingState and use it in next remoteRefresh
+ receiver?.accessHint(
+ ViewportHint.Access(
+ pageOffset = 0,
+ indexInPage = 0,
+ presentedItemsBefore = 0,
+ presentedItemsAfter = 2,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0,
+ )
+ )
+
+ // Verify remote refresh is called with initial empty case.
+ assertThat(remoteMediator.newLoadEvents).containsExactly(
+ RemoteMediatorMock.LoadEvent(
+ loadType = REFRESH,
+ state = PagingState(
+ pages = listOf(),
+ anchorPosition = null,
+ config = config,
+ leadingPlaceholderCount = 0,
+ ),
+ )
+ )
+
+ // Trigger refresh, instantiating second generation.
+ pageFetcher.refresh()
+
+ // Allow remote refresh to get triggered, and let paging source load finish.
+ advanceUntilIdle()
+
+ // Verify remote refresh is called with PagingState from first generation.
+ val pagingState = PagingState(
+ pages = listOf(
+ PagingSource.LoadResult.Page(
+ data = listOf(50, 51, 52),
+ prevKey = 49,
+ nextKey = 53,
+ itemsBefore = 50,
+ itemsAfter = 47,
+ )
+ ),
+ anchorPosition = 50,
+ config = config,
+ leadingPlaceholderCount = 50,
+ )
+ assertThat(remoteMediator.newLoadEvents).containsExactly(
+ RemoteMediatorMock.LoadEvent(loadType = REFRESH, state = pagingState)
+ )
+
+ // Trigger refresh instantiating third generation before second has a chance to complete
+ // initial load.
+ pageFetcher.refresh()
+
+ // Wait for all non-canceled loads to complete.
+ advanceUntilIdle()
+
+ // Verify remote refresh is called with PagingState from first generation, since second
+ // generation never loaded any pages.
+ assertThat(remoteMediator.newLoadEvents).containsExactly(
+ RemoteMediatorMock.LoadEvent(loadType = REFRESH, state = pagingState)
+ )
+
+ job.cancel()
+ }
+
@Test
fun invalidateBeforeAccessPreservesPagingState() = testScope.runBlockingTest {
pauseDispatcher {
diff --git a/room/common/api/2.3.0-beta03.txt b/room/common/api/2.3.0-beta03.txt
new file mode 100644
index 0000000..ab3d354
--- /dev/null
+++ b/room/common/api/2.3.0-beta03.txt
@@ -0,0 +1,206 @@
+// Signature format: 4.0
+package androidx.room {
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public @interface ColumnInfo {
+ method @androidx.room.ColumnInfo.Collate public abstract int collate() default androidx.room.ColumnInfo.UNSPECIFIED;
+ method public abstract String defaultValue() default androidx.room.ColumnInfo.VALUE_UNSPECIFIED;
+ method public abstract boolean index() default false;
+ method public abstract String name() default androidx.room.ColumnInfo.INHERIT_FIELD_NAME;
+ method @androidx.room.ColumnInfo.SQLiteTypeAffinity public abstract int typeAffinity() default androidx.room.ColumnInfo.UNDEFINED;
+ field public static final int BINARY = 2; // 0x2
+ field public static final int BLOB = 5; // 0x5
+ field public static final String INHERIT_FIELD_NAME = "[field-name]";
+ field public static final int INTEGER = 3; // 0x3
+ field @RequiresApi(21) public static final int LOCALIZED = 5; // 0x5
+ field public static final int NOCASE = 3; // 0x3
+ field public static final int REAL = 4; // 0x4
+ field public static final int RTRIM = 4; // 0x4
+ field public static final int TEXT = 2; // 0x2
+ field public static final int UNDEFINED = 1; // 0x1
+ field @RequiresApi(21) public static final int UNICODE = 6; // 0x6
+ field public static final int UNSPECIFIED = 1; // 0x1
+ field public static final String VALUE_UNSPECIFIED = "[value-unspecified]";
+ }
+
+ @IntDef({androidx.room.ColumnInfo.UNSPECIFIED, androidx.room.ColumnInfo.BINARY, androidx.room.ColumnInfo.NOCASE, androidx.room.ColumnInfo.RTRIM, androidx.room.ColumnInfo.LOCALIZED, androidx.room.ColumnInfo.UNICODE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface ColumnInfo.Collate {
+ }
+
+ @IntDef({androidx.room.ColumnInfo.UNDEFINED, androidx.room.ColumnInfo.TEXT, androidx.room.ColumnInfo.INTEGER, androidx.room.ColumnInfo.REAL, androidx.room.ColumnInfo.BLOB}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface ColumnInfo.SQLiteTypeAffinity {
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Dao {
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Database {
+ method public abstract Class<?>[] entities();
+ method public abstract boolean exportSchema() default true;
+ method public abstract int version();
+ method public abstract Class<?>[] views() default {};
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface DatabaseView {
+ method public abstract String value() default "";
+ method public abstract String viewName() default "";
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD) public @interface Delete {
+ method public abstract Class<?> entity() default java.lang.Object.class;
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public @interface Embedded {
+ method public abstract String prefix() default "";
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Entity {
+ method public abstract androidx.room.ForeignKey[] foreignKeys() default {};
+ method public abstract String[] ignoredColumns() default {};
+ method public abstract androidx.room.Index[] indices() default {};
+ method public abstract boolean inheritSuperIndices() default false;
+ method public abstract String[] primaryKeys() default {};
+ method public abstract String tableName() default "";
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({}) public @interface ForeignKey {
+ method public abstract String[] childColumns();
+ method public abstract boolean deferred() default false;
+ method public abstract Class<?> entity();
+ method @androidx.room.ForeignKey.Action public abstract int onDelete() default androidx.room.ForeignKey.NO_ACTION;
+ method @androidx.room.ForeignKey.Action public abstract int onUpdate() default androidx.room.ForeignKey.NO_ACTION;
+ method public abstract String[] parentColumns();
+ field public static final int CASCADE = 5; // 0x5
+ field public static final int NO_ACTION = 1; // 0x1
+ field public static final int RESTRICT = 2; // 0x2
+ field public static final int SET_DEFAULT = 4; // 0x4
+ field public static final int SET_NULL = 3; // 0x3
+ }
+
+ @IntDef({androidx.room.ForeignKey.NO_ACTION, androidx.room.ForeignKey.RESTRICT, androidx.room.ForeignKey.SET_NULL, androidx.room.ForeignKey.SET_DEFAULT, androidx.room.ForeignKey.CASCADE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface ForeignKey.Action {
+ }
+
+ @RequiresApi(16) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Fts3 {
+ method public abstract String tokenizer() default androidx.room.FtsOptions.TOKENIZER_SIMPLE;
+ method public abstract String[] tokenizerArgs() default {};
+ }
+
+ @RequiresApi(16) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Fts4 {
+ method public abstract Class<?> contentEntity() default java.lang.Object.class;
+ method public abstract String languageId() default "";
+ method public abstract androidx.room.FtsOptions.MatchInfo matchInfo() default androidx.room.FtsOptions.MatchInfo.FTS4;
+ method public abstract String[] notIndexed() default {};
+ method public abstract androidx.room.FtsOptions.Order order() default androidx.room.FtsOptions.Order.ASC;
+ method public abstract int[] prefix() default {};
+ method public abstract String tokenizer() default androidx.room.FtsOptions.TOKENIZER_SIMPLE;
+ method public abstract String[] tokenizerArgs() default {};
+ }
+
+ public class FtsOptions {
+ field public static final String TOKENIZER_ICU = "icu";
+ field public static final String TOKENIZER_PORTER = "porter";
+ field public static final String TOKENIZER_SIMPLE = "simple";
+ field @RequiresApi(21) public static final String TOKENIZER_UNICODE61 = "unicode61";
+ }
+
+ public enum FtsOptions.MatchInfo {
+ enum_constant public static final androidx.room.FtsOptions.MatchInfo FTS3;
+ enum_constant public static final androidx.room.FtsOptions.MatchInfo FTS4;
+ }
+
+ public enum FtsOptions.Order {
+ enum_constant public static final androidx.room.FtsOptions.Order ASC;
+ enum_constant public static final androidx.room.FtsOptions.Order DESC;
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.CONSTRUCTOR}) public @interface Ignore {
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({}) public @interface Index {
+ method public abstract String name() default "";
+ method public abstract boolean unique() default false;
+ method public abstract String[] value();
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface Insert {
+ method public abstract Class<?> entity() default java.lang.Object.class;
+ method @androidx.room.OnConflictStrategy public abstract int onConflict() default androidx.room.OnConflictStrategy.ABORT;
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({}) public @interface Junction {
+ method public abstract String entityColumn() default "";
+ method public abstract String parentColumn() default "";
+ method public abstract Class<?> value();
+ }
+
+ @IntDef({androidx.room.OnConflictStrategy.REPLACE, androidx.room.OnConflictStrategy.ROLLBACK, androidx.room.OnConflictStrategy.ABORT, androidx.room.OnConflictStrategy.FAIL, androidx.room.OnConflictStrategy.IGNORE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface OnConflictStrategy {
+ field public static final int ABORT = 3; // 0x3
+ field @Deprecated public static final int FAIL = 4; // 0x4
+ field public static final int IGNORE = 5; // 0x5
+ field public static final int REPLACE = 1; // 0x1
+ field @Deprecated public static final int ROLLBACK = 2; // 0x2
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public @interface PrimaryKey {
+ method public abstract boolean autoGenerate() default false;
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface ProvidedTypeConverter {
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD) public @interface Query {
+ method public abstract String value();
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD) public @interface RawQuery {
+ method public abstract Class<?>[] observedEntities() default {};
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public @interface Relation {
+ method public abstract androidx.room.Junction associateBy() default @androidx.room.Junction;
+ method public abstract Class<?> entity() default java.lang.Object.class;
+ method public abstract String entityColumn();
+ method public abstract String parentColumn();
+ method public abstract String[] projection() default {};
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.TYPE}) public @interface RewriteQueriesToDropUnusedColumns {
+ }
+
+ public class RoomWarnings {
+ ctor @Deprecated public RoomWarnings();
+ field public static final String CANNOT_CREATE_VERIFICATION_DATABASE = "ROOM_CANNOT_CREATE_VERIFICATION_DATABASE";
+ field public static final String CURSOR_MISMATCH = "ROOM_CURSOR_MISMATCH";
+ field public static final String DEFAULT_CONSTRUCTOR = "ROOM_DEFAULT_CONSTRUCTOR";
+ field public static final String INDEX_FROM_EMBEDDED_ENTITY_IS_DROPPED = "ROOM_EMBEDDED_ENTITY_INDEX_IS_DROPPED";
+ field public static final String INDEX_FROM_EMBEDDED_FIELD_IS_DROPPED = "ROOM_EMBEDDED_INDEX_IS_DROPPED";
+ field public static final String INDEX_FROM_PARENT_FIELD_IS_DROPPED = "ROOM_PARENT_FIELD_INDEX_IS_DROPPED";
+ field public static final String INDEX_FROM_PARENT_IS_DROPPED = "ROOM_PARENT_INDEX_IS_DROPPED";
+ field public static final String MISMATCHED_GETTER = "ROOM_MISMATCHED_GETTER_TYPE";
+ field public static final String MISMATCHED_SETTER = "ROOM_MISMATCHED_SETTER_TYPE";
+ field public static final String MISSING_INDEX_ON_FOREIGN_KEY_CHILD = "ROOM_MISSING_FOREIGN_KEY_CHILD_INDEX";
+ field public static final String MISSING_INDEX_ON_JUNCTION = "MISSING_INDEX_ON_JUNCTION";
+ field public static final String MISSING_JAVA_TMP_DIR = "ROOM_MISSING_JAVA_TMP_DIR";
+ field public static final String MISSING_SCHEMA_LOCATION = "ROOM_MISSING_SCHEMA_LOCATION";
+ field public static final String PRIMARY_KEY_FROM_EMBEDDED_IS_DROPPED = "ROOM_EMBEDDED_PRIMARY_KEY_IS_DROPPED";
+ field public static final String RELATION_QUERY_WITHOUT_TRANSACTION = "ROOM_RELATION_QUERY_WITHOUT_TRANSACTION";
+ field public static final String RELATION_TYPE_MISMATCH = "ROOM_RELATION_TYPE_MISMATCH";
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.TYPE}) public @interface SkipQueryVerification {
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface Transaction {
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface TypeConverter {
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.FIELD}) public @interface TypeConverters {
+ method public abstract Class<?>[] value();
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface Update {
+ method public abstract Class<?> entity() default java.lang.Object.class;
+ method @androidx.room.OnConflictStrategy public abstract int onConflict() default androidx.room.OnConflictStrategy.ABORT;
+ }
+
+}
+
diff --git a/room/common/api/public_plus_experimental_2.3.0-beta03.txt b/room/common/api/public_plus_experimental_2.3.0-beta03.txt
new file mode 100644
index 0000000..ab3d354
--- /dev/null
+++ b/room/common/api/public_plus_experimental_2.3.0-beta03.txt
@@ -0,0 +1,206 @@
+// Signature format: 4.0
+package androidx.room {
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public @interface ColumnInfo {
+ method @androidx.room.ColumnInfo.Collate public abstract int collate() default androidx.room.ColumnInfo.UNSPECIFIED;
+ method public abstract String defaultValue() default androidx.room.ColumnInfo.VALUE_UNSPECIFIED;
+ method public abstract boolean index() default false;
+ method public abstract String name() default androidx.room.ColumnInfo.INHERIT_FIELD_NAME;
+ method @androidx.room.ColumnInfo.SQLiteTypeAffinity public abstract int typeAffinity() default androidx.room.ColumnInfo.UNDEFINED;
+ field public static final int BINARY = 2; // 0x2
+ field public static final int BLOB = 5; // 0x5
+ field public static final String INHERIT_FIELD_NAME = "[field-name]";
+ field public static final int INTEGER = 3; // 0x3
+ field @RequiresApi(21) public static final int LOCALIZED = 5; // 0x5
+ field public static final int NOCASE = 3; // 0x3
+ field public static final int REAL = 4; // 0x4
+ field public static final int RTRIM = 4; // 0x4
+ field public static final int TEXT = 2; // 0x2
+ field public static final int UNDEFINED = 1; // 0x1
+ field @RequiresApi(21) public static final int UNICODE = 6; // 0x6
+ field public static final int UNSPECIFIED = 1; // 0x1
+ field public static final String VALUE_UNSPECIFIED = "[value-unspecified]";
+ }
+
+ @IntDef({androidx.room.ColumnInfo.UNSPECIFIED, androidx.room.ColumnInfo.BINARY, androidx.room.ColumnInfo.NOCASE, androidx.room.ColumnInfo.RTRIM, androidx.room.ColumnInfo.LOCALIZED, androidx.room.ColumnInfo.UNICODE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface ColumnInfo.Collate {
+ }
+
+ @IntDef({androidx.room.ColumnInfo.UNDEFINED, androidx.room.ColumnInfo.TEXT, androidx.room.ColumnInfo.INTEGER, androidx.room.ColumnInfo.REAL, androidx.room.ColumnInfo.BLOB}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface ColumnInfo.SQLiteTypeAffinity {
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Dao {
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Database {
+ method public abstract Class<?>[] entities();
+ method public abstract boolean exportSchema() default true;
+ method public abstract int version();
+ method public abstract Class<?>[] views() default {};
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface DatabaseView {
+ method public abstract String value() default "";
+ method public abstract String viewName() default "";
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD) public @interface Delete {
+ method public abstract Class<?> entity() default java.lang.Object.class;
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public @interface Embedded {
+ method public abstract String prefix() default "";
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Entity {
+ method public abstract androidx.room.ForeignKey[] foreignKeys() default {};
+ method public abstract String[] ignoredColumns() default {};
+ method public abstract androidx.room.Index[] indices() default {};
+ method public abstract boolean inheritSuperIndices() default false;
+ method public abstract String[] primaryKeys() default {};
+ method public abstract String tableName() default "";
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({}) public @interface ForeignKey {
+ method public abstract String[] childColumns();
+ method public abstract boolean deferred() default false;
+ method public abstract Class<?> entity();
+ method @androidx.room.ForeignKey.Action public abstract int onDelete() default androidx.room.ForeignKey.NO_ACTION;
+ method @androidx.room.ForeignKey.Action public abstract int onUpdate() default androidx.room.ForeignKey.NO_ACTION;
+ method public abstract String[] parentColumns();
+ field public static final int CASCADE = 5; // 0x5
+ field public static final int NO_ACTION = 1; // 0x1
+ field public static final int RESTRICT = 2; // 0x2
+ field public static final int SET_DEFAULT = 4; // 0x4
+ field public static final int SET_NULL = 3; // 0x3
+ }
+
+ @IntDef({androidx.room.ForeignKey.NO_ACTION, androidx.room.ForeignKey.RESTRICT, androidx.room.ForeignKey.SET_NULL, androidx.room.ForeignKey.SET_DEFAULT, androidx.room.ForeignKey.CASCADE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface ForeignKey.Action {
+ }
+
+ @RequiresApi(16) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Fts3 {
+ method public abstract String tokenizer() default androidx.room.FtsOptions.TOKENIZER_SIMPLE;
+ method public abstract String[] tokenizerArgs() default {};
+ }
+
+ @RequiresApi(16) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Fts4 {
+ method public abstract Class<?> contentEntity() default java.lang.Object.class;
+ method public abstract String languageId() default "";
+ method public abstract androidx.room.FtsOptions.MatchInfo matchInfo() default androidx.room.FtsOptions.MatchInfo.FTS4;
+ method public abstract String[] notIndexed() default {};
+ method public abstract androidx.room.FtsOptions.Order order() default androidx.room.FtsOptions.Order.ASC;
+ method public abstract int[] prefix() default {};
+ method public abstract String tokenizer() default androidx.room.FtsOptions.TOKENIZER_SIMPLE;
+ method public abstract String[] tokenizerArgs() default {};
+ }
+
+ public class FtsOptions {
+ field public static final String TOKENIZER_ICU = "icu";
+ field public static final String TOKENIZER_PORTER = "porter";
+ field public static final String TOKENIZER_SIMPLE = "simple";
+ field @RequiresApi(21) public static final String TOKENIZER_UNICODE61 = "unicode61";
+ }
+
+ public enum FtsOptions.MatchInfo {
+ enum_constant public static final androidx.room.FtsOptions.MatchInfo FTS3;
+ enum_constant public static final androidx.room.FtsOptions.MatchInfo FTS4;
+ }
+
+ public enum FtsOptions.Order {
+ enum_constant public static final androidx.room.FtsOptions.Order ASC;
+ enum_constant public static final androidx.room.FtsOptions.Order DESC;
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.CONSTRUCTOR}) public @interface Ignore {
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({}) public @interface Index {
+ method public abstract String name() default "";
+ method public abstract boolean unique() default false;
+ method public abstract String[] value();
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface Insert {
+ method public abstract Class<?> entity() default java.lang.Object.class;
+ method @androidx.room.OnConflictStrategy public abstract int onConflict() default androidx.room.OnConflictStrategy.ABORT;
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({}) public @interface Junction {
+ method public abstract String entityColumn() default "";
+ method public abstract String parentColumn() default "";
+ method public abstract Class<?> value();
+ }
+
+ @IntDef({androidx.room.OnConflictStrategy.REPLACE, androidx.room.OnConflictStrategy.ROLLBACK, androidx.room.OnConflictStrategy.ABORT, androidx.room.OnConflictStrategy.FAIL, androidx.room.OnConflictStrategy.IGNORE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface OnConflictStrategy {
+ field public static final int ABORT = 3; // 0x3
+ field @Deprecated public static final int FAIL = 4; // 0x4
+ field public static final int IGNORE = 5; // 0x5
+ field public static final int REPLACE = 1; // 0x1
+ field @Deprecated public static final int ROLLBACK = 2; // 0x2
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public @interface PrimaryKey {
+ method public abstract boolean autoGenerate() default false;
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface ProvidedTypeConverter {
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD) public @interface Query {
+ method public abstract String value();
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD) public @interface RawQuery {
+ method public abstract Class<?>[] observedEntities() default {};
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public @interface Relation {
+ method public abstract androidx.room.Junction associateBy() default @androidx.room.Junction;
+ method public abstract Class<?> entity() default java.lang.Object.class;
+ method public abstract String entityColumn();
+ method public abstract String parentColumn();
+ method public abstract String[] projection() default {};
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.TYPE}) public @interface RewriteQueriesToDropUnusedColumns {
+ }
+
+ public class RoomWarnings {
+ ctor @Deprecated public RoomWarnings();
+ field public static final String CANNOT_CREATE_VERIFICATION_DATABASE = "ROOM_CANNOT_CREATE_VERIFICATION_DATABASE";
+ field public static final String CURSOR_MISMATCH = "ROOM_CURSOR_MISMATCH";
+ field public static final String DEFAULT_CONSTRUCTOR = "ROOM_DEFAULT_CONSTRUCTOR";
+ field public static final String INDEX_FROM_EMBEDDED_ENTITY_IS_DROPPED = "ROOM_EMBEDDED_ENTITY_INDEX_IS_DROPPED";
+ field public static final String INDEX_FROM_EMBEDDED_FIELD_IS_DROPPED = "ROOM_EMBEDDED_INDEX_IS_DROPPED";
+ field public static final String INDEX_FROM_PARENT_FIELD_IS_DROPPED = "ROOM_PARENT_FIELD_INDEX_IS_DROPPED";
+ field public static final String INDEX_FROM_PARENT_IS_DROPPED = "ROOM_PARENT_INDEX_IS_DROPPED";
+ field public static final String MISMATCHED_GETTER = "ROOM_MISMATCHED_GETTER_TYPE";
+ field public static final String MISMATCHED_SETTER = "ROOM_MISMATCHED_SETTER_TYPE";
+ field public static final String MISSING_INDEX_ON_FOREIGN_KEY_CHILD = "ROOM_MISSING_FOREIGN_KEY_CHILD_INDEX";
+ field public static final String MISSING_INDEX_ON_JUNCTION = "MISSING_INDEX_ON_JUNCTION";
+ field public static final String MISSING_JAVA_TMP_DIR = "ROOM_MISSING_JAVA_TMP_DIR";
+ field public static final String MISSING_SCHEMA_LOCATION = "ROOM_MISSING_SCHEMA_LOCATION";
+ field public static final String PRIMARY_KEY_FROM_EMBEDDED_IS_DROPPED = "ROOM_EMBEDDED_PRIMARY_KEY_IS_DROPPED";
+ field public static final String RELATION_QUERY_WITHOUT_TRANSACTION = "ROOM_RELATION_QUERY_WITHOUT_TRANSACTION";
+ field public static final String RELATION_TYPE_MISMATCH = "ROOM_RELATION_TYPE_MISMATCH";
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.TYPE}) public @interface SkipQueryVerification {
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface Transaction {
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface TypeConverter {
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.FIELD}) public @interface TypeConverters {
+ method public abstract Class<?>[] value();
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface Update {
+ method public abstract Class<?> entity() default java.lang.Object.class;
+ method @androidx.room.OnConflictStrategy public abstract int onConflict() default androidx.room.OnConflictStrategy.ABORT;
+ }
+
+}
+
diff --git a/room/common/api/restricted_2.3.0-beta03.txt b/room/common/api/restricted_2.3.0-beta03.txt
new file mode 100644
index 0000000..c88fdd3
--- /dev/null
+++ b/room/common/api/restricted_2.3.0-beta03.txt
@@ -0,0 +1,215 @@
+// Signature format: 4.0
+package androidx.room {
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public @interface ColumnInfo {
+ method @androidx.room.ColumnInfo.Collate public abstract int collate() default androidx.room.ColumnInfo.UNSPECIFIED;
+ method public abstract String defaultValue() default androidx.room.ColumnInfo.VALUE_UNSPECIFIED;
+ method public abstract boolean index() default false;
+ method public abstract String name() default androidx.room.ColumnInfo.INHERIT_FIELD_NAME;
+ method @androidx.room.ColumnInfo.SQLiteTypeAffinity public abstract int typeAffinity() default androidx.room.ColumnInfo.UNDEFINED;
+ field public static final int BINARY = 2; // 0x2
+ field public static final int BLOB = 5; // 0x5
+ field public static final String INHERIT_FIELD_NAME = "[field-name]";
+ field public static final int INTEGER = 3; // 0x3
+ field @RequiresApi(21) public static final int LOCALIZED = 5; // 0x5
+ field public static final int NOCASE = 3; // 0x3
+ field public static final int REAL = 4; // 0x4
+ field public static final int RTRIM = 4; // 0x4
+ field public static final int TEXT = 2; // 0x2
+ field public static final int UNDEFINED = 1; // 0x1
+ field @RequiresApi(21) public static final int UNICODE = 6; // 0x6
+ field public static final int UNSPECIFIED = 1; // 0x1
+ field public static final String VALUE_UNSPECIFIED = "[value-unspecified]";
+ }
+
+ @IntDef({androidx.room.ColumnInfo.UNSPECIFIED, androidx.room.ColumnInfo.BINARY, androidx.room.ColumnInfo.NOCASE, androidx.room.ColumnInfo.RTRIM, androidx.room.ColumnInfo.LOCALIZED, androidx.room.ColumnInfo.UNICODE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface ColumnInfo.Collate {
+ }
+
+ @IntDef({androidx.room.ColumnInfo.UNDEFINED, androidx.room.ColumnInfo.TEXT, androidx.room.ColumnInfo.INTEGER, androidx.room.ColumnInfo.REAL, androidx.room.ColumnInfo.BLOB}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface ColumnInfo.SQLiteTypeAffinity {
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Dao {
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Database {
+ method public abstract Class<?>[] entities();
+ method public abstract boolean exportSchema() default true;
+ method public abstract int version();
+ method public abstract Class<?>[] views() default {};
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface DatabaseView {
+ method public abstract String value() default "";
+ method public abstract String viewName() default "";
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD) public @interface Delete {
+ method public abstract Class<?> entity() default java.lang.Object.class;
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public @interface Embedded {
+ method public abstract String prefix() default "";
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Entity {
+ method public abstract androidx.room.ForeignKey[] foreignKeys() default {};
+ method public abstract String[] ignoredColumns() default {};
+ method public abstract androidx.room.Index[] indices() default {};
+ method public abstract boolean inheritSuperIndices() default false;
+ method public abstract String[] primaryKeys() default {};
+ method public abstract String tableName() default "";
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({}) public @interface ForeignKey {
+ method public abstract String[] childColumns();
+ method public abstract boolean deferred() default false;
+ method public abstract Class<?> entity();
+ method @androidx.room.ForeignKey.Action public abstract int onDelete() default androidx.room.ForeignKey.NO_ACTION;
+ method @androidx.room.ForeignKey.Action public abstract int onUpdate() default androidx.room.ForeignKey.NO_ACTION;
+ method public abstract String[] parentColumns();
+ field public static final int CASCADE = 5; // 0x5
+ field public static final int NO_ACTION = 1; // 0x1
+ field public static final int RESTRICT = 2; // 0x2
+ field public static final int SET_DEFAULT = 4; // 0x4
+ field public static final int SET_NULL = 3; // 0x3
+ }
+
+ @IntDef({androidx.room.ForeignKey.NO_ACTION, androidx.room.ForeignKey.RESTRICT, androidx.room.ForeignKey.SET_NULL, androidx.room.ForeignKey.SET_DEFAULT, androidx.room.ForeignKey.CASCADE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface ForeignKey.Action {
+ }
+
+ @RequiresApi(16) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Fts3 {
+ method public abstract String tokenizer() default androidx.room.FtsOptions.TOKENIZER_SIMPLE;
+ method public abstract String[] tokenizerArgs() default {};
+ }
+
+ @RequiresApi(16) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Fts4 {
+ method public abstract Class<?> contentEntity() default java.lang.Object.class;
+ method public abstract String languageId() default "";
+ method public abstract androidx.room.FtsOptions.MatchInfo matchInfo() default androidx.room.FtsOptions.MatchInfo.FTS4;
+ method public abstract String[] notIndexed() default {};
+ method public abstract androidx.room.FtsOptions.Order order() default androidx.room.FtsOptions.Order.ASC;
+ method public abstract int[] prefix() default {};
+ method public abstract String tokenizer() default androidx.room.FtsOptions.TOKENIZER_SIMPLE;
+ method public abstract String[] tokenizerArgs() default {};
+ }
+
+ public class FtsOptions {
+ field public static final String TOKENIZER_ICU = "icu";
+ field public static final String TOKENIZER_PORTER = "porter";
+ field public static final String TOKENIZER_SIMPLE = "simple";
+ field @RequiresApi(21) public static final String TOKENIZER_UNICODE61 = "unicode61";
+ }
+
+ public enum FtsOptions.MatchInfo {
+ enum_constant public static final androidx.room.FtsOptions.MatchInfo FTS3;
+ enum_constant public static final androidx.room.FtsOptions.MatchInfo FTS4;
+ }
+
+ public enum FtsOptions.Order {
+ enum_constant public static final androidx.room.FtsOptions.Order ASC;
+ enum_constant public static final androidx.room.FtsOptions.Order DESC;
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.CONSTRUCTOR}) public @interface Ignore {
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({}) public @interface Index {
+ method public abstract String name() default "";
+ method public abstract boolean unique() default false;
+ method public abstract String[] value();
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface Insert {
+ method public abstract Class<?> entity() default java.lang.Object.class;
+ method @androidx.room.OnConflictStrategy public abstract int onConflict() default androidx.room.OnConflictStrategy.ABORT;
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({}) public @interface Junction {
+ method public abstract String entityColumn() default "";
+ method public abstract String parentColumn() default "";
+ method public abstract Class<?> value();
+ }
+
+ @IntDef({androidx.room.OnConflictStrategy.REPLACE, androidx.room.OnConflictStrategy.ROLLBACK, androidx.room.OnConflictStrategy.ABORT, androidx.room.OnConflictStrategy.FAIL, androidx.room.OnConflictStrategy.IGNORE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface OnConflictStrategy {
+ field public static final int ABORT = 3; // 0x3
+ field @Deprecated public static final int FAIL = 4; // 0x4
+ field public static final int IGNORE = 5; // 0x5
+ field public static final int REPLACE = 1; // 0x1
+ field @Deprecated public static final int ROLLBACK = 2; // 0x2
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public @interface PrimaryKey {
+ method public abstract boolean autoGenerate() default false;
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface ProvidedTypeConverter {
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD) public @interface Query {
+ method public abstract String value();
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD) public @interface RawQuery {
+ method public abstract Class<?>[] observedEntities() default {};
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public @interface Relation {
+ method public abstract androidx.room.Junction associateBy() default @androidx.room.Junction;
+ method public abstract Class<?> entity() default java.lang.Object.class;
+ method public abstract String entityColumn();
+ method public abstract String parentColumn();
+ method public abstract String[] projection() default {};
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.TYPE}) public @interface RewriteQueriesToDropUnusedColumns {
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class RoomMasterTable {
+ method public static String! createInsertQuery(String!);
+ field public static final String CREATE_QUERY = "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)";
+ field public static final String DEFAULT_ID = "42";
+ field public static final String NAME = "room_master_table";
+ field public static final String READ_QUERY = "SELECT identity_hash FROM room_master_table WHERE id = 42 LIMIT 1";
+ field public static final String TABLE_NAME = "room_master_table";
+ }
+
+ public class RoomWarnings {
+ ctor @Deprecated public RoomWarnings();
+ field public static final String CANNOT_CREATE_VERIFICATION_DATABASE = "ROOM_CANNOT_CREATE_VERIFICATION_DATABASE";
+ field public static final String CURSOR_MISMATCH = "ROOM_CURSOR_MISMATCH";
+ field public static final String DEFAULT_CONSTRUCTOR = "ROOM_DEFAULT_CONSTRUCTOR";
+ field public static final String INDEX_FROM_EMBEDDED_ENTITY_IS_DROPPED = "ROOM_EMBEDDED_ENTITY_INDEX_IS_DROPPED";
+ field public static final String INDEX_FROM_EMBEDDED_FIELD_IS_DROPPED = "ROOM_EMBEDDED_INDEX_IS_DROPPED";
+ field public static final String INDEX_FROM_PARENT_FIELD_IS_DROPPED = "ROOM_PARENT_FIELD_INDEX_IS_DROPPED";
+ field public static final String INDEX_FROM_PARENT_IS_DROPPED = "ROOM_PARENT_INDEX_IS_DROPPED";
+ field public static final String MISMATCHED_GETTER = "ROOM_MISMATCHED_GETTER_TYPE";
+ field public static final String MISMATCHED_SETTER = "ROOM_MISMATCHED_SETTER_TYPE";
+ field public static final String MISSING_INDEX_ON_FOREIGN_KEY_CHILD = "ROOM_MISSING_FOREIGN_KEY_CHILD_INDEX";
+ field public static final String MISSING_INDEX_ON_JUNCTION = "MISSING_INDEX_ON_JUNCTION";
+ field public static final String MISSING_JAVA_TMP_DIR = "ROOM_MISSING_JAVA_TMP_DIR";
+ field public static final String MISSING_SCHEMA_LOCATION = "ROOM_MISSING_SCHEMA_LOCATION";
+ field public static final String PRIMARY_KEY_FROM_EMBEDDED_IS_DROPPED = "ROOM_EMBEDDED_PRIMARY_KEY_IS_DROPPED";
+ field public static final String RELATION_QUERY_WITHOUT_TRANSACTION = "ROOM_RELATION_QUERY_WITHOUT_TRANSACTION";
+ field public static final String RELATION_TYPE_MISMATCH = "ROOM_RELATION_TYPE_MISMATCH";
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.TYPE}) public @interface SkipQueryVerification {
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface Transaction {
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface TypeConverter {
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.FIELD}) public @interface TypeConverters {
+ method public abstract Class<?>[] value();
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface Update {
+ method public abstract Class<?> entity() default java.lang.Object.class;
+ method @androidx.room.OnConflictStrategy public abstract int onConflict() default androidx.room.OnConflictStrategy.ABORT;
+ }
+
+}
+
diff --git a/room/guava/api/2.3.0-beta03.txt b/room/guava/api/2.3.0-beta03.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/room/guava/api/2.3.0-beta03.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/room/guava/api/public_plus_experimental_2.3.0-beta03.txt b/room/guava/api/public_plus_experimental_2.3.0-beta03.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/room/guava/api/public_plus_experimental_2.3.0-beta03.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/room/guava/api/res-2.3.0-beta03.txt b/room/guava/api/res-2.3.0-beta03.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/room/guava/api/res-2.3.0-beta03.txt
diff --git a/room/guava/api/restricted_2.3.0-beta03.txt b/room/guava/api/restricted_2.3.0-beta03.txt
new file mode 100644
index 0000000..1c04602
--- /dev/null
+++ b/room/guava/api/restricted_2.3.0-beta03.txt
@@ -0,0 +1,14 @@
+// Signature format: 4.0
+package androidx.room.guava {
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class GuavaRoom {
+ method @Deprecated public static <T> com.google.common.util.concurrent.ListenableFuture<T!>! createListenableFuture(java.util.concurrent.Callable<T!>!, androidx.room.RoomSQLiteQuery!, boolean);
+ method @Deprecated public static <T> com.google.common.util.concurrent.ListenableFuture<T!>! createListenableFuture(androidx.room.RoomDatabase!, java.util.concurrent.Callable<T!>!, androidx.room.RoomSQLiteQuery!, boolean);
+ method public static <T> com.google.common.util.concurrent.ListenableFuture<T!>! createListenableFuture(androidx.room.RoomDatabase!, boolean, java.util.concurrent.Callable<T!>!, androidx.room.RoomSQLiteQuery!, boolean);
+ method public static <T> com.google.common.util.concurrent.ListenableFuture<T!> createListenableFuture(androidx.room.RoomDatabase, boolean, java.util.concurrent.Callable<T!>, androidx.room.RoomSQLiteQuery, boolean, android.os.CancellationSignal?);
+ method @Deprecated public static <T> com.google.common.util.concurrent.ListenableFuture<T!> createListenableFuture(androidx.room.RoomDatabase, java.util.concurrent.Callable<T!>);
+ method public static <T> com.google.common.util.concurrent.ListenableFuture<T!> createListenableFuture(androidx.room.RoomDatabase, boolean, java.util.concurrent.Callable<T!>);
+ }
+
+}
+
diff --git a/room/ktx/api/2.3.0-beta03.txt b/room/ktx/api/2.3.0-beta03.txt
new file mode 100644
index 0000000..83dcd49
--- /dev/null
+++ b/room/ktx/api/2.3.0-beta03.txt
@@ -0,0 +1,12 @@
+// Signature format: 4.0
+package androidx.room {
+
+ public final class CoroutinesRoomKt {
+ }
+
+ public final class RoomDatabaseKt {
+ method public static suspend <R> Object? withTransaction(androidx.room.RoomDatabase, kotlin.jvm.functions.Function1<? super kotlin.coroutines.Continuation<? super R>,?> block, kotlin.coroutines.Continuation<? super R> p);
+ }
+
+}
+
diff --git a/room/ktx/api/public_plus_experimental_2.3.0-beta03.txt b/room/ktx/api/public_plus_experimental_2.3.0-beta03.txt
new file mode 100644
index 0000000..83dcd49
--- /dev/null
+++ b/room/ktx/api/public_plus_experimental_2.3.0-beta03.txt
@@ -0,0 +1,12 @@
+// Signature format: 4.0
+package androidx.room {
+
+ public final class CoroutinesRoomKt {
+ }
+
+ public final class RoomDatabaseKt {
+ method public static suspend <R> Object? withTransaction(androidx.room.RoomDatabase, kotlin.jvm.functions.Function1<? super kotlin.coroutines.Continuation<? super R>,?> block, kotlin.coroutines.Continuation<? super R> p);
+ }
+
+}
+
diff --git a/room/ktx/api/res-2.3.0-beta03.txt b/room/ktx/api/res-2.3.0-beta03.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/room/ktx/api/res-2.3.0-beta03.txt
diff --git a/room/ktx/api/restricted_2.3.0-beta03.txt b/room/ktx/api/restricted_2.3.0-beta03.txt
new file mode 100644
index 0000000..3047be8
--- /dev/null
+++ b/room/ktx/api/restricted_2.3.0-beta03.txt
@@ -0,0 +1,25 @@
+// Signature format: 4.0
+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);
+ method public static suspend <R> Object? execute(androidx.room.RoomDatabase p, boolean db, android.os.CancellationSignal inTransaction, java.util.concurrent.Callable<R> cancellationSignal, 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);
+ method public suspend <R> Object? execute(androidx.room.RoomDatabase db, boolean inTransaction, android.os.CancellationSignal cancellationSignal, java.util.concurrent.Callable<R> callable, kotlin.coroutines.Continuation<? super R> p);
+ }
+
+ public final class CoroutinesRoomKt {
+ }
+
+ public final class RoomDatabaseKt {
+ method public static suspend <R> Object? withTransaction(androidx.room.RoomDatabase, kotlin.jvm.functions.Function1<? super kotlin.coroutines.Continuation<? super R>,?> block, kotlin.coroutines.Continuation<? super R> p);
+ }
+
+}
+
diff --git a/room/migration/api/2.3.0-beta03.txt b/room/migration/api/2.3.0-beta03.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/room/migration/api/2.3.0-beta03.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/room/migration/api/public_plus_experimental_2.3.0-beta03.txt b/room/migration/api/public_plus_experimental_2.3.0-beta03.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/room/migration/api/public_plus_experimental_2.3.0-beta03.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/room/migration/api/restricted_2.3.0-beta03.txt b/room/migration/api/restricted_2.3.0-beta03.txt
new file mode 100644
index 0000000..fe4aad7
--- /dev/null
+++ b/room/migration/api/restricted_2.3.0-beta03.txt
@@ -0,0 +1,107 @@
+// Signature format: 4.0
+package androidx.room.migration.bundle {
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class BundleUtil {
+ field public static final String TABLE_NAME_PLACEHOLDER = "${TABLE_NAME}";
+ field public static final String VIEW_NAME_PLACEHOLDER = "${VIEW_NAME}";
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class DatabaseBundle {
+ ctor public DatabaseBundle(int, String!, java.util.List<androidx.room.migration.bundle.EntityBundle!>!, java.util.List<androidx.room.migration.bundle.DatabaseViewBundle!>!, java.util.List<java.lang.String!>!);
+ ctor public DatabaseBundle();
+ method public java.util.List<java.lang.String!>! buildCreateQueries();
+ method public java.util.List<androidx.room.migration.bundle.EntityBundle!>! getEntities();
+ method public java.util.Map<java.lang.String!,androidx.room.migration.bundle.EntityBundle!>! getEntitiesByTableName();
+ method public String! getIdentityHash();
+ method public int getVersion();
+ method public java.util.List<androidx.room.migration.bundle.DatabaseViewBundle!>! getViews();
+ method public boolean isSchemaEqual(androidx.room.migration.bundle.DatabaseBundle!);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class DatabaseViewBundle {
+ ctor public DatabaseViewBundle(String!, String!);
+ method public String! createView();
+ method public String! getCreateSql();
+ method public String! getViewName();
+ method public boolean isSchemaEqual(androidx.room.migration.bundle.DatabaseViewBundle!);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class EntityBundle {
+ ctor public EntityBundle(String!, String!, java.util.List<androidx.room.migration.bundle.FieldBundle!>!, androidx.room.migration.bundle.PrimaryKeyBundle!, java.util.List<androidx.room.migration.bundle.IndexBundle!>!, java.util.List<androidx.room.migration.bundle.ForeignKeyBundle!>!);
+ method public java.util.Collection<java.lang.String!>! buildCreateQueries();
+ method public String! createNewTable();
+ method public String! createTable();
+ method public String! getCreateSql();
+ method public java.util.List<androidx.room.migration.bundle.FieldBundle!>! getFields();
+ method public java.util.Map<java.lang.String!,androidx.room.migration.bundle.FieldBundle!>! getFieldsByColumnName();
+ method public java.util.List<androidx.room.migration.bundle.ForeignKeyBundle!>! getForeignKeys();
+ method public java.util.List<androidx.room.migration.bundle.IndexBundle!>! getIndices();
+ method public String! getNewTableName();
+ method public androidx.room.migration.bundle.PrimaryKeyBundle! getPrimaryKey();
+ method public String! getTableName();
+ method public boolean isSchemaEqual(androidx.room.migration.bundle.EntityBundle!);
+ method public String renameToOriginal();
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class FieldBundle {
+ ctor @Deprecated public FieldBundle(String!, String!, String!, boolean);
+ ctor public FieldBundle(String!, String!, String!, boolean, String!);
+ method public String! getAffinity();
+ method public String! getColumnName();
+ method public String! getDefaultValue();
+ method public String! getFieldPath();
+ method public boolean isNonNull();
+ method public boolean isSchemaEqual(androidx.room.migration.bundle.FieldBundle!);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class ForeignKeyBundle {
+ ctor public ForeignKeyBundle(String!, String!, String!, java.util.List<java.lang.String!>!, java.util.List<java.lang.String!>!);
+ method public java.util.List<java.lang.String!>! getColumns();
+ method public String! getOnDelete();
+ method public String! getOnUpdate();
+ method public java.util.List<java.lang.String!>! getReferencedColumns();
+ method public String! getTable();
+ method public boolean isSchemaEqual(androidx.room.migration.bundle.ForeignKeyBundle!);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class FtsEntityBundle extends androidx.room.migration.bundle.EntityBundle {
+ ctor public FtsEntityBundle(String!, String!, java.util.List<androidx.room.migration.bundle.FieldBundle!>!, androidx.room.migration.bundle.PrimaryKeyBundle!, String!, androidx.room.migration.bundle.FtsOptionsBundle!, java.util.List<java.lang.String!>!);
+ method public androidx.room.migration.bundle.FtsOptionsBundle! getFtsOptions();
+ method public java.util.List<java.lang.String!>! getShadowTableNames();
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class FtsOptionsBundle {
+ ctor public FtsOptionsBundle(String!, java.util.List<java.lang.String!>!, String!, String!, String!, java.util.List<java.lang.String!>!, java.util.List<java.lang.Integer!>!, String!);
+ method public String! getContentTable();
+ method public boolean isSchemaEqual(androidx.room.migration.bundle.FtsOptionsBundle!);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class IndexBundle {
+ ctor public IndexBundle(String!, boolean, java.util.List<java.lang.String!>!, String!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public String! create(String!);
+ method public java.util.List<java.lang.String!>! getColumnNames();
+ method public String! getName();
+ method public boolean isSchemaEqual(androidx.room.migration.bundle.IndexBundle!);
+ method public boolean isUnique();
+ field public static final String DEFAULT_PREFIX = "index_";
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class PrimaryKeyBundle {
+ ctor public PrimaryKeyBundle(boolean, java.util.List<java.lang.String!>!);
+ method public java.util.List<java.lang.String!>! getColumnNames();
+ method public boolean isAutoGenerate();
+ method public boolean isSchemaEqual(androidx.room.migration.bundle.PrimaryKeyBundle!);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class SchemaBundle {
+ ctor public SchemaBundle(int, androidx.room.migration.bundle.DatabaseBundle!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static androidx.room.migration.bundle.SchemaBundle! deserialize(java.io.InputStream!) throws java.io.UnsupportedEncodingException;
+ method public androidx.room.migration.bundle.DatabaseBundle! getDatabase();
+ method public int getFormatVersion();
+ method public boolean isSchemaEqual(androidx.room.migration.bundle.SchemaBundle!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static void serialize(androidx.room.migration.bundle.SchemaBundle!, java.io.File!) throws java.io.IOException;
+ field public static final int LATEST_FORMAT = 1; // 0x1
+ }
+
+}
+
diff --git a/room/runtime/api/2.3.0-beta03.txt b/room/runtime/api/2.3.0-beta03.txt
new file mode 100644
index 0000000..95f23cc
--- /dev/null
+++ b/room/runtime/api/2.3.0-beta03.txt
@@ -0,0 +1,139 @@
+// Signature format: 4.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 String? copyFromAssetPath;
+ field public final java.io.File? copyFromFile;
+ field public final java.util.concurrent.Callable<java.io.InputStream!>? copyFromInputStream;
+ 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 androidx.room.RoomDatabase.PrepackagedDatabaseCallback? prepackagedDatabaseCallback;
+ 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;
+ field public final java.util.List<java.lang.Object!> typeConverters;
+ }
+
+ @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface ExperimentalRoomApi {
+ }
+
+ 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 <T> T? getTypeConverter(Class<T!>);
+ 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 android.database.Cursor query(androidx.sqlite.db.SupportSQLiteQuery, android.os.CancellationSignal?);
+ method public void runInTransaction(Runnable);
+ method public <V> V! runInTransaction(java.util.concurrent.Callable<V!>);
+ method @Deprecated public void setTransactionSuccessful();
+ 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!> addTypeConverter(Object);
+ method public androidx.room.RoomDatabase.Builder<T!> allowMainThreadQueries();
+ method public T build();
+ method public androidx.room.RoomDatabase.Builder<T!> createFromAsset(String);
+ method public androidx.room.RoomDatabase.Builder<T!> createFromAsset(String, androidx.room.RoomDatabase.PrepackagedDatabaseCallback);
+ method public androidx.room.RoomDatabase.Builder<T!> createFromFile(java.io.File);
+ method public androidx.room.RoomDatabase.Builder<T!> createFromFile(java.io.File, androidx.room.RoomDatabase.PrepackagedDatabaseCallback);
+ method public androidx.room.RoomDatabase.Builder<T!> createFromInputStream(java.util.concurrent.Callable<java.io.InputStream!>);
+ method public androidx.room.RoomDatabase.Builder<T!> createFromInputStream(java.util.concurrent.Callable<java.io.InputStream!>, androidx.room.RoomDatabase.PrepackagedDatabaseCallback);
+ 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 @androidx.room.ExperimentalRoomApi public androidx.room.RoomDatabase.Builder<T!> setAutoCloseTimeout(@IntRange(from=0) long, java.util.concurrent.TimeUnit);
+ method public androidx.room.RoomDatabase.Builder<T!> setJournalMode(androidx.room.RoomDatabase.JournalMode);
+ method public androidx.room.RoomDatabase.Builder<T!> setQueryCallback(androidx.room.RoomDatabase.QueryCallback, java.util.concurrent.Executor);
+ 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 onDestructiveMigration(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);
+ }
+
+ public abstract static class RoomDatabase.PrepackagedDatabaseCallback {
+ ctor public RoomDatabase.PrepackagedDatabaseCallback();
+ method public void onOpenPrepackagedDatabase(androidx.sqlite.db.SupportSQLiteDatabase);
+ }
+
+ public static interface RoomDatabase.QueryCallback {
+ method public void onQuery(String, java.util.List<java.lang.Object!>);
+ }
+
+}
+
+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/current.txt b/room/runtime/api/current.txt
index d9b5519..95f23cc 100644
--- a/room/runtime/api/current.txt
+++ b/room/runtime/api/current.txt
@@ -23,6 +23,9 @@
field public final java.util.List<java.lang.Object!> typeConverters;
}
+ @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface ExperimentalRoomApi {
+ }
+
public class InvalidationTracker {
method @WorkerThread public void addObserver(androidx.room.InvalidationTracker.Observer);
method public void refreshVersionsAsync();
@@ -86,7 +89,7 @@
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!> setAutoCloseTimeout(@IntRange(from=0) long, java.util.concurrent.TimeUnit);
+ method @androidx.room.ExperimentalRoomApi public androidx.room.RoomDatabase.Builder<T!> setAutoCloseTimeout(@IntRange(from=0) long, java.util.concurrent.TimeUnit);
method public androidx.room.RoomDatabase.Builder<T!> setJournalMode(androidx.room.RoomDatabase.JournalMode);
method public androidx.room.RoomDatabase.Builder<T!> setQueryCallback(androidx.room.RoomDatabase.QueryCallback, java.util.concurrent.Executor);
method public androidx.room.RoomDatabase.Builder<T!> setQueryExecutor(java.util.concurrent.Executor);
diff --git a/room/runtime/api/public_plus_experimental_2.3.0-beta03.txt b/room/runtime/api/public_plus_experimental_2.3.0-beta03.txt
new file mode 100644
index 0000000..b1f6259
--- /dev/null
+++ b/room/runtime/api/public_plus_experimental_2.3.0-beta03.txt
@@ -0,0 +1,140 @@
+// Signature format: 4.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 String? copyFromAssetPath;
+ field public final java.io.File? copyFromFile;
+ field public final java.util.concurrent.Callable<java.io.InputStream!>? copyFromInputStream;
+ 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 androidx.room.RoomDatabase.PrepackagedDatabaseCallback? prepackagedDatabaseCallback;
+ 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;
+ field public final java.util.List<java.lang.Object!> typeConverters;
+ }
+
+ @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface ExperimentalRoomApi {
+ }
+
+ 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 @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) protected java.util.Map<java.lang.Class<?>!,java.util.List<java.lang.Class<?>!>!> getRequiredTypeConverters();
+ method public java.util.concurrent.Executor getTransactionExecutor();
+ method public <T> T? getTypeConverter(Class<T!>);
+ 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 android.database.Cursor query(androidx.sqlite.db.SupportSQLiteQuery, android.os.CancellationSignal?);
+ method public void runInTransaction(Runnable);
+ method public <V> V! runInTransaction(java.util.concurrent.Callable<V!>);
+ method @Deprecated public void setTransactionSuccessful();
+ 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!> addTypeConverter(Object);
+ method public androidx.room.RoomDatabase.Builder<T!> allowMainThreadQueries();
+ method public T build();
+ method public androidx.room.RoomDatabase.Builder<T!> createFromAsset(String);
+ method public androidx.room.RoomDatabase.Builder<T!> createFromAsset(String, androidx.room.RoomDatabase.PrepackagedDatabaseCallback);
+ method public androidx.room.RoomDatabase.Builder<T!> createFromFile(java.io.File);
+ method public androidx.room.RoomDatabase.Builder<T!> createFromFile(java.io.File, androidx.room.RoomDatabase.PrepackagedDatabaseCallback);
+ method public androidx.room.RoomDatabase.Builder<T!> createFromInputStream(java.util.concurrent.Callable<java.io.InputStream!>);
+ method public androidx.room.RoomDatabase.Builder<T!> createFromInputStream(java.util.concurrent.Callable<java.io.InputStream!>, androidx.room.RoomDatabase.PrepackagedDatabaseCallback);
+ 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 @androidx.room.ExperimentalRoomApi public androidx.room.RoomDatabase.Builder<T!> setAutoCloseTimeout(@IntRange(from=0) long, java.util.concurrent.TimeUnit);
+ method public androidx.room.RoomDatabase.Builder<T!> setJournalMode(androidx.room.RoomDatabase.JournalMode);
+ method public androidx.room.RoomDatabase.Builder<T!> setQueryCallback(androidx.room.RoomDatabase.QueryCallback, java.util.concurrent.Executor);
+ 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 onDestructiveMigration(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);
+ }
+
+ public abstract static class RoomDatabase.PrepackagedDatabaseCallback {
+ ctor public RoomDatabase.PrepackagedDatabaseCallback();
+ method public void onOpenPrepackagedDatabase(androidx.sqlite.db.SupportSQLiteDatabase);
+ }
+
+ public static interface RoomDatabase.QueryCallback {
+ method public void onQuery(String, java.util.List<java.lang.Object!>);
+ }
+
+}
+
+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/public_plus_experimental_current.txt b/room/runtime/api/public_plus_experimental_current.txt
index d0cfe70..b1f6259 100644
--- a/room/runtime/api/public_plus_experimental_current.txt
+++ b/room/runtime/api/public_plus_experimental_current.txt
@@ -23,6 +23,9 @@
field public final java.util.List<java.lang.Object!> typeConverters;
}
+ @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface ExperimentalRoomApi {
+ }
+
public class InvalidationTracker {
method @WorkerThread public void addObserver(androidx.room.InvalidationTracker.Observer);
method public void refreshVersionsAsync();
@@ -87,7 +90,7 @@
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!> setAutoCloseTimeout(@IntRange(from=0) long, java.util.concurrent.TimeUnit);
+ method @androidx.room.ExperimentalRoomApi public androidx.room.RoomDatabase.Builder<T!> setAutoCloseTimeout(@IntRange(from=0) long, java.util.concurrent.TimeUnit);
method public androidx.room.RoomDatabase.Builder<T!> setJournalMode(androidx.room.RoomDatabase.JournalMode);
method public androidx.room.RoomDatabase.Builder<T!> setQueryCallback(androidx.room.RoomDatabase.QueryCallback, java.util.concurrent.Executor);
method public androidx.room.RoomDatabase.Builder<T!> setQueryExecutor(java.util.concurrent.Executor);
diff --git a/room/runtime/api/res-2.3.0-beta03.txt b/room/runtime/api/res-2.3.0-beta03.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/room/runtime/api/res-2.3.0-beta03.txt
diff --git a/room/runtime/api/restricted_2.3.0-beta03.txt b/room/runtime/api/restricted_2.3.0-beta03.txt
new file mode 100644
index 0000000..52fb0a1
--- /dev/null
+++ b/room/runtime/api/restricted_2.3.0-beta03.txt
@@ -0,0 +1,339 @@
+// Signature format: 4.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 @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, java.util.concurrent.Executor, boolean, boolean, boolean, java.util.Set<java.lang.Integer!>?);
+ 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, java.util.concurrent.Executor, boolean, boolean, boolean, java.util.Set<java.lang.Integer!>?, String?, java.io.File?);
+ 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, java.util.concurrent.Executor, boolean, boolean, boolean, java.util.Set<java.lang.Integer!>?, String?, java.io.File?, java.util.concurrent.Callable<java.io.InputStream!>?);
+ 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, java.util.concurrent.Executor, boolean, boolean, boolean, java.util.Set<java.lang.Integer!>?, String?, java.io.File?, java.util.concurrent.Callable<java.io.InputStream!>?, androidx.room.RoomDatabase.PrepackagedDatabaseCallback?);
+ 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!>?, String?, java.io.File?, java.util.concurrent.Callable<java.io.InputStream!>?, androidx.room.RoomDatabase.PrepackagedDatabaseCallback?, java.util.List<java.lang.Object!>?);
+ 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 String? copyFromAssetPath;
+ field public final java.io.File? copyFromFile;
+ field public final java.util.concurrent.Callable<java.io.InputStream!>? copyFromInputStream;
+ 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 androidx.room.RoomDatabase.PrepackagedDatabaseCallback? prepackagedDatabaseCallback;
+ 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;
+ field public final java.util.List<java.lang.Object!> typeConverters;
+ }
+
+ @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<? extends 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<? extends T>!);
+ method public final long insertAndReturnId(T!);
+ method public final long[]! insertAndReturnIdsArray(java.util.Collection<? extends T>!);
+ method public final long[]! insertAndReturnIdsArray(T![]!);
+ method public final Long![]! insertAndReturnIdsArrayBox(java.util.Collection<? extends 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<? extends T>!);
+ }
+
+ @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface ExperimentalRoomApi {
+ }
+
+ 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 @WorkerThread public void addObserver(androidx.room.InvalidationTracker.Observer);
+ 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 public void refreshVersionsAsync();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @WorkerThread public void refreshVersionsSync();
+ 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!>);
+ }
+
+ @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 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 @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void assertNotMainThread();
+ 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 <T> T? getTypeConverter(Class<T!>);
+ 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 android.database.Cursor query(androidx.sqlite.db.SupportSQLiteQuery, android.os.CancellationSignal?);
+ method public void runInTransaction(Runnable);
+ method public <V> V! runInTransaction(java.util.concurrent.Callable<V!>);
+ method @Deprecated public void setTransactionSuccessful();
+ field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int MAX_BIND_PARAMETER_CNT = 999; // 0x3e7
+ field @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) 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!> addTypeConverter(Object);
+ method public androidx.room.RoomDatabase.Builder<T!> allowMainThreadQueries();
+ method public T build();
+ method public androidx.room.RoomDatabase.Builder<T!> createFromAsset(String);
+ method public androidx.room.RoomDatabase.Builder<T!> createFromAsset(String, androidx.room.RoomDatabase.PrepackagedDatabaseCallback);
+ method public androidx.room.RoomDatabase.Builder<T!> createFromFile(java.io.File);
+ method public androidx.room.RoomDatabase.Builder<T!> createFromFile(java.io.File, androidx.room.RoomDatabase.PrepackagedDatabaseCallback);
+ method public androidx.room.RoomDatabase.Builder<T!> createFromInputStream(java.util.concurrent.Callable<java.io.InputStream!>);
+ method public androidx.room.RoomDatabase.Builder<T!> createFromInputStream(java.util.concurrent.Callable<java.io.InputStream!>, androidx.room.RoomDatabase.PrepackagedDatabaseCallback);
+ 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 @androidx.room.ExperimentalRoomApi public androidx.room.RoomDatabase.Builder<T!> setAutoCloseTimeout(@IntRange(from=0) long, java.util.concurrent.TimeUnit);
+ method public androidx.room.RoomDatabase.Builder<T!> setJournalMode(androidx.room.RoomDatabase.JournalMode);
+ method public androidx.room.RoomDatabase.Builder<T!> setQueryCallback(androidx.room.RoomDatabase.QueryCallback, java.util.concurrent.Executor);
+ 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 onDestructiveMigration(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);
+ }
+
+ public abstract static class RoomDatabase.PrepackagedDatabaseCallback {
+ ctor public RoomDatabase.PrepackagedDatabaseCallback();
+ method public void onOpenPrepackagedDatabase(androidx.sqlite.db.SupportSQLiteDatabase);
+ }
+
+ public static interface RoomDatabase.QueryCallback {
+ method public void onQuery(String, java.util.List<java.lang.Object!>);
+ }
+
+ @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 androidx.room.RoomOpenHelper.ValidationResult onValidateSchema(androidx.sqlite.db.SupportSQLiteDatabase);
+ method @Deprecated protected void validateMigration(androidx.sqlite.db.SupportSQLiteDatabase!);
+ field public final int version;
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static class RoomOpenHelper.ValidationResult {
+ ctor public RoomOpenHelper.ValidationResult(boolean, String?);
+ field public final String? expectedFoundMsg;
+ field public final boolean isValid;
+ }
+
+ @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.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;
+ }
+
+}
+
+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 CopyLock {
+ ctor public CopyLock(String, java.io.File, boolean);
+ method public void lock();
+ method public void unlock();
+ }
+
+ @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 android.os.CancellationSignal? createCancellationSignal();
+ method public static void dropFtsSyncTriggers(androidx.sqlite.db.SupportSQLiteDatabase!);
+ method @Deprecated public static android.database.Cursor query(androidx.room.RoomDatabase!, androidx.sqlite.db.SupportSQLiteQuery!, boolean);
+ method public static android.database.Cursor query(androidx.room.RoomDatabase, androidx.sqlite.db.SupportSQLiteQuery, boolean, android.os.CancellationSignal?);
+ method public static int readVersion(java.io.File) throws java.io.IOException;
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class FileUtil {
+ method public static void copy(java.nio.channels.ReadableByteChannel, java.nio.channels.FileChannel) throws java.io.IOException;
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final 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 final 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 static final int CREATED_FROM_DATABASE = 2; // 0x2
+ field public static final int CREATED_FROM_ENTITY = 1; // 0x1
+ field public static final int CREATED_FROM_UNKNOWN = 0; // 0x0
+ 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 final class TableInfo.Column {
+ ctor @Deprecated public TableInfo.Column(String!, String!, boolean, int);
+ ctor public TableInfo.Column(String!, String!, boolean, int, String!, int);
+ method public boolean isPrimaryKey();
+ field @androidx.room.ColumnInfo.SQLiteTypeAffinity public final int affinity;
+ field public final String! defaultValue;
+ 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 final 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 final 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 final 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_current.txt b/room/runtime/api/restricted_current.txt
index 0db4e83..52fb0a1 100644
--- a/room/runtime/api/restricted_current.txt
+++ b/room/runtime/api/restricted_current.txt
@@ -52,6 +52,9 @@
method public final java.util.List<java.lang.Long!>! insertAndReturnIdsList(java.util.Collection<? extends T>!);
}
+ @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface ExperimentalRoomApi {
+ }
+
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!...);
@@ -129,7 +132,7 @@
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!> setAutoCloseTimeout(@IntRange(from=0) long, java.util.concurrent.TimeUnit);
+ method @androidx.room.ExperimentalRoomApi public androidx.room.RoomDatabase.Builder<T!> setAutoCloseTimeout(@IntRange(from=0) long, java.util.concurrent.TimeUnit);
method public androidx.room.RoomDatabase.Builder<T!> setJournalMode(androidx.room.RoomDatabase.JournalMode);
method public androidx.room.RoomDatabase.Builder<T!> setQueryCallback(androidx.room.RoomDatabase.QueryCallback, java.util.concurrent.Executor);
method public androidx.room.RoomDatabase.Builder<T!> setQueryExecutor(java.util.concurrent.Executor);
diff --git a/room/runtime/build.gradle b/room/runtime/build.gradle
index 651e93f..31b6246 100644
--- a/room/runtime/build.gradle
+++ b/room/runtime/build.gradle
@@ -42,7 +42,7 @@
implementation("androidx.arch.core:core-runtime:2.0.1")
compileOnly("androidx.paging:paging-common:2.0.0")
compileOnly("androidx.lifecycle:lifecycle-livedata-core:2.0.0")
- implementation(project(":annotation:annotation-experimental"))
+ implementation("androidx.annotation:annotation-experimental:1.1.0-beta01")
compileOnly KOTLIN_STDLIB // Due to :annotation-experimental
testImplementation("androidx.arch.core:core-testing:2.0.1")
diff --git a/room/runtime/src/main/java/androidx/room/ExperimentalRoomApi.java b/room/runtime/src/main/java/androidx/room/ExperimentalRoomApi.java
index 7396429..729a4a9 100644
--- a/room/runtime/src/main/java/androidx/room/ExperimentalRoomApi.java
+++ b/room/runtime/src/main/java/androidx/room/ExperimentalRoomApi.java
@@ -26,4 +26,4 @@
*/
@Target({ElementType.METHOD})
@RequiresOptIn()
-@interface ExperimentalRoomApi {}
+public @interface ExperimentalRoomApi {}
diff --git a/room/rxjava2/api/2.3.0-beta03.txt b/room/rxjava2/api/2.3.0-beta03.txt
new file mode 100644
index 0000000..64b6fe4
--- /dev/null
+++ b/room/rxjava2/api/2.3.0-beta03.txt
@@ -0,0 +1,16 @@
+// Signature format: 4.0
+package androidx.room {
+
+ public class EmptyResultSetException extends java.lang.RuntimeException {
+ ctor public EmptyResultSetException(String!);
+ }
+
+ public class RxRoom {
+ ctor @Deprecated public RxRoom();
+ method public static io.reactivex.Flowable<java.lang.Object!>! createFlowable(androidx.room.RoomDatabase!, java.lang.String!...);
+ method public static io.reactivex.Observable<java.lang.Object!>! createObservable(androidx.room.RoomDatabase!, java.lang.String!...);
+ field public static final Object! NOTHING;
+ }
+
+}
+
diff --git a/room/rxjava2/api/public_plus_experimental_2.3.0-beta03.txt b/room/rxjava2/api/public_plus_experimental_2.3.0-beta03.txt
new file mode 100644
index 0000000..64b6fe4
--- /dev/null
+++ b/room/rxjava2/api/public_plus_experimental_2.3.0-beta03.txt
@@ -0,0 +1,16 @@
+// Signature format: 4.0
+package androidx.room {
+
+ public class EmptyResultSetException extends java.lang.RuntimeException {
+ ctor public EmptyResultSetException(String!);
+ }
+
+ public class RxRoom {
+ ctor @Deprecated public RxRoom();
+ method public static io.reactivex.Flowable<java.lang.Object!>! createFlowable(androidx.room.RoomDatabase!, java.lang.String!...);
+ method public static io.reactivex.Observable<java.lang.Object!>! createObservable(androidx.room.RoomDatabase!, java.lang.String!...);
+ field public static final Object! NOTHING;
+ }
+
+}
+
diff --git a/room/rxjava2/api/res-2.3.0-beta03.txt b/room/rxjava2/api/res-2.3.0-beta03.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/room/rxjava2/api/res-2.3.0-beta03.txt
diff --git a/room/rxjava2/api/restricted_2.3.0-beta03.txt b/room/rxjava2/api/restricted_2.3.0-beta03.txt
new file mode 100644
index 0000000..5505f93
--- /dev/null
+++ b/room/rxjava2/api/restricted_2.3.0-beta03.txt
@@ -0,0 +1,21 @@
+// Signature format: 4.0
+package androidx.room {
+
+ public class EmptyResultSetException extends java.lang.RuntimeException {
+ ctor public EmptyResultSetException(String!);
+ }
+
+ public class RxRoom {
+ ctor @Deprecated public RxRoom();
+ method public static io.reactivex.Flowable<java.lang.Object!>! createFlowable(androidx.room.RoomDatabase!, java.lang.String!...);
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.Flowable<T!>! createFlowable(androidx.room.RoomDatabase!, String![]!, java.util.concurrent.Callable<T!>!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.Flowable<T!>! createFlowable(androidx.room.RoomDatabase!, boolean, String![]!, java.util.concurrent.Callable<T!>!);
+ method public static io.reactivex.Observable<java.lang.Object!>! createObservable(androidx.room.RoomDatabase!, java.lang.String!...);
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.Observable<T!>! createObservable(androidx.room.RoomDatabase!, String![]!, java.util.concurrent.Callable<T!>!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.Observable<T!>! createObservable(androidx.room.RoomDatabase!, boolean, String![]!, java.util.concurrent.Callable<T!>!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.Single<T!>! createSingle(java.util.concurrent.Callable<T!>!);
+ field public static final Object! NOTHING;
+ }
+
+}
+
diff --git a/room/rxjava3/api/2.3.0-beta03.txt b/room/rxjava3/api/2.3.0-beta03.txt
new file mode 100644
index 0000000..6b78281
--- /dev/null
+++ b/room/rxjava3/api/2.3.0-beta03.txt
@@ -0,0 +1,15 @@
+// Signature format: 4.0
+package androidx.room.rxjava3 {
+
+ public final class EmptyResultSetException extends java.lang.RuntimeException {
+ ctor public EmptyResultSetException(String);
+ }
+
+ public final class RxRoom {
+ method public static io.reactivex.rxjava3.core.Flowable<java.lang.Object!> createFlowable(androidx.room.RoomDatabase, java.lang.String!...);
+ method public static io.reactivex.rxjava3.core.Observable<java.lang.Object!> createObservable(androidx.room.RoomDatabase, java.lang.String!...);
+ field public static final Object NOTHING;
+ }
+
+}
+
diff --git a/room/rxjava3/api/public_plus_experimental_2.3.0-beta03.txt b/room/rxjava3/api/public_plus_experimental_2.3.0-beta03.txt
new file mode 100644
index 0000000..6b78281
--- /dev/null
+++ b/room/rxjava3/api/public_plus_experimental_2.3.0-beta03.txt
@@ -0,0 +1,15 @@
+// Signature format: 4.0
+package androidx.room.rxjava3 {
+
+ public final class EmptyResultSetException extends java.lang.RuntimeException {
+ ctor public EmptyResultSetException(String);
+ }
+
+ public final class RxRoom {
+ method public static io.reactivex.rxjava3.core.Flowable<java.lang.Object!> createFlowable(androidx.room.RoomDatabase, java.lang.String!...);
+ method public static io.reactivex.rxjava3.core.Observable<java.lang.Object!> createObservable(androidx.room.RoomDatabase, java.lang.String!...);
+ field public static final Object NOTHING;
+ }
+
+}
+
diff --git a/room/rxjava3/api/res-2.3.0-beta03.txt b/room/rxjava3/api/res-2.3.0-beta03.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/room/rxjava3/api/res-2.3.0-beta03.txt
diff --git a/room/rxjava3/api/restricted_2.3.0-beta03.txt b/room/rxjava3/api/restricted_2.3.0-beta03.txt
new file mode 100644
index 0000000..0680710
--- /dev/null
+++ b/room/rxjava3/api/restricted_2.3.0-beta03.txt
@@ -0,0 +1,18 @@
+// Signature format: 4.0
+package androidx.room.rxjava3 {
+
+ public final class EmptyResultSetException extends java.lang.RuntimeException {
+ ctor public EmptyResultSetException(String);
+ }
+
+ public final class RxRoom {
+ method public static io.reactivex.rxjava3.core.Flowable<java.lang.Object!> createFlowable(androidx.room.RoomDatabase, java.lang.String!...);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.rxjava3.core.Flowable<T!> createFlowable(androidx.room.RoomDatabase, boolean, String![], java.util.concurrent.Callable<T!>);
+ method public static io.reactivex.rxjava3.core.Observable<java.lang.Object!> createObservable(androidx.room.RoomDatabase, java.lang.String!...);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.rxjava3.core.Observable<T!> createObservable(androidx.room.RoomDatabase, boolean, String![], java.util.concurrent.Callable<T!>);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.rxjava3.core.Single<T!> createSingle(java.util.concurrent.Callable<T!>);
+ field public static final Object NOTHING;
+ }
+
+}
+
diff --git a/room/testing/api/2.3.0-beta03.txt b/room/testing/api/2.3.0-beta03.txt
new file mode 100644
index 0000000..891c1b7
--- /dev/null
+++ b/room/testing/api/2.3.0-beta03.txt
@@ -0,0 +1,14 @@
+// Signature format: 4.0
+package androidx.room.testing {
+
+ public class MigrationTestHelper extends org.junit.rules.TestWatcher {
+ ctor public MigrationTestHelper(android.app.Instrumentation!, String!);
+ ctor public MigrationTestHelper(android.app.Instrumentation!, String!, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory!);
+ method public void closeWhenFinished(androidx.sqlite.db.SupportSQLiteDatabase!);
+ method public void closeWhenFinished(androidx.room.RoomDatabase!);
+ method public androidx.sqlite.db.SupportSQLiteDatabase! createDatabase(String!, int) throws java.io.IOException;
+ method public androidx.sqlite.db.SupportSQLiteDatabase! runMigrationsAndValidate(String!, int, boolean, androidx.room.migration.Migration!...) throws java.io.IOException;
+ }
+
+}
+
diff --git a/room/testing/api/public_plus_experimental_2.3.0-beta03.txt b/room/testing/api/public_plus_experimental_2.3.0-beta03.txt
new file mode 100644
index 0000000..891c1b7
--- /dev/null
+++ b/room/testing/api/public_plus_experimental_2.3.0-beta03.txt
@@ -0,0 +1,14 @@
+// Signature format: 4.0
+package androidx.room.testing {
+
+ public class MigrationTestHelper extends org.junit.rules.TestWatcher {
+ ctor public MigrationTestHelper(android.app.Instrumentation!, String!);
+ ctor public MigrationTestHelper(android.app.Instrumentation!, String!, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory!);
+ method public void closeWhenFinished(androidx.sqlite.db.SupportSQLiteDatabase!);
+ method public void closeWhenFinished(androidx.room.RoomDatabase!);
+ method public androidx.sqlite.db.SupportSQLiteDatabase! createDatabase(String!, int) throws java.io.IOException;
+ method public androidx.sqlite.db.SupportSQLiteDatabase! runMigrationsAndValidate(String!, int, boolean, androidx.room.migration.Migration!...) throws java.io.IOException;
+ }
+
+}
+
diff --git a/room/testing/api/res-2.3.0-beta03.txt b/room/testing/api/res-2.3.0-beta03.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/room/testing/api/res-2.3.0-beta03.txt
diff --git a/room/testing/api/restricted_2.3.0-beta03.txt b/room/testing/api/restricted_2.3.0-beta03.txt
new file mode 100644
index 0000000..891c1b7
--- /dev/null
+++ b/room/testing/api/restricted_2.3.0-beta03.txt
@@ -0,0 +1,14 @@
+// Signature format: 4.0
+package androidx.room.testing {
+
+ public class MigrationTestHelper extends org.junit.rules.TestWatcher {
+ ctor public MigrationTestHelper(android.app.Instrumentation!, String!);
+ ctor public MigrationTestHelper(android.app.Instrumentation!, String!, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory!);
+ method public void closeWhenFinished(androidx.sqlite.db.SupportSQLiteDatabase!);
+ method public void closeWhenFinished(androidx.room.RoomDatabase!);
+ method public androidx.sqlite.db.SupportSQLiteDatabase! createDatabase(String!, int) throws java.io.IOException;
+ method public androidx.sqlite.db.SupportSQLiteDatabase! runMigrationsAndValidate(String!, int, boolean, androidx.room.migration.Migration!...) throws java.io.IOException;
+ }
+
+}
+
diff --git a/wear/wear-phone-interactions/api/current.txt b/wear/wear-phone-interactions/api/current.txt
index e73768b..0863cad 100644
--- a/wear/wear-phone-interactions/api/current.txt
+++ b/wear/wear-phone-interactions/api/current.txt
@@ -16,6 +16,90 @@
}
+package androidx.wear.phone.interactions.authentication {
+
+ @RequiresApi(android.os.Build.VERSION_CODES.O) public final class CodeChallenge {
+ ctor public CodeChallenge(androidx.wear.phone.interactions.authentication.CodeVerifier codeVerifier);
+ method public String getValue();
+ }
+
+ @RequiresApi(android.os.Build.VERSION_CODES.O) public final class CodeVerifier {
+ ctor public CodeVerifier(optional int byteLength);
+ ctor public CodeVerifier(String value);
+ method public String getValue();
+ }
+
+ public final class OAuthRequest {
+ method public String getPackageName();
+ method public android.net.Uri getRequestUrl();
+ field public static final androidx.wear.phone.interactions.authentication.OAuthRequest.Companion Companion;
+ field public static final String WEAR_REDIRECT_URL_PREFIX = "https://wear.googleapis.com/3p_auth/";
+ }
+
+ public static final class OAuthRequest.Builder {
+ ctor public OAuthRequest.Builder(String packageName);
+ method @RequiresApi(android.os.Build.VERSION_CODES.O) public androidx.wear.phone.interactions.authentication.OAuthRequest build();
+ method public androidx.wear.phone.interactions.authentication.OAuthRequest.Builder setAuthProviderUrl(android.net.Uri authProviderUrl);
+ method public androidx.wear.phone.interactions.authentication.OAuthRequest.Builder setClientId(String clientId);
+ method public androidx.wear.phone.interactions.authentication.OAuthRequest.Builder setCodeChallenge(androidx.wear.phone.interactions.authentication.CodeChallenge codeChallenge);
+ method public androidx.wear.phone.interactions.authentication.OAuthRequest.Builder setRedirectUrl(android.net.Uri redirectUrl);
+ }
+
+ public static final class OAuthRequest.Companion {
+ }
+
+ public final class OAuthResponse {
+ method public int getErrorCode();
+ method public android.net.Uri? getResponseUrl();
+ }
+
+ public static final class OAuthResponse.Builder {
+ ctor public OAuthResponse.Builder();
+ method public androidx.wear.phone.interactions.authentication.OAuthResponse build();
+ method public androidx.wear.phone.interactions.authentication.OAuthResponse.Builder setErrorCode(int errorCode);
+ method public androidx.wear.phone.interactions.authentication.OAuthResponse.Builder setResponseUrl(android.net.Uri responseUrl);
+ }
+
+ public final class RemoteAuthClient implements java.lang.AutoCloseable {
+ method @UiThread public void close();
+ method public static androidx.wear.phone.interactions.authentication.RemoteAuthClient create(android.content.Context context);
+ method protected void finalize();
+ method @UiThread public void sendAuthorizationRequest(androidx.wear.phone.interactions.authentication.OAuthRequest request, androidx.wear.phone.interactions.authentication.RemoteAuthClient.Callback clientCallback);
+ field public static final androidx.wear.phone.interactions.authentication.RemoteAuthClient.Companion Companion;
+ field public static final int ERROR_PHONE_UNAVAILABLE = 2; // 0x2
+ field public static final int ERROR_UNSUPPORTED = 1; // 0x1
+ field public static final int NO_ERROR = 0; // 0x0
+ }
+
+ public abstract static class RemoteAuthClient.Callback {
+ ctor public RemoteAuthClient.Callback();
+ method @UiThread public abstract void onAuthorizationError(int errorCode);
+ method @UiThread public abstract void onAuthorizationResponse(androidx.wear.phone.interactions.authentication.OAuthRequest request, androidx.wear.phone.interactions.authentication.OAuthResponse response);
+ }
+
+ public static final class RemoteAuthClient.Companion {
+ method public androidx.wear.phone.interactions.authentication.RemoteAuthClient create(android.content.Context context);
+ }
+
+ public interface RemoteAuthRequestHandler {
+ method public boolean isAuthSupported();
+ method public void sendAuthRequest(androidx.wear.phone.interactions.authentication.OAuthRequest request, kotlin.Pair<java.lang.String,java.lang.Integer> packageNameAndRequestId);
+ }
+
+ public abstract class RemoteAuthService extends android.app.Service {
+ ctor public RemoteAuthService();
+ method protected final android.os.IBinder onBind(android.content.Intent intent, androidx.wear.phone.interactions.authentication.RemoteAuthRequestHandler remoteAuthRequestHandler);
+ method public static final void sendResponseToCallback(androidx.wear.phone.interactions.authentication.OAuthResponse response, kotlin.Pair<java.lang.String,java.lang.Integer> packageNameAndRequestId);
+ method protected boolean verifyPackageName(android.content.Context context, String? requestPackageName);
+ field public static final androidx.wear.phone.interactions.authentication.RemoteAuthService.Companion Companion;
+ }
+
+ public static final class RemoteAuthService.Companion {
+ method public void sendResponseToCallback(androidx.wear.phone.interactions.authentication.OAuthResponse response, kotlin.Pair<java.lang.String,java.lang.Integer> packageNameAndRequestId);
+ }
+
+}
+
package androidx.wear.phone.interactions.notifications {
public final class BridgingConfig {
diff --git a/wear/wear-phone-interactions/api/public_plus_experimental_current.txt b/wear/wear-phone-interactions/api/public_plus_experimental_current.txt
index e73768b..0863cad 100644
--- a/wear/wear-phone-interactions/api/public_plus_experimental_current.txt
+++ b/wear/wear-phone-interactions/api/public_plus_experimental_current.txt
@@ -16,6 +16,90 @@
}
+package androidx.wear.phone.interactions.authentication {
+
+ @RequiresApi(android.os.Build.VERSION_CODES.O) public final class CodeChallenge {
+ ctor public CodeChallenge(androidx.wear.phone.interactions.authentication.CodeVerifier codeVerifier);
+ method public String getValue();
+ }
+
+ @RequiresApi(android.os.Build.VERSION_CODES.O) public final class CodeVerifier {
+ ctor public CodeVerifier(optional int byteLength);
+ ctor public CodeVerifier(String value);
+ method public String getValue();
+ }
+
+ public final class OAuthRequest {
+ method public String getPackageName();
+ method public android.net.Uri getRequestUrl();
+ field public static final androidx.wear.phone.interactions.authentication.OAuthRequest.Companion Companion;
+ field public static final String WEAR_REDIRECT_URL_PREFIX = "https://wear.googleapis.com/3p_auth/";
+ }
+
+ public static final class OAuthRequest.Builder {
+ ctor public OAuthRequest.Builder(String packageName);
+ method @RequiresApi(android.os.Build.VERSION_CODES.O) public androidx.wear.phone.interactions.authentication.OAuthRequest build();
+ method public androidx.wear.phone.interactions.authentication.OAuthRequest.Builder setAuthProviderUrl(android.net.Uri authProviderUrl);
+ method public androidx.wear.phone.interactions.authentication.OAuthRequest.Builder setClientId(String clientId);
+ method public androidx.wear.phone.interactions.authentication.OAuthRequest.Builder setCodeChallenge(androidx.wear.phone.interactions.authentication.CodeChallenge codeChallenge);
+ method public androidx.wear.phone.interactions.authentication.OAuthRequest.Builder setRedirectUrl(android.net.Uri redirectUrl);
+ }
+
+ public static final class OAuthRequest.Companion {
+ }
+
+ public final class OAuthResponse {
+ method public int getErrorCode();
+ method public android.net.Uri? getResponseUrl();
+ }
+
+ public static final class OAuthResponse.Builder {
+ ctor public OAuthResponse.Builder();
+ method public androidx.wear.phone.interactions.authentication.OAuthResponse build();
+ method public androidx.wear.phone.interactions.authentication.OAuthResponse.Builder setErrorCode(int errorCode);
+ method public androidx.wear.phone.interactions.authentication.OAuthResponse.Builder setResponseUrl(android.net.Uri responseUrl);
+ }
+
+ public final class RemoteAuthClient implements java.lang.AutoCloseable {
+ method @UiThread public void close();
+ method public static androidx.wear.phone.interactions.authentication.RemoteAuthClient create(android.content.Context context);
+ method protected void finalize();
+ method @UiThread public void sendAuthorizationRequest(androidx.wear.phone.interactions.authentication.OAuthRequest request, androidx.wear.phone.interactions.authentication.RemoteAuthClient.Callback clientCallback);
+ field public static final androidx.wear.phone.interactions.authentication.RemoteAuthClient.Companion Companion;
+ field public static final int ERROR_PHONE_UNAVAILABLE = 2; // 0x2
+ field public static final int ERROR_UNSUPPORTED = 1; // 0x1
+ field public static final int NO_ERROR = 0; // 0x0
+ }
+
+ public abstract static class RemoteAuthClient.Callback {
+ ctor public RemoteAuthClient.Callback();
+ method @UiThread public abstract void onAuthorizationError(int errorCode);
+ method @UiThread public abstract void onAuthorizationResponse(androidx.wear.phone.interactions.authentication.OAuthRequest request, androidx.wear.phone.interactions.authentication.OAuthResponse response);
+ }
+
+ public static final class RemoteAuthClient.Companion {
+ method public androidx.wear.phone.interactions.authentication.RemoteAuthClient create(android.content.Context context);
+ }
+
+ public interface RemoteAuthRequestHandler {
+ method public boolean isAuthSupported();
+ method public void sendAuthRequest(androidx.wear.phone.interactions.authentication.OAuthRequest request, kotlin.Pair<java.lang.String,java.lang.Integer> packageNameAndRequestId);
+ }
+
+ public abstract class RemoteAuthService extends android.app.Service {
+ ctor public RemoteAuthService();
+ method protected final android.os.IBinder onBind(android.content.Intent intent, androidx.wear.phone.interactions.authentication.RemoteAuthRequestHandler remoteAuthRequestHandler);
+ method public static final void sendResponseToCallback(androidx.wear.phone.interactions.authentication.OAuthResponse response, kotlin.Pair<java.lang.String,java.lang.Integer> packageNameAndRequestId);
+ method protected boolean verifyPackageName(android.content.Context context, String? requestPackageName);
+ field public static final androidx.wear.phone.interactions.authentication.RemoteAuthService.Companion Companion;
+ }
+
+ public static final class RemoteAuthService.Companion {
+ method public void sendResponseToCallback(androidx.wear.phone.interactions.authentication.OAuthResponse response, kotlin.Pair<java.lang.String,java.lang.Integer> packageNameAndRequestId);
+ }
+
+}
+
package androidx.wear.phone.interactions.notifications {
public final class BridgingConfig {
diff --git a/wear/wear-phone-interactions/api/restricted_current.txt b/wear/wear-phone-interactions/api/restricted_current.txt
index e73768b..0863cad 100644
--- a/wear/wear-phone-interactions/api/restricted_current.txt
+++ b/wear/wear-phone-interactions/api/restricted_current.txt
@@ -16,6 +16,90 @@
}
+package androidx.wear.phone.interactions.authentication {
+
+ @RequiresApi(android.os.Build.VERSION_CODES.O) public final class CodeChallenge {
+ ctor public CodeChallenge(androidx.wear.phone.interactions.authentication.CodeVerifier codeVerifier);
+ method public String getValue();
+ }
+
+ @RequiresApi(android.os.Build.VERSION_CODES.O) public final class CodeVerifier {
+ ctor public CodeVerifier(optional int byteLength);
+ ctor public CodeVerifier(String value);
+ method public String getValue();
+ }
+
+ public final class OAuthRequest {
+ method public String getPackageName();
+ method public android.net.Uri getRequestUrl();
+ field public static final androidx.wear.phone.interactions.authentication.OAuthRequest.Companion Companion;
+ field public static final String WEAR_REDIRECT_URL_PREFIX = "https://wear.googleapis.com/3p_auth/";
+ }
+
+ public static final class OAuthRequest.Builder {
+ ctor public OAuthRequest.Builder(String packageName);
+ method @RequiresApi(android.os.Build.VERSION_CODES.O) public androidx.wear.phone.interactions.authentication.OAuthRequest build();
+ method public androidx.wear.phone.interactions.authentication.OAuthRequest.Builder setAuthProviderUrl(android.net.Uri authProviderUrl);
+ method public androidx.wear.phone.interactions.authentication.OAuthRequest.Builder setClientId(String clientId);
+ method public androidx.wear.phone.interactions.authentication.OAuthRequest.Builder setCodeChallenge(androidx.wear.phone.interactions.authentication.CodeChallenge codeChallenge);
+ method public androidx.wear.phone.interactions.authentication.OAuthRequest.Builder setRedirectUrl(android.net.Uri redirectUrl);
+ }
+
+ public static final class OAuthRequest.Companion {
+ }
+
+ public final class OAuthResponse {
+ method public int getErrorCode();
+ method public android.net.Uri? getResponseUrl();
+ }
+
+ public static final class OAuthResponse.Builder {
+ ctor public OAuthResponse.Builder();
+ method public androidx.wear.phone.interactions.authentication.OAuthResponse build();
+ method public androidx.wear.phone.interactions.authentication.OAuthResponse.Builder setErrorCode(int errorCode);
+ method public androidx.wear.phone.interactions.authentication.OAuthResponse.Builder setResponseUrl(android.net.Uri responseUrl);
+ }
+
+ public final class RemoteAuthClient implements java.lang.AutoCloseable {
+ method @UiThread public void close();
+ method public static androidx.wear.phone.interactions.authentication.RemoteAuthClient create(android.content.Context context);
+ method protected void finalize();
+ method @UiThread public void sendAuthorizationRequest(androidx.wear.phone.interactions.authentication.OAuthRequest request, androidx.wear.phone.interactions.authentication.RemoteAuthClient.Callback clientCallback);
+ field public static final androidx.wear.phone.interactions.authentication.RemoteAuthClient.Companion Companion;
+ field public static final int ERROR_PHONE_UNAVAILABLE = 2; // 0x2
+ field public static final int ERROR_UNSUPPORTED = 1; // 0x1
+ field public static final int NO_ERROR = 0; // 0x0
+ }
+
+ public abstract static class RemoteAuthClient.Callback {
+ ctor public RemoteAuthClient.Callback();
+ method @UiThread public abstract void onAuthorizationError(int errorCode);
+ method @UiThread public abstract void onAuthorizationResponse(androidx.wear.phone.interactions.authentication.OAuthRequest request, androidx.wear.phone.interactions.authentication.OAuthResponse response);
+ }
+
+ public static final class RemoteAuthClient.Companion {
+ method public androidx.wear.phone.interactions.authentication.RemoteAuthClient create(android.content.Context context);
+ }
+
+ public interface RemoteAuthRequestHandler {
+ method public boolean isAuthSupported();
+ method public void sendAuthRequest(androidx.wear.phone.interactions.authentication.OAuthRequest request, kotlin.Pair<java.lang.String,java.lang.Integer> packageNameAndRequestId);
+ }
+
+ public abstract class RemoteAuthService extends android.app.Service {
+ ctor public RemoteAuthService();
+ method protected final android.os.IBinder onBind(android.content.Intent intent, androidx.wear.phone.interactions.authentication.RemoteAuthRequestHandler remoteAuthRequestHandler);
+ method public static final void sendResponseToCallback(androidx.wear.phone.interactions.authentication.OAuthResponse response, kotlin.Pair<java.lang.String,java.lang.Integer> packageNameAndRequestId);
+ method protected boolean verifyPackageName(android.content.Context context, String? requestPackageName);
+ field public static final androidx.wear.phone.interactions.authentication.RemoteAuthService.Companion Companion;
+ }
+
+ public static final class RemoteAuthService.Companion {
+ method public void sendResponseToCallback(androidx.wear.phone.interactions.authentication.OAuthResponse response, kotlin.Pair<java.lang.String,java.lang.Integer> packageNameAndRequestId);
+ }
+
+}
+
package androidx.wear.phone.interactions.notifications {
public final class BridgingConfig {
diff --git a/wear/wear-phone-interactions/src/main/aidl/android/support/wearable/authentication/IAuthenticationRequestCallback.aidl b/wear/wear-phone-interactions/src/main/aidl/android/support/wearable/authentication/IAuthenticationRequestCallback.aidl
new file mode 100644
index 0000000..c9f49542
--- /dev/null
+++ b/wear/wear-phone-interactions/src/main/aidl/android/support/wearable/authentication/IAuthenticationRequestCallback.aidl
@@ -0,0 +1,37 @@
+package android.support.wearable.authentication;
+
+/**
+ * Interface for defining the callback to be notified when an aync remote authentication request
+ * completes.
+ *
+ * @hide
+ */
+interface IAuthenticationRequestCallback {
+ // IMPORTANT NOTE: All methods must be given an explicit transcation id that must never change
+ // in the future to remain binary backwards compatible
+ // Next Id: 2
+
+ /**
+ * API version number. This should be incremented every time a new method is added.
+ */
+ const int API_VERSION = 1;
+
+ /**
+ * Called when an aync authentication request is completed.
+ *
+ * Bundle contents:
+ * <ul><li>"responseUrl": the response URL from the remote auth request (Uri)
+ * <ul><li>"error": an error code explaining the request result status (int)
+ *
+ * @since API version 0
+ */
+ void onResult(in Bundle result) = 0;
+
+ /**
+ * Return the version number of this interface which the client can use to determine which
+ * methods are available.
+ *
+ * @since API version 1
+ */
+ int getApiVersion() = 1;
+}
diff --git a/wear/wear-phone-interactions/src/main/aidl/android/support/wearable/authentication/IAuthenticationRequestService.aidl b/wear/wear-phone-interactions/src/main/aidl/android/support/wearable/authentication/IAuthenticationRequestService.aidl
new file mode 100644
index 0000000..7e3d3a1
--- /dev/null
+++ b/wear/wear-phone-interactions/src/main/aidl/android/support/wearable/authentication/IAuthenticationRequestService.aidl
@@ -0,0 +1,39 @@
+package android.support.wearable.authentication;
+
+import android.support.wearable.authentication.IAuthenticationRequestCallback;
+
+/**
+ * Interface of a service that supports an async remote authentication.
+ *
+ * @hide
+ */
+interface IAuthenticationRequestService {
+ // IMPORTANT NOTE: all methods must be given an explicit transaction id that must never change
+ // in the future to remain binary backwards compatible.
+ // Next Id: 2
+
+ /**
+ * API version number. This should be incremented every time a new method is added.
+ */
+ const int API_VERSION = 1;
+
+ /**
+ * Open the request Url, and send the respond result back to callback when authentication
+ * completed.
+ *
+ * Bundle contents:
+ * <ul><li>"requestUrl": the URL of the OAuth request (Uri)
+ * <ul><li>"packageName": the package name of the requester (String)
+ *
+ * @since API version 0
+ */
+ void openUrl(
+ in Bundle request,
+ in IAuthenticationRequestCallback authenticationRequestCallback) = 0;
+
+ /**
+ * Return the version number of this interface which the client can use to determine which
+ * methods are available
+ */
+ int getApiVersion() = 1;
+}
diff --git a/wear/wear-phone-interactions/src/main/aidl/android/support/wearable/notifications/IBridgingManagerService.aidl b/wear/wear-phone-interactions/src/main/aidl/android/support/wearable/notifications/IBridgingManagerService.aidl
index d887837..4141777 100644
--- a/wear/wear-phone-interactions/src/main/aidl/android/support/wearable/notifications/IBridgingManagerService.aidl
+++ b/wear/wear-phone-interactions/src/main/aidl/android/support/wearable/notifications/IBridgingManagerService.aidl
@@ -34,7 +34,7 @@
/**
* Sets the bridging configuration
*
- * @since API version 1
+ * @since API version 0
*/
void setBridgingConfig(in Bundle bridgingConfig) = 0;
diff --git a/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/authentication/CodeChallenge.kt b/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/authentication/CodeChallenge.kt
new file mode 100644
index 0000000..3b10dcf
--- /dev/null
+++ b/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/authentication/CodeChallenge.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.phone.interactions.authentication
+
+import android.os.Build
+import androidx.annotation.RequiresApi
+import java.nio.charset.StandardCharsets
+import java.security.MessageDigest
+import java.util.Base64
+
+/**
+ * Authorization code challenge
+ *
+ * * Related specifications:
+ * Proof Key for Code Exchange by OAuth Public Clients (RFC 7636).
+ * https://tools.ietf.org/html/rfc7636
+ */
+@RequiresApi(Build.VERSION_CODES.O)
+public class CodeChallenge constructor(
+ codeVerifier: CodeVerifier
+) {
+
+ /**
+ * The challenge value.
+ */
+ private var value: String? = null
+
+ /**
+ * Computes the code challenge value using the specified verifier with SHA-256.
+ */
+ init {
+ val md = MessageDigest.getInstance("SHA-256")
+ val hash: ByteArray = md.digest(codeVerifier.getValueBytes())
+ value = Base64.getUrlEncoder().withoutPadding().encodeToString(hash)
+ }
+
+ public fun getValue(): String {
+ return value!!
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (other is CodeChallenge) {
+ return other.getValue() == value
+ }
+ return false
+ }
+
+ override fun hashCode(): Int {
+ return value!!.toByteArray(StandardCharsets.UTF_8).contentHashCode()
+ }
+}
\ No newline at end of file
diff --git a/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/authentication/CodeVerifier.kt b/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/authentication/CodeVerifier.kt
new file mode 100644
index 0000000..6dacb90
--- /dev/null
+++ b/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/authentication/CodeVerifier.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.phone.interactions.authentication
+
+import android.os.Build
+import androidx.annotation.RequiresApi
+import java.nio.charset.StandardCharsets
+import java.security.SecureRandom
+import java.util.Base64
+
+/**
+ * Authorisation code verifier.
+ *
+ * * Related specifications:
+ * Proof Key for Code Exchange by OAuth Public Clients (RFC 7636).
+ * https://tools.ietf.org/html/rfc7636
+ */
+@RequiresApi(Build.VERSION_CODES.O)
+public class CodeVerifier {
+ private companion object {
+ /**
+ * The minimum byte length of a code verifier.
+ */
+ private const val MIN_LENGTH_BYTE = 32
+
+ /**
+ * The maximum character length of a code verifier.
+ */
+ private const val MAX_LENGTH_BYTE = 96
+
+ /**
+ * The minimum character length of a code verifier with base64url-encoded.
+ */
+ private const val MIN_LENGTH_BASE64URL = 43
+
+ /**
+ * The maximum character length of a code verifier with base64url-encoded.
+ */
+ private const val MAX_LENGTH_BASE64URL = 128
+
+ /**
+ * The secure random generator.
+ */
+ private val SECURE_RANDOM: SecureRandom = SecureRandom()
+ }
+
+ /**
+ * The verifier value.
+ */
+ private var value: String? = null
+
+ @JvmOverloads
+ public constructor(
+ /**
+ * It is RECOMMENDED that the output of a suitable random number generator be used to create a
+ * 32-octet sequence. The octet sequence is then base64url-encoded to produce a 43-octet URL
+ * safe string to use as the code verifier.
+ */
+ byteLength: Int = 32
+ ) {
+ /**
+ * Generates a new code verifier with a cryptographic random value of the specified byte
+ * length, Base64URL-encoded
+ */
+ require((byteLength >= MIN_LENGTH_BYTE) and (byteLength <= MAX_LENGTH_BYTE)) {
+ "code verifier for PKCE must has a minimum length of $MIN_LENGTH_BASE64URL characters" +
+ " and a maximum length of $MAX_LENGTH_BASE64URL characters, please generate " +
+ "the code verifier with byte length between $MIN_LENGTH_BYTE and $MAX_LENGTH_BYTE"
+ }
+
+ val randomBytes = ByteArray(byteLength)
+
+ SECURE_RANDOM.nextBytes(randomBytes)
+
+ value = Base64.getUrlEncoder().withoutPadding().encodeToString(randomBytes)
+ }
+
+ public constructor(value: String) {
+ this.value = value
+ }
+
+ public fun getValue(): String {
+ return value!!
+ }
+
+ internal fun getValueBytes(): ByteArray {
+ return value!!.toByteArray(StandardCharsets.UTF_8)
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (other is CodeVerifier) {
+ return other.getValue() == value
+ }
+ return false
+ }
+
+ override fun hashCode(): Int {
+ return getValueBytes().contentHashCode()
+ }
+}
\ No newline at end of file
diff --git a/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/authentication/OAuthRequest.kt b/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/authentication/OAuthRequest.kt
new file mode 100644
index 0000000..89402f1
--- /dev/null
+++ b/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/authentication/OAuthRequest.kt
@@ -0,0 +1,221 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.phone.interactions.authentication
+
+import android.annotation.SuppressLint
+import android.net.Uri
+import android.os.Build
+import android.os.Bundle
+import androidx.annotation.RequiresApi
+
+/**
+ * The OAuth request to be sent to the server to start the OAuth 2 authentication flow
+ */
+public class OAuthRequest internal constructor(
+ private var packageName: String,
+ private val requestUrl: Uri
+) {
+ public companion object {
+ /**
+ * The default google-specific custom URL to route the response from the auth
+ * server back to the 1P companion app, which then forwards it to the 3P app that made
+ * the request on the wear device.
+ *
+ * To deliver an Auth response to your Wear app, set the redirect_uri
+ * parameter on the Auth request, with your app's package name appended.
+ *
+ * For example, if your app's package name is com.package.name, with 1P companion app
+ * paired, the redirect_uri query will be WEAR_REDIRECT_URL_PREFIX + "com.package.name".
+ */
+ public const val WEAR_REDIRECT_URL_PREFIX: String = "https://wear.googleapis.com/3p_auth/"
+ }
+
+ /**
+ * Builder for constructing new instance of OAuth request.
+ */
+ public class Builder(private val packageName: String) {
+ private var authProviderUrl: Uri? = null
+ private var codeChallenge: CodeChallenge? = null
+ private var clientId: String? = null
+ private var redirectUrl: Uri? = null
+ /**
+ * Set the url of the auth provider site.
+ * It provides the address pointing to the 3p/4p auth site. Appending query parameters in
+ * this uri is optional, it is recommended to let the builder append query parameters
+ * automatically through the use of setters (no setter is required for the builder to
+ * append the redirect_uri).
+ */
+ @SuppressLint("MissingGetterMatchingBuilder")
+ public fun setAuthProviderUrl(authProviderUrl: Uri): Builder =
+ this.apply {
+ this.authProviderUrl = authProviderUrl
+ }
+
+ /**
+ * Set the code challenge for authentication with PKCE (proof key for code exchange).
+ * With this setter called, the builder appends the "code_challenge",
+ * "code_challenge_method" and "response_type" queries to the requestUrl.
+ */
+ @SuppressLint("MissingGetterMatchingBuilder")
+ public fun setCodeChallenge(codeChallenge: CodeChallenge): Builder =
+ this.apply {
+ this.codeChallenge = codeChallenge
+ }
+
+ /**
+ * Set the client id of this OAuth request.
+ * With this setter called. the builder appends the "client_id" to the requestUrl.
+ */
+ @SuppressLint("MissingGetterMatchingBuilder")
+ public fun setClientId(clientId: String): Builder =
+ this.apply {
+ this.clientId = clientId
+ }
+
+ /**
+ * Set the redirect url the companion app registered to, so that the response will be
+ * routed from the auth server back to the companion.
+ *
+ * Calling this method is optional. If the redirect URL is not specified, it will be
+ * automatically set to [WEAR_REDIRECT_URL_PREFIX]
+ *
+ * Note, the app package name should NOT be included here, it will be appended to the end
+ * of redirect_uri automatically in [Builder.build].
+ */
+ @SuppressLint("MissingGetterMatchingBuilder")
+ public fun setRedirectUrl(redirectUrl: Uri): Builder =
+ this.apply {
+ this.redirectUrl = redirectUrl
+ }
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ internal fun composeRequestUrl(): Uri {
+ require(authProviderUrl != null) {
+ "The request requires the auth provider url to be provided."
+ }
+ val requestUriBuilder = authProviderUrl!!.buildUpon()
+
+ clientId?.let {
+ appendQueryParameter(requestUriBuilder, "client_id", clientId!!)
+ }
+
+ /**
+ * Set the request url by redirecting the auth provider URL with the WearOS auth
+ * site [WEAR_REDIRECT_URL_PREFIX].
+ * The receiving app's package name is also required as the 3rd path component in the
+ * redirect_uri, this allows Wear to ensure other apps cannot reuse your redirect_uri
+ * to receive responses.
+ */
+ appendQueryParameter(
+ requestUriBuilder,
+ "redirect_uri",
+ Uri.withAppendedPath(
+ if (redirectUrl == null) Uri.parse(WEAR_REDIRECT_URL_PREFIX) else redirectUrl,
+ packageName
+ ).toString()
+ )
+
+ codeChallenge?.let {
+ appendQueryParameter(requestUriBuilder, "response_type", "code")
+ appendQueryParameter(
+ requestUriBuilder,
+ "code_challenge",
+ codeChallenge!!.getValue()
+ )
+ appendQueryParameter(requestUriBuilder, "code_challenge_method", "S256")
+ }
+
+ return requestUriBuilder.build()
+ }
+
+ private fun appendQueryParameter(
+ requestUriBuilder: Uri.Builder,
+ queryKey: String,
+ expectedQueryParam: String
+ ) {
+ val currentQueryParam = authProviderUrl!!.getQueryParameter(queryKey)
+ currentQueryParam?.let {
+ require(expectedQueryParam == currentQueryParam) {
+ "The '$queryKey' query param already exists in the authProviderUrl, " +
+ "expect to have the value of '$expectedQueryParam', but " +
+ "'$currentQueryParam' is given. Please correct it, or leave it out " +
+ "to allow the request builder to append it automatically."
+ }
+ } ?: run {
+ requestUriBuilder.appendQueryParameter(queryKey, expectedQueryParam)
+ }
+ }
+
+ /** Build the request instance specified by this builder */
+ @RequiresApi(Build.VERSION_CODES.O)
+ public fun build(): OAuthRequest {
+ val requestUrl = composeRequestUrl()
+ checkValidity(requestUrl)
+ return OAuthRequest(packageName, requestUrl)
+ }
+
+ // check the validity of the request for the OAuth2 flow with PKCE
+ private fun checkValidity(requestUrl: Uri) {
+ // check that client_id is provided in the request
+ queryParameterCheck(requestUrl, "client_id", null)
+ // check that code_challenge is provided in the request
+ queryParameterCheck(requestUrl, "code_challenge", null)
+ // check that code_challenge_mode is provided in the request, and with value 'S256'
+ queryParameterCheck(requestUrl, "code_challenge_method", "S256")
+ // check that response_type is provided in the request, and with value 'code'
+ queryParameterCheck(requestUrl, "response_type", "code")
+ }
+
+ private fun queryParameterCheck(
+ requestUrl: Uri,
+ queryKey: String,
+ expectedQueryParameter: String?
+ ) {
+ val queryParam = requestUrl.getQueryParameter(queryKey)
+ require(queryParam != null) {
+ "The use of Proof Key for Code Exchange is required for authentication, " +
+ "please provide $queryKey in the request."
+ }
+ expectedQueryParameter?.let {
+ require(queryParam == expectedQueryParameter) {
+ "The use of Proof Key for Code Exchange is required for authentication, " +
+ "the query parameter '$queryKey' is expected to have value of " +
+ "'$expectedQueryParameter', but '$queryParam' is set"
+ }
+ }
+ }
+ }
+
+ /** Get the package name of the app that send the auth request */
+ public fun getPackageName(): String = packageName
+
+ /**
+ * Get the Url of the auth request.
+ * The request is expected to craft a URL something like:
+ * https://authorization-server.com/auth?client_id=XXXXX
+ * &redirect_uri=https://wear.googleapis.com/3p_auth/mypackagename
+ * &response_type=code
+ * &code_challenge=XXXXX...XXX
+ * &code_challenge_method=S256
+ */
+ public fun getRequestUrl(): Uri = requestUrl
+
+ internal fun toBundle(): Bundle = Bundle().apply {
+ putParcelable(RemoteAuthClient.KEY_REQUEST_URL, requestUrl)
+ putString(RemoteAuthClient.KEY_PACKAGE_NAME, packageName)
+ }
+}
diff --git a/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/authentication/OAuthResponse.kt b/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/authentication/OAuthResponse.kt
new file mode 100644
index 0000000..7f0e5b4
--- /dev/null
+++ b/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/authentication/OAuthResponse.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.phone.interactions.authentication
+
+import android.net.Uri
+
+/**
+ * The authentication response to be sent back to the client after completing the OAuth2 flow.
+ */
+public class OAuthResponse internal constructor(
+ @RemoteAuthClient.Companion.ErrorCode private val errorCode: Int = RemoteAuthClient.NO_ERROR,
+ private val responseUrl: Uri?
+) {
+ /**
+ * Builder for constructing new instance of authentication response.
+ */
+ public class Builder {
+ @RemoteAuthClient.Companion.ErrorCode
+ private var errorCode: Int = RemoteAuthClient.NO_ERROR
+ private var responseUrl: Uri? = null
+
+ /** Set the error code to indicate the request result status */
+ public fun setErrorCode(@RemoteAuthClient.Companion.ErrorCode errorCode: Int): Builder =
+ this.apply {
+ this.errorCode = errorCode
+ }
+
+ /** Set the Url of the auth response */
+ public fun setResponseUrl(responseUrl: Uri): Builder = this.apply {
+ this.responseUrl = responseUrl
+ }
+
+ /** Build the response instance specified by this builder*/
+ public fun build(): OAuthResponse = OAuthResponse(errorCode, responseUrl)
+ }
+
+ /** get the error code that indicated the request result status */
+ @RemoteAuthClient.Companion.ErrorCode
+ public fun getErrorCode(): Int = errorCode
+
+ /** get the Url of the auth response */
+ public fun getResponseUrl(): Uri? = responseUrl
+}
\ No newline at end of file
diff --git a/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/authentication/RemoteAuthClient.kt b/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/authentication/RemoteAuthClient.kt
new file mode 100644
index 0000000..e1c5d72
--- /dev/null
+++ b/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/authentication/RemoteAuthClient.kt
@@ -0,0 +1,374 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.phone.interactions.authentication
+
+import android.annotation.SuppressLint
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.ServiceConnection
+import android.net.Uri
+import android.os.Bundle
+import android.os.Handler
+import android.os.IBinder
+import android.support.wearable.authentication.IAuthenticationRequestCallback
+import android.support.wearable.authentication.IAuthenticationRequestService
+import androidx.annotation.IntDef
+import androidx.annotation.UiThread
+import java.util.ArrayDeque
+import java.util.Queue
+import java.util.concurrent.Executor
+
+/**
+ * Provides a client for supporting remote authentication on Wear. The authentication session
+ * will be opened on the user's paired phone.
+ *
+ * * The following example triggers an authorization session to open on the phone.
+ * ```
+ * // PKCE (Proof Key for Code Exchange) is required for the auth
+ * private var codeVerifier: CodeVerifier
+ * private var authClient: RemoteAuthClient
+ *
+ * override public fun onCreate(b: Bundle) {
+ * super.onCreate(b);
+ * authClient = RemoteAuthClient.create(this);
+ * ...
+ * }
+ *
+ * override public fun onDestroy() {
+ * authClient.close();
+ * super.onDestroy();
+ * }
+ *
+ * public fun startAuthFlow() {
+ * // PKCE (Proof Key for Code Exchange) is required, store this code verifier here .
+ * // To access the resource later, both the auth token ans code verifier are needed.
+ * codeVerifier = CodeVerifier()
+ *
+ * // Construct your auth request.
+ * authClient.sendAuthorizationRequest(
+ * OAuthRequest.Builder(this.applicationContext.packageName)
+ * .setAuthProviderUrl(Uri.parse("https://...."))
+ * .setCodeChallenge(CodeChallenge(codeVerifier))
+ * .build(),
+ * new MyAuthCallback()
+ * );
+ * }
+ *
+ * private class MyAuthCallback: RemoteAuthClient.Callback {
+ * override public fun onAuthorizationResponse(
+ * request: OAuthRequest,
+ * response: OAuthResponse
+ * ) {
+ * // Parse the result token out of the response and store it, e.g. in SharedPreferences,
+ * // so you can use it later (Note, use together with code verifier from version R)
+ * // You'll also want to display a success UI.
+ * ...
+ * }
+ *
+ * override public fun onAuthorizationError(errorCode: int) {
+ * // Compare against codes available in RemoteAuthClient.ErrorCode
+ * // You'll also want to display an error UI.
+ * ...
+ * }
+ * }
+ * ```
+ */
+public class RemoteAuthClient internal constructor(
+ private val serviceBinder: ServiceBinder,
+ private val uiThreadExecutor: Executor,
+ private val packageName: String
+) : AutoCloseable {
+ public companion object {
+ /**
+ * The URL to be opened in a web browser on the companion.
+ * Value type: Uri
+ */
+ internal const val KEY_REQUEST_URL: String = "requestUrl"
+
+ /**
+ * The package name obtained from calling getPackageName() on the context passed into
+ * [.create].
+ * Value type: String
+ */
+ internal const val KEY_PACKAGE_NAME: String = "packageName"
+
+ /**
+ * The URL that the web browser is directed to that triggered the companion to open.
+ * Value type: Uri
+ */
+ internal const val KEY_RESPONSE_URL: String = "responseUrl"
+
+ /**
+ * The error code explaining why the request failed.
+ * Value type: [.ErrorCode]
+ */
+ internal const val KEY_ERROR_CODE: String = "errorCode"
+
+ /**
+ * Package name for the service provider on Wearable.
+ * Home app for Wear 2, and Wear Core Service for wear 3
+ */
+ internal const val WEARABLE_PACKAGE_NAME: String = "com.google.android.wearable.app"
+
+ /**
+ * Triggering a service that will prompt a user for authorization credential on the phone
+ * For backwards compatibility, leave this action name as "OAUTH", so 3p app using this new
+ * androidx class can still send request to the service in clockwork home with WSL.
+ */
+ internal const val ACTION_AUTH: String =
+ "android.support.wearable.authentication.action.OAUTH"
+
+ /** Indicates 3p authentication is finished without error */
+ public const val NO_ERROR: Int = 0
+
+ /** Indicates 3p authentication isn't supported by Wear OS */
+ public const val ERROR_UNSUPPORTED: Int = 1
+
+ /** Indicates no phone is connected, or the phone connected doesn't support 3p auth */
+ public const val ERROR_PHONE_UNAVAILABLE: Int = 2
+
+ /** Errors returned in [.Callback.onAuthorizationError]. */
+ @Retention(AnnotationRetention.SOURCE)
+ @IntDef(NO_ERROR, ERROR_UNSUPPORTED, ERROR_PHONE_UNAVAILABLE)
+ internal annotation class ErrorCode
+
+ /** service connection status */
+ private const val STATE_DISCONNECTED: Int = 0
+ private const val STATE_CONNECTING: Int = 1
+ private const val STATE_CONNECTED: Int = 2
+
+ /** Return a client that can be used to make async remote authorization requests */
+ @JvmStatic
+ public fun create(context: Context): RemoteAuthClient {
+ val appContext: Context = context.applicationContext
+ return RemoteAuthClient(
+ object : ServiceBinder {
+ override fun bindService(
+ intent: Intent?,
+ connection: ServiceConnection?,
+ flags: Int
+ ): Boolean {
+ return appContext.bindService(intent, connection!!, flags)
+ }
+
+ override fun unbindService(connection: ServiceConnection?) {
+ appContext.unbindService(connection!!)
+ }
+ },
+ { command -> Handler(appContext.mainLooper).post(command) },
+ context.packageName
+ )
+ }
+ }
+
+ private var allocationSite: Throwable? =
+ Throwable("Explicit termination method 'close' not called")
+ private var connectionState: Int = STATE_DISCONNECTED
+ private var service: IAuthenticationRequestService? = null
+ private val outstandingRequests: MutableSet<RequestCallback> = HashSet()
+ private val queuedRunnables: Queue<Runnable> = ArrayDeque()
+ private val connection: ServiceConnection = RemoteAuthConnection()
+
+ /**
+ * This callback is notified when an async remote authentication request completes.
+ *
+ * Typically, your app should update its UI to let the user aware of the success or failure.
+ */
+ public abstract class Callback {
+
+ /**
+ * Called when an async remote authentication request completes successfully.
+ *
+ * see [.sendAuthorizationRequest]
+ */
+ @UiThread
+ public abstract fun onAuthorizationResponse(request: OAuthRequest, response: OAuthResponse)
+
+ /**
+ * Called when an async remote authentication request fails.
+ *
+ * see [.sendAuthorizationRequest]
+ */
+ @UiThread
+ public abstract fun onAuthorizationError(@ErrorCode errorCode: Int)
+ }
+
+ /**
+ * Send a remote auth request. This will cause an authorization UI to be presented on
+ * the user's phone.
+ * This request is asynchronous; the callback provided will be be notified when the request
+ * completes.
+ *
+ * @param request Request that will be sent to the phone. The auth response should redirect
+ * to the Wear OS companion. See [.WEAR_REDIRECT_URL_PREFIX]
+ *
+ * @Throws RuntimeException if the service has error to open the request
+ */
+ @UiThread
+ @SuppressLint("ExecutorRegistration")
+ public fun sendAuthorizationRequest(request: OAuthRequest, clientCallback: Callback) {
+ require(packageName == request.getPackageName()) {
+ "The request's package name is different from the auth client's package name."
+ }
+
+ if (connectionState == STATE_DISCONNECTED) {
+ connect()
+ }
+ whenConnected(
+ Runnable {
+ val callback = RequestCallback(request, clientCallback)
+ outstandingRequests.add(callback)
+ try {
+ service!!.openUrl(request.toBundle(), callback)
+ } catch (e: Exception) {
+ removePendingCallback(callback)
+ throw RuntimeException(e)
+ }
+ }
+ )
+ }
+
+ /**
+ * Check that the explicit termination method 'close' is called
+ *
+ * @Throws RuntimeException if the 'close' method was not called
+ */
+ protected fun finalize() {
+ if (allocationSite != null) {
+ throw RuntimeException(
+ "A RemoteAuthClient was acquired at the attached stack trace but never released" +
+ " Call RemoteAuthClient.close()"
+ )
+ }
+ }
+
+ /**
+ * Frees any resources used by the client, dropping any outstanding requests. The client
+ * cannot be used to make requests thereafter.
+ */
+ @UiThread
+ override fun close() {
+ allocationSite = null
+ queuedRunnables.clear()
+ outstandingRequests.clear()
+ disconnect()
+ }
+
+ internal interface ServiceBinder {
+ /** See [Context.bindService]. */
+ fun bindService(intent: Intent?, connection: ServiceConnection?, flags: Int): Boolean
+
+ /** See [Context.unbindService]. */
+ fun unbindService(connection: ServiceConnection?)
+ }
+
+ /**
+ * Runs the given runnable immediately if already connected, or queues it for later if a
+ * connection has not yet been fully established.
+ */
+ private fun whenConnected(runnable: Runnable) {
+ if (connectionState == STATE_CONNECTED) {
+ runnable.run()
+ } else {
+ queuedRunnables.add(runnable)
+ }
+ }
+
+ private fun removePendingCallback(requestCallback: RequestCallback) {
+ outstandingRequests.remove(requestCallback)
+ if (outstandingRequests.isEmpty() && service != null) {
+ disconnect()
+ }
+ }
+
+ private fun connect() {
+ check(connectionState == STATE_DISCONNECTED) { "State is $connectionState" }
+ val intent =
+ Intent(ACTION_AUTH).setPackage(WEARABLE_PACKAGE_NAME)
+ val success: Boolean =
+ serviceBinder.bindService(intent, connection, Context.BIND_AUTO_CREATE)
+ if (success) {
+ connectionState = STATE_CONNECTING
+ } else {
+ throw RuntimeException("Failed to bind to Auth service")
+ }
+ }
+
+ private fun disconnect() {
+ if (connectionState != STATE_DISCONNECTED) {
+ serviceBinder.unbindService(connection)
+ service = null
+ connectionState = STATE_DISCONNECTED
+ }
+ }
+
+ /** Receives results of async requests to the remote auth service. */
+ internal inner class RequestCallback internal constructor(
+ private val request: OAuthRequest,
+ private val clientCallback: Callback
+ ) : IAuthenticationRequestCallback.Stub() {
+
+ override fun getApiVersion(): Int = IAuthenticationRequestCallback.API_VERSION
+
+ /**
+ * Called when an aync remote authentication request is completed.
+ *
+ * Bundle contents:
+ * <ul><li>"responseUrl": the response URL from the Auth request (Uri)
+ * <ul><li>"error": an error code explaining why the request failed (int)
+ */
+ override fun onResult(result: Bundle) {
+ val errorCode = result.getInt(KEY_ERROR_CODE, NO_ERROR)
+ val responseUrl: Uri? = result.getParcelable(KEY_RESPONSE_URL)
+ onResult(OAuthResponse(errorCode, responseUrl))
+ }
+
+ @SuppressLint("SyntheticAccessor")
+ internal fun onResult(response: OAuthResponse) {
+ @ErrorCode val error = response.getErrorCode()
+ uiThreadExecutor.execute(
+ Runnable {
+ removePendingCallback(this@RequestCallback)
+ if (error == NO_ERROR) {
+ clientCallback.onAuthorizationResponse(request, response)
+ } else {
+ clientCallback.onAuthorizationError(response.getErrorCode())
+ }
+ }
+ )
+ }
+ }
+
+ /** Manages the connection with Wearable Auth service. */
+ private inner class RemoteAuthConnection : ServiceConnection {
+ @UiThread
+ override fun onServiceConnected(name: ComponentName, boundService: IBinder) {
+ service = IAuthenticationRequestService.Stub.asInterface(boundService)
+ connectionState = STATE_CONNECTED
+ // Run all queued runnables
+ while (!queuedRunnables.isEmpty()) {
+ queuedRunnables.poll()!!.run()
+ }
+ }
+
+ @UiThread
+ override fun onServiceDisconnected(name: ComponentName) {
+ service = null
+ }
+ }
+}
\ No newline at end of file
diff --git a/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/authentication/RemoteAuthService.kt b/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/authentication/RemoteAuthService.kt
new file mode 100644
index 0000000..747bb43
--- /dev/null
+++ b/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/authentication/RemoteAuthService.kt
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.phone.interactions.authentication
+
+import android.annotation.SuppressLint
+import android.app.Service
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import android.os.Binder
+import android.os.Bundle
+import android.os.IBinder
+import android.os.RemoteException
+import android.support.wearable.authentication.IAuthenticationRequestCallback
+import android.support.wearable.authentication.IAuthenticationRequestService
+import java.security.SecureRandom
+
+/**
+ * Interface for specifying how the service handles the remote auth requests.
+ */
+public interface RemoteAuthRequestHandler {
+
+ /**
+ * Whether the auth service is enabled, return false would give an early out by sending the
+ * 3p app a response with error code of ERROR_UNSUPPORTED
+ */
+ public fun isAuthSupported(): Boolean
+
+ /**
+ * Handle the auth request by sending it to the phone.
+ * Typically, if the paired phone is not connected, send a response with error code of
+ * ERROR_PHONE_UNAVAILABLE; otherwise listening for the response from the phone and send it
+ * back to the 3p app.
+ *
+ * [RemoteAuthService.sendResponseToCallback] is provided for sending response back to the
+ * callback provided by the 3p app.
+ *
+ */
+ public fun sendAuthRequest(
+ request: OAuthRequest,
+ packageNameAndRequestId: Pair<String, Int>
+ )
+}
+
+/*
+ * Extend this service class to trigger the handling of the remote auth requests, the
+ * RemoteAuthRequestHandler is specified when the service is bound, typically:
+ *
+ * class AuthenticationService : RemoteAuthService {
+ * override fun onBind(intent: Intent): IBinder {
+ * return onBind(
+ * intent,
+ * object : RemoteAuthRequestHandler {
+ * override fun isAuthSupported(): Boolean {...}
+ * override fun sendAuthRequest(...) {
+ * ...
+ * sendResponseToCallback(...)
+ * }
+ * })
+ * }
+ * }
+ */
+public abstract class RemoteAuthService : Service() {
+
+ public companion object {
+ @JvmStatic
+ private val callbacksByPackageNameAndRequestID:
+ MutableMap<Pair<String, Int>, RemoteAuthClient.RequestCallback> = HashMap()
+
+ /**
+ * To be called by the child class to invoke the callback with Response
+ */
+ @SuppressLint("DocumentExceptions")
+ @JvmStatic
+ public fun sendResponseToCallback(
+ response: OAuthResponse,
+ packageNameAndRequestId: Pair<String, Int>
+ ) {
+ try {
+ callbacksByPackageNameAndRequestID[packageNameAndRequestId]?.onResult(response)
+ callbacksByPackageNameAndRequestID.remove(packageNameAndRequestId)
+ } catch (e: RemoteException) {
+ throw e.cause!!
+ }
+ }
+
+ internal fun getCallback(packageNameAndRequestId: Pair<String, Int>):
+ RemoteAuthClient.RequestCallback? =
+ callbacksByPackageNameAndRequestID[packageNameAndRequestId]
+ }
+
+ private val secureRandom: SecureRandom = SecureRandom()
+
+ /**
+ * To be called by child class when implementing the [Service.onBind], provide the
+ * RemoteAuthRequestHandler and return the IBinder.
+ */
+ protected fun onBind(
+ @Suppress("UNUSED_PARAMETER") intent: Intent,
+ remoteAuthRequestHandler: RemoteAuthRequestHandler
+ ): IBinder = RemoteAuthServiceBinder(this, remoteAuthRequestHandler)
+
+ /**
+ * Implementation of [Service.onUnbind]
+ */
+ public override fun onUnbind(intent: Intent): Boolean {
+ callbacksByPackageNameAndRequestID.clear()
+ return super.onUnbind(intent)
+ }
+
+ /**
+ * Allow the child class to override the default behavior of the package name verification.
+ *
+ * By default, we check the request's package name belongs to the requester's UID.
+ */
+ protected open fun verifyPackageName(context: Context, requestPackageName: String?): Boolean {
+ val packagesForUID: Array<String>? =
+ context.packageManager.getPackagesForUid(Binder.getCallingUid())
+ return !(
+ requestPackageName.isNullOrEmpty() ||
+ packagesForUID.isNullOrEmpty() ||
+ !(packagesForUID.contains(requestPackageName))
+ )
+ }
+
+ internal inner class RemoteAuthServiceBinder(
+ private val context: Context,
+ private val remoteAuthRequestHandler: RemoteAuthRequestHandler
+ ) : IAuthenticationRequestService.Stub() {
+
+ override fun getApiVersion(): Int = IAuthenticationRequestService.API_VERSION
+
+ /**
+ * @throws SecurityException
+ */
+ override fun openUrl(
+ request: Bundle,
+ authenticationRequestCallback: IAuthenticationRequestCallback
+ ) {
+ if (remoteAuthRequestHandler.isAuthSupported()) {
+ val packageName = request.getString(RemoteAuthClient.KEY_PACKAGE_NAME)
+ if (!verifyPackageName(context, packageName)) {
+ throw SecurityException("Failed to verify the Requester's package name")
+ }
+
+ val packageNameAndRequestId = Pair(packageName!!, secureRandom.nextInt())
+ callbacksByPackageNameAndRequestID[packageNameAndRequestId] =
+ authenticationRequestCallback as RemoteAuthClient.RequestCallback
+
+ val requestUrl: Uri? = request.getParcelable(RemoteAuthClient.KEY_REQUEST_URL)
+ remoteAuthRequestHandler.sendAuthRequest(
+ OAuthRequest(packageName, requestUrl!!),
+ packageNameAndRequestId
+ )
+ } else {
+ (authenticationRequestCallback as RemoteAuthClient.RequestCallback).onResult(
+ OAuthResponse.Builder()
+ .setErrorCode(RemoteAuthClient.ERROR_UNSUPPORTED).build()
+ )
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/wear/wear-phone-interactions/src/test/java/androidx/wear/phone/interactions/WearPhoneInteractionsTestRunner.kt b/wear/wear-phone-interactions/src/test/java/androidx/wear/phone/interactions/WearPhoneInteractionsTestRunner.kt
index 3ade521..fdff318 100644
--- a/wear/wear-phone-interactions/src/test/java/androidx/wear/phone/interactions/WearPhoneInteractionsTestRunner.kt
+++ b/wear/wear-phone-interactions/src/test/java/androidx/wear/phone/interactions/WearPhoneInteractionsTestRunner.kt
@@ -29,11 +29,13 @@
* inline classes. We don't need shadowing of our classes because we want to use the actual
* objects in our tests.
*/
-class WearPhoneInteractionsTestRunner(testClass: Class<*>) : RobolectricTestRunner(testClass) {
+internal class WearPhoneInteractionsTestRunner(
+ testClass: Class<*>
+) : RobolectricTestRunner(testClass) {
override fun createClassLoaderConfig(method: FrameworkMethod): InstrumentationConfiguration =
InstrumentationConfiguration.Builder(
super.createClassLoaderConfig(method)
)
.doNotInstrumentPackage("androidx.wear.phone.interactions")
.build()
-}
\ No newline at end of file
+}
diff --git a/wear/wear-phone-interactions/src/test/java/androidx/wear/phone/interactions/authentication/CodeVerifierCodeChallengeTest.kt b/wear/wear-phone-interactions/src/test/java/androidx/wear/phone/interactions/authentication/CodeVerifierCodeChallengeTest.kt
new file mode 100644
index 0000000..7fb442d
--- /dev/null
+++ b/wear/wear-phone-interactions/src/test/java/androidx/wear/phone/interactions/authentication/CodeVerifierCodeChallengeTest.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.phone.interactions.authentication
+
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Assert.fail
+import org.junit.Test
+
+/** Unit tests for [CodeVerifier] and [CodeChallenge] */
+public class CodeVerifierCodeChallengeTest {
+ @Test
+ public fun testVerifierDefaultConstructor() {
+ val verifier = CodeVerifier()
+ assertEquals(43, verifier.getValue().length)
+ }
+
+ @Test
+ public fun testVerifierConstructor() {
+ val verifier = CodeVerifier(96)
+ assertEquals(128, verifier.getValue().length)
+ }
+
+ @Test
+ public fun testVerifierConstructorInvalidParam() {
+ try {
+ CodeVerifier(100)
+ fail("should fail due to verifier over the required length")
+ } catch (e: Exception) {
+ // Expected
+ }
+ }
+
+ @Test
+ public fun testVerifierEquality() {
+ val verifier = CodeVerifier()
+ assertTrue(verifier.equals(CodeVerifier(verifier.getValue())))
+ }
+
+ @Test
+ public fun testVerifierInequality() {
+ assertFalse(CodeVerifier().equals(CodeVerifier()))
+ assertFalse(CodeVerifier(50).equals(CodeVerifier(50)))
+ assertFalse(CodeVerifier().equals(null))
+ }
+
+ @Test
+ public fun testChallengeTransformS256() {
+ // see https://tools.ietf.org/html/rfc7636#appendix-A
+ val verifier = CodeVerifier("dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk")
+ val challenge = CodeChallenge(verifier)
+ assertEquals("E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM", challenge.getValue())
+ }
+
+ @Test
+ public fun testChallengeEquality() {
+ val verifierValue = "jdshfkshg-8973834_SDFSSGE"
+ assertTrue(
+ CodeChallenge(CodeVerifier(verifierValue)).equals(
+ CodeChallenge(CodeVerifier(verifierValue))
+ )
+ )
+ }
+
+ @Test
+ public fun testChallengeInequality() {
+ assertFalse(CodeChallenge(CodeVerifier()).equals(CodeChallenge(CodeVerifier())))
+ assertFalse(CodeChallenge(CodeVerifier()).equals(null))
+ }
+}
\ No newline at end of file
diff --git a/wear/wear-phone-interactions/src/test/java/androidx/wear/phone/interactions/authentication/OAuthRequestResponseTest.kt b/wear/wear-phone-interactions/src/test/java/androidx/wear/phone/interactions/authentication/OAuthRequestResponseTest.kt
new file mode 100644
index 0000000..cbbaa46
--- /dev/null
+++ b/wear/wear-phone-interactions/src/test/java/androidx/wear/phone/interactions/authentication/OAuthRequestResponseTest.kt
@@ -0,0 +1,363 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.phone.interactions.authentication
+
+import android.net.Uri
+import androidx.wear.phone.interactions.WearPhoneInteractionsTestRunner
+import org.junit.Assert.assertEquals
+import org.junit.Assert.fail
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.annotation.internal.DoNotInstrument
+
+/** Unit tests for [OAuthRequest] and [OAuthResponse] */
+@RunWith(WearPhoneInteractionsTestRunner::class)
+@DoNotInstrument
+public class OAuthRequestResponseTest {
+ internal companion object {
+ private const val authProviderUrl = "http://account.myapp.com/auth"
+ private const val clientId = "iamtheclient"
+ private const val appPackageName = "com.friendlyapp"
+ private const val redirectUrl = OAuthRequest.WEAR_REDIRECT_URL_PREFIX
+ private const val redirectUrlWithPackageName = "$redirectUrl$appPackageName"
+ private const val customRedirectUrl = "https://app.example.com/oauth2redirect"
+ private const val customRedirectUrlWithPackageName = "$customRedirectUrl/$appPackageName"
+ private val responseUrl = Uri.parse("http://myresponseurl")
+ }
+
+ private fun checkBuildSuccess(
+ builder: OAuthRequest.Builder,
+ expectedAuthProviderUrl: String,
+ expectedClientId: String,
+ expectedRedirectUri: String,
+ expectedCodeChallenge: String,
+ ) {
+ var request: OAuthRequest? = null
+ try {
+ request = builder.build()
+ } catch (e: Exception) {
+ fail("The build shall succeed and this line will not be executed")
+ }
+
+ val requestUrl = request!!.getRequestUrl()
+ assertEquals(requestUrl.toString().indexOf(expectedAuthProviderUrl), 0)
+ assertEquals(requestUrl.getQueryParameter("redirect_uri"), expectedRedirectUri)
+ assertEquals(requestUrl.getQueryParameter("client_id"), expectedClientId)
+ assertEquals(requestUrl.getQueryParameter("response_type"), "code")
+ assertEquals(requestUrl.getQueryParameter("code_challenge"), expectedCodeChallenge)
+ assertEquals(requestUrl.getQueryParameter("code_challenge_method"), "S256")
+ }
+
+ private fun checkBuildFailure(builder: OAuthRequest.Builder, errorMsg: String) {
+ try {
+ builder.build()
+ fail("should fail without providing correct/adequate info for building request")
+ } catch (e: Exception) {
+ assertEquals(errorMsg, e.message)
+ }
+ }
+
+ @Test
+ public fun testRequestBuildScuccessWithSetters() {
+ val codeChallenge = CodeChallenge(CodeVerifier())
+ val requestBuilder = OAuthRequest.Builder(appPackageName)
+ .setAuthProviderUrl(authProviderUrl = Uri.parse(authProviderUrl))
+ .setClientId(clientId)
+ .setCodeChallenge(codeChallenge)
+
+ checkBuildSuccess(
+ requestBuilder,
+ authProviderUrl,
+ clientId,
+ redirectUrlWithPackageName,
+ codeChallenge.getValue()
+ )
+ }
+
+ @Test
+ public fun testRequestBuildScuccessWithCustomRedirectUti() {
+ val codeChallenge = CodeChallenge(CodeVerifier())
+ val requestBuilder = OAuthRequest.Builder(appPackageName)
+ .setAuthProviderUrl(authProviderUrl = Uri.parse(authProviderUrl))
+ .setClientId(clientId)
+ .setRedirectUrl(Uri.parse(customRedirectUrl))
+ .setCodeChallenge(codeChallenge)
+
+ checkBuildSuccess(
+ requestBuilder,
+ authProviderUrl,
+ clientId,
+ customRedirectUrlWithPackageName,
+ codeChallenge.getValue()
+ )
+ }
+
+ @Test
+ public fun testRequestBuildSuccessWithCompleteUrl() {
+ val codeChallenge = CodeChallenge(CodeVerifier())
+ val requestBuilder = OAuthRequest.Builder(appPackageName)
+ .setAuthProviderUrl(
+ authProviderUrl = Uri.parse(
+ "$authProviderUrl?client_id=$clientId" +
+ "&redirect_uri=$redirectUrlWithPackageName" +
+ "&response_type=code" +
+ "&code_challenge=${codeChallenge.getValue()}" +
+ "&code_challenge_method=S256"
+ )
+ )
+
+ checkBuildSuccess(
+ requestBuilder,
+ authProviderUrl,
+ clientId,
+ redirectUrlWithPackageName,
+ codeChallenge.getValue()
+ )
+ }
+
+ @Test
+ public fun testRequestBuildFailureWithoutAuthProviderUrl() {
+ val builder = OAuthRequest.Builder(appPackageName)
+ .setClientId(clientId)
+ .setCodeChallenge(CodeChallenge(CodeVerifier()))
+
+ checkBuildFailure(
+ builder,
+ "The request requires the auth provider url to be provided."
+ )
+ }
+
+ @Test
+ public fun testRequestBuildFailureWithoutClientId() {
+ val builder = OAuthRequest.Builder(appPackageName)
+ .setAuthProviderUrl(authProviderUrl = Uri.parse(authProviderUrl))
+ .setCodeChallenge(CodeChallenge(CodeVerifier()))
+
+ checkBuildFailure(
+ builder,
+ "The use of Proof Key for Code Exchange is required for authentication, " +
+ "please provide client_id in the request."
+ )
+ }
+
+ @Test
+ public fun testRequestBuildFailureWithConflictedClientId() {
+ val builder = OAuthRequest.Builder(appPackageName)
+ .setAuthProviderUrl(
+ Uri.parse("$authProviderUrl?client_id=XXX")
+ )
+ .setClientId(clientId)
+ .setCodeChallenge(CodeChallenge(CodeVerifier()))
+
+ checkBuildFailure(
+ builder,
+ "The 'client_id' query param already exists in the authProviderUrl, " +
+ "expect to have the value of '$clientId', but " +
+ "'XXX' is given. Please correct it, or leave it out " +
+ "to allow the request builder to append it automatically."
+ )
+ }
+
+ @Test
+ public fun testRequestBuildSuccessWithDuplicatedClientId() {
+ val codeChallenge = CodeChallenge(CodeVerifier())
+ val builder = OAuthRequest.Builder(appPackageName)
+ .setAuthProviderUrl(
+ Uri.parse("$authProviderUrl?client_id=$clientId")
+ )
+ .setClientId(clientId)
+ .setCodeChallenge(codeChallenge)
+
+ checkBuildSuccess(
+ builder,
+ authProviderUrl,
+ clientId,
+ redirectUrlWithPackageName,
+ codeChallenge.getValue()
+ )
+ }
+
+ @Test
+ public fun testRequestBuildFailureWithoutCodeChallenge() {
+ val builder = OAuthRequest.Builder(appPackageName)
+ .setAuthProviderUrl(authProviderUrl = Uri.parse(authProviderUrl))
+ .setClientId(clientId)
+
+ checkBuildFailure(
+ builder,
+ "The use of Proof Key for Code Exchange is required for authentication, " +
+ "please provide code_challenge in the request."
+ )
+ }
+
+ @Test
+ public fun testRequestBuildFailureWithConflictedCodeChallenge() {
+ val codeChallenge = CodeChallenge(CodeVerifier())
+ val builder = OAuthRequest.Builder(appPackageName)
+ .setAuthProviderUrl(
+ Uri.parse("$authProviderUrl?code_challenge=XXX")
+ )
+ .setClientId(clientId)
+ .setCodeChallenge(codeChallenge)
+
+ checkBuildFailure(
+ builder,
+ "The 'code_challenge' query param already exists in the authProviderUrl, " +
+ "expect to have the value of '${codeChallenge.getValue()}', but " +
+ "'XXX' is given. Please correct it, or leave it out " +
+ "to allow the request builder to append it automatically."
+ )
+ }
+
+ @Test
+ public fun testRequestBuildSuccessWithDuplicatedCodeChallenge() {
+ val codeChallenge = CodeChallenge(CodeVerifier())
+ val builder = OAuthRequest.Builder(appPackageName)
+ .setAuthProviderUrl(
+ Uri.parse("$authProviderUrl?code_challenge=${codeChallenge.getValue()}")
+ )
+ .setClientId(clientId)
+ .setCodeChallenge(codeChallenge)
+
+ checkBuildSuccess(
+ builder,
+ authProviderUrl,
+ clientId,
+ redirectUrlWithPackageName,
+ codeChallenge.getValue()
+ )
+ }
+
+ @Test
+ public fun testRequestBuildFailureWithWrongResponseType() {
+ val builder = OAuthRequest.Builder(appPackageName)
+ .setAuthProviderUrl(
+ Uri.parse("$authProviderUrl?response_type=XXX")
+ )
+ .setClientId(clientId)
+ .setCodeChallenge(CodeChallenge(CodeVerifier()))
+
+ checkBuildFailure(
+ builder,
+ "The 'response_type' query param already exists in the authProviderUrl, " +
+ "expect to have the value of 'code', but " +
+ "'XXX' is given. Please correct it, or leave it out " +
+ "to allow the request builder to append it automatically."
+ )
+ }
+
+ @Test
+ public fun testRequestBuildFailureWithDuplicatedResponseType() {
+ val codeChallenge = CodeChallenge(CodeVerifier())
+ val builder = OAuthRequest.Builder(appPackageName)
+ .setAuthProviderUrl(
+ Uri.parse("$authProviderUrl?response_type=code")
+ )
+ .setClientId(clientId)
+ .setCodeChallenge(codeChallenge)
+
+ checkBuildSuccess(
+ builder,
+ authProviderUrl,
+ clientId,
+ redirectUrlWithPackageName,
+ codeChallenge.getValue()
+ )
+ }
+
+ @Test
+ public fun testRequestBuildFailureWithWrongCodeChallengeMethod() {
+ val builder = OAuthRequest.Builder(appPackageName)
+ .setAuthProviderUrl(
+ Uri.parse("$authProviderUrl?code_challenge_method=PLAIN")
+ )
+ .setClientId(clientId)
+ .setCodeChallenge(CodeChallenge(CodeVerifier()))
+
+ checkBuildFailure(
+ builder,
+ "The 'code_challenge_method' query param already exists in the authProviderUrl, " +
+ "expect to have the value of 'S256', but " +
+ "'PLAIN' is given. Please correct it, or leave it out " +
+ "to allow the request builder to append it automatically."
+ )
+ }
+
+ @Test
+ public fun testRequestBuildFailureWithDuplicatedCodeChallengeMethod() {
+ val codeChallenge = CodeChallenge(CodeVerifier())
+ val builder = OAuthRequest.Builder(appPackageName)
+ .setAuthProviderUrl(
+ Uri.parse("$authProviderUrl?code_challenge_method=S256")
+ )
+ .setClientId(clientId)
+ .setCodeChallenge(codeChallenge)
+
+ checkBuildSuccess(
+ builder,
+ authProviderUrl,
+ clientId,
+ redirectUrlWithPackageName,
+ codeChallenge.getValue()
+ )
+ }
+
+ @Test
+ public fun testRequestBuildFailureWithConflictedRedirectUri() {
+ val codeChallenge = CodeChallenge(CodeVerifier())
+ val builder = OAuthRequest.Builder(appPackageName)
+ .setAuthProviderUrl(
+ Uri.parse("$authProviderUrl?redirect_uri=$redirectUrlWithPackageName")
+ )
+ .setRedirectUrl(Uri.parse(customRedirectUrl))
+ .setClientId(clientId)
+ .setCodeChallenge(codeChallenge)
+
+ checkBuildFailure(
+ builder,
+ "The 'redirect_uri' query param already exists in the authProviderUrl, " +
+ "expect to have the value of '$customRedirectUrlWithPackageName', but " +
+ "'$redirectUrlWithPackageName' is given. Please correct it, or leave it out " +
+ "to allow the request builder to append it automatically."
+ )
+ }
+
+ @Test
+ public fun testNoErrorResponseBuild() {
+ val response = OAuthResponse.Builder().setResponseUrl(responseUrl).build()
+
+ assertEquals(RemoteAuthClient.NO_ERROR, response.getErrorCode())
+ assertEquals(responseUrl, response.getResponseUrl())
+ }
+
+ @Test
+ public fun testErrorResponseBuild() {
+ val response1 = OAuthResponse.Builder()
+ .setErrorCode(RemoteAuthClient.ERROR_UNSUPPORTED)
+ .build()
+
+ assertEquals(RemoteAuthClient.ERROR_UNSUPPORTED, response1.getErrorCode())
+ assertEquals(null, response1.getResponseUrl())
+
+ val response2 = OAuthResponse.Builder()
+ .setErrorCode(RemoteAuthClient.ERROR_PHONE_UNAVAILABLE)
+ .build()
+
+ assertEquals(RemoteAuthClient.ERROR_PHONE_UNAVAILABLE, response2.getErrorCode())
+ assertEquals(null, response2.getResponseUrl())
+ }
+}
\ No newline at end of file
diff --git a/wear/wear-phone-interactions/src/test/java/androidx/wear/phone/interactions/authentication/RemoteAuthTest.kt b/wear/wear-phone-interactions/src/test/java/androidx/wear/phone/interactions/authentication/RemoteAuthTest.kt
new file mode 100644
index 0000000..8d8bf82
--- /dev/null
+++ b/wear/wear-phone-interactions/src/test/java/androidx/wear/phone/interactions/authentication/RemoteAuthTest.kt
@@ -0,0 +1,273 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.phone.interactions.authentication
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.ServiceConnection
+import android.net.Uri
+import android.os.IBinder
+import android.os.RemoteException
+import android.util.Pair
+import androidx.wear.phone.interactions.WearPhoneInteractionsTestRunner
+import org.junit.Assert
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito
+import org.robolectric.annotation.internal.DoNotInstrument
+import java.util.ArrayList
+import java.util.concurrent.Executor
+
+/** Unit tests for [RemoteAuthClient]. */
+@RunWith(WearPhoneInteractionsTestRunner::class)
+@DoNotInstrument // Needed because it is defined in the "android" package.
+public class RemoteAuthTest {
+
+ internal companion object {
+ private val DIRECT_EXECUTOR = Executor { command -> command.run() }
+ private const val authProviderUrlA = "http://myrequesturl/a?client_id=iamtheclient"
+ private const val authProviderUrlB = "http://myrequesturl/b?client_id=iamtheclient"
+ private val responseUrl = Uri.parse("http://myresponseurl")
+ private const val appPackageName = "com.friendlyapp"
+ private val requestA =
+ OAuthRequest.Builder(appPackageName)
+ .setAuthProviderUrl(Uri.parse(authProviderUrlA))
+ .setCodeChallenge(CodeChallenge(CodeVerifier()))
+ .build()
+ private val requestB =
+ OAuthRequest.Builder(appPackageName)
+ .setAuthProviderUrl(Uri.parse(authProviderUrlB))
+ .setCodeChallenge(CodeChallenge(CodeVerifier()))
+ .build()
+ private val response =
+ OAuthResponse.Builder().setResponseUrl(responseUrl).build()
+
+ // Note: This can't be static as Robolectric isn't set up at class init time.
+ private val mServiceName = ComponentName(
+ "com.google.android.wearable.app", "auth_lib_shouldnt_care_about_this"
+ )
+ private val mockCallback: RemoteAuthClient.Callback =
+ Mockito.mock(RemoteAuthClient.Callback::class.java)
+ }
+
+ private var fakeServiceBinder: FakeServiceBinder = FakeServiceBinder()
+ private var fakeService: FakeClockworkHomeAuthService = FakeClockworkHomeAuthService()
+ private var clientUnderTest: RemoteAuthClient =
+ RemoteAuthClient(fakeServiceBinder, DIRECT_EXECUTOR, appPackageName)
+
+ @Test
+ public fun doesntConnectUntilARequestIsMade() {
+ // WHEN the client is created
+ // THEN the Auth library should not yet connect to Clockwork Home
+ Assert.assertEquals(ConnectionState.DISCONNECTED, fakeServiceBinder.state)
+ }
+
+ @Test
+ public fun sendAuthorizationRequestShouldMakeConnectionToClockworkHome() {
+ val requestUri = "http://myrequesturl?client_id=xxx"
+ // WHEN an authorization request is sent
+ clientUnderTest.sendAuthorizationRequest(
+ OAuthRequest.Builder(appPackageName)
+ .setAuthProviderUrl(Uri.parse(requestUri))
+ .setCodeChallenge(CodeChallenge(CodeVerifier()))
+ .build(),
+ mockCallback
+ )
+ // THEN a connection is made to Clockwork Home's Auth service
+ Assert.assertEquals(ConnectionState.CONNECTING, fakeServiceBinder.state)
+ }
+
+ @Test
+ public fun sendAuthorizationRequestShouldCallBinderMethod() {
+ // WHEN an authorization request is sent
+ clientUnderTest.sendAuthorizationRequest(requestA, mockCallback)
+ fakeServiceBinder.completeConnection()
+ // THEN a request is made to Clockwork Home
+ val request = fakeService.requests[0]
+ val requestReceived = request.first
+ // THEN the request url is set correctly
+ Assert.assertEquals(
+ requestA.getRequestUrl(),
+ requestReceived.getRequestUrl()
+ )
+ Assert.assertEquals(
+ requestReceived.getRequestUrl().toString().indexOf(authProviderUrlA),
+ 0
+ )
+ }
+
+ @Test
+ public fun twoQueuedAuthorizationRequestsBeforeConnectCompletes() {
+ // GIVEN two authorization requests were made before connecting to Clockwork Home completes
+ clientUnderTest.sendAuthorizationRequest(requestA, mockCallback)
+ clientUnderTest.sendAuthorizationRequest(requestB, mockCallback)
+ // WHEN the connection does complete
+ fakeServiceBinder.completeConnection()
+ // THEN two requests are made to Clockwork Home
+ val requestAReceived = fakeService.requests[0].first
+ val requestBReceived = fakeService.requests[1].first
+ Assert.assertEquals(2, fakeService.requests.size.toLong())
+ // THEN the request url is set correctly for both (A then B)
+ Assert.assertEquals(
+ requestA.getRequestUrl(),
+ requestAReceived.getRequestUrl()
+ )
+ Assert.assertEquals(
+ requestB.getRequestUrl(),
+ requestBReceived.getRequestUrl()
+ )
+ Assert.assertEquals(
+ requestAReceived.getRequestUrl().toString().indexOf(authProviderUrlA),
+ 0
+ )
+ Assert.assertEquals(
+ requestBReceived.getRequestUrl().toString().indexOf(authProviderUrlB),
+ 0
+ )
+ }
+
+ @Test
+ @Throws(RemoteException::class)
+ public fun requestCompletionShouldCallBackToClient() {
+ // GIVEN an authorization request was sent
+ clientUnderTest.sendAuthorizationRequest(requestA, mockCallback)
+ fakeServiceBinder.completeConnection()
+ val request = fakeService.requests[0]
+ // WHEN the request completes
+ // callback supplied earlier is called with the correct request URL and response URL
+ Mockito.verify(mockCallback).onAuthorizationResponse(request.first, response)
+ }
+
+ @Test
+ @Throws(RemoteException::class)
+ public fun doesntDisconnectWhenRequestStillInProgress() {
+ // GIVEN 2 authorization requests were sent
+ clientUnderTest.sendAuthorizationRequest(requestA, mockCallback)
+ // GIVEN the async binding to Clockwork Home completed after the 1st but before the 2nd
+ fakeServiceBinder.completeConnection()
+ clientUnderTest.sendAuthorizationRequest(requestB, mockCallback)
+ // WHEN the first one completes
+ RemoteAuthService.sendResponseToCallback(
+ response,
+ fakeService.requests[0].second
+ )
+ // THEN the service remains connected (as there's still a request ongoing, and we won't get
+ // the callback for the other request if we unbind now)
+ Assert.assertEquals(ConnectionState.CONNECTED, fakeServiceBinder.state)
+ }
+
+ @Test
+ @Throws(RemoteException::class)
+ public fun disconnectsWhenAllRequestsComplete() {
+ // GIVEN 2 authorization requests were sent
+ clientUnderTest.sendAuthorizationRequest(requestA, mockCallback)
+ // GIVEN the async binding to Clockwork Home completed after the 1st but before the 2nd
+ fakeServiceBinder.completeConnection()
+ clientUnderTest.sendAuthorizationRequest(requestB, mockCallback)
+ RemoteAuthService.sendResponseToCallback(
+ response,
+ fakeService.requests[0].second
+ )
+ // WHEN the other completes
+ RemoteAuthService.sendResponseToCallback(
+ response,
+ fakeService.requests[1].second
+ )
+ // THEN the OAuth library disconnects from Clockwork Home
+ Assert.assertEquals(ConnectionState.DISCONNECTED, fakeServiceBinder.state)
+ }
+
+ private enum class ConnectionState {
+ DISCONNECTED, CONNECTING, CONNECTED
+ }
+
+ /** Fakes binding to Clockwork Home. */
+ private inner class FakeServiceBinder : RemoteAuthClient.ServiceBinder {
+ var state = ConnectionState.DISCONNECTED
+ private var serviceConnection: ServiceConnection? = null
+ override fun bindService(
+ intent: Intent?,
+ connection: ServiceConnection?,
+ flags: Int
+ ): Boolean {
+ if (intent!!.getPackage() != RemoteAuthClient.WEARABLE_PACKAGE_NAME) {
+ throw UnsupportedOperationException()
+ }
+ if (intent.action != RemoteAuthClient.ACTION_AUTH) {
+ throw UnsupportedOperationException()
+ }
+ check(state == ConnectionState.DISCONNECTED) { "Already connected or connecting" }
+ state = ConnectionState.CONNECTING
+ serviceConnection = connection
+ return true
+ }
+
+ fun completeConnection() {
+ Assert.assertTrue(state == ConnectionState.CONNECTING)
+ state = ConnectionState.CONNECTED
+ serviceConnection!!.onServiceConnected(mServiceName, fakeService.onBind(Intent()))
+ }
+
+ override fun unbindService(connection: ServiceConnection?) {
+ check(state != ConnectionState.DISCONNECTED) { "Not connected; can't disconnect" }
+ state = ConnectionState.DISCONNECTED
+ serviceConnection = null
+ }
+ }
+
+ /**
+ * Fake implementation of the OAuth service in Clockwork Home. Instead of talking to the user's
+ * phone, this class just records the method calls that were made so we can handle them manually
+ * in our tests.
+ */
+ private inner class FakeClockworkHomeAuthService : RemoteAuthService() {
+ private val requestHandler: RemoteAuthRequestHandler
+
+ val requests: MutableList<Pair<OAuthRequest, kotlin.Pair<String, Int>>> =
+ ArrayList()
+
+ init {
+ requestHandler = AuthenticationRequestHandler()
+ }
+
+ override fun onBind(intent: Intent): IBinder {
+ return onBind(intent, requestHandler)
+ }
+
+ override fun verifyPackageName(context: Context, requestPackageName: String?): Boolean {
+ return true
+ }
+
+ private inner class AuthenticationRequestHandler : RemoteAuthRequestHandler {
+ override fun isAuthSupported(): Boolean {
+ return true
+ }
+
+ override fun sendAuthRequest(
+ request: OAuthRequest,
+ packageNameAndRequestId: kotlin.Pair<String, Int>
+ ) {
+ if (fakeServiceBinder.state != ConnectionState.CONNECTED) {
+ throw RemoteException("not connected")
+ }
+ requests.add(Pair.create(request, packageNameAndRequestId))
+ mockCallback.onAuthorizationResponse(request, response)
+ }
+ }
+ }
+}
diff --git a/wear/wear-phone-interactions/src/test/resources/robolectric.properties b/wear/wear-phone-interactions/src/test/resources/robolectric.properties
index c0066c1..74bb2a9 100644
--- a/wear/wear-phone-interactions/src/test/resources/robolectric.properties
+++ b/wear/wear-phone-interactions/src/test/resources/robolectric.properties
@@ -14,7 +14,7 @@
# limitations under the License.
#
-# Robolectric currently doesn't support API 30, so we have to explicitly the target sdk levels for
-# now.
+# Robolectric currently doesn't support API 30, so we have to explicitly specify the target sdk
+# levels for now.
# TODO(b/177072877): Remove when no longer necessary.
-sdk=25,26,27,28
+sdk=28
diff --git a/wear/wear-remote-interactions/api/current.txt b/wear/wear-remote-interactions/api/current.txt
index f0446b9..0a4a745 100644
--- a/wear/wear-remote-interactions/api/current.txt
+++ b/wear/wear-remote-interactions/api/current.txt
@@ -1,6 +1,14 @@
// Signature format: 4.0
package androidx.wear.remote.interactions {
+ @RequiresApi(android.os.Build.VERSION_CODES.N) public final class PlayStoreAvailability {
+ method public int getPlayStoreAvailabilityOnPhone(android.content.Context context);
+ field public static final androidx.wear.remote.interactions.PlayStoreAvailability INSTANCE;
+ field public static final int PLAY_STORE_AVAILABLE = 1; // 0x1
+ field public static final int PLAY_STORE_ERROR_UNKNOWN = 0; // 0x0
+ field public static final int PLAY_STORE_UNAVAILABLE = 2; // 0x2
+ }
+
public final class WatchFaceConfigIntentHelper {
method public static String? getPeerIdExtra(android.content.Intent watchFaceIntent);
method public static android.content.ComponentName? getWatchFaceComponentExtra(android.content.Intent watchFaceIntent);
diff --git a/wear/wear-remote-interactions/api/public_plus_experimental_current.txt b/wear/wear-remote-interactions/api/public_plus_experimental_current.txt
index f0446b9..0a4a745 100644
--- a/wear/wear-remote-interactions/api/public_plus_experimental_current.txt
+++ b/wear/wear-remote-interactions/api/public_plus_experimental_current.txt
@@ -1,6 +1,14 @@
// Signature format: 4.0
package androidx.wear.remote.interactions {
+ @RequiresApi(android.os.Build.VERSION_CODES.N) public final class PlayStoreAvailability {
+ method public int getPlayStoreAvailabilityOnPhone(android.content.Context context);
+ field public static final androidx.wear.remote.interactions.PlayStoreAvailability INSTANCE;
+ field public static final int PLAY_STORE_AVAILABLE = 1; // 0x1
+ field public static final int PLAY_STORE_ERROR_UNKNOWN = 0; // 0x0
+ field public static final int PLAY_STORE_UNAVAILABLE = 2; // 0x2
+ }
+
public final class WatchFaceConfigIntentHelper {
method public static String? getPeerIdExtra(android.content.Intent watchFaceIntent);
method public static android.content.ComponentName? getWatchFaceComponentExtra(android.content.Intent watchFaceIntent);
diff --git a/wear/wear-remote-interactions/api/restricted_current.txt b/wear/wear-remote-interactions/api/restricted_current.txt
index f0446b9..0a4a745 100644
--- a/wear/wear-remote-interactions/api/restricted_current.txt
+++ b/wear/wear-remote-interactions/api/restricted_current.txt
@@ -1,6 +1,14 @@
// Signature format: 4.0
package androidx.wear.remote.interactions {
+ @RequiresApi(android.os.Build.VERSION_CODES.N) public final class PlayStoreAvailability {
+ method public int getPlayStoreAvailabilityOnPhone(android.content.Context context);
+ field public static final androidx.wear.remote.interactions.PlayStoreAvailability INSTANCE;
+ field public static final int PLAY_STORE_AVAILABLE = 1; // 0x1
+ field public static final int PLAY_STORE_ERROR_UNKNOWN = 0; // 0x0
+ field public static final int PLAY_STORE_UNAVAILABLE = 2; // 0x2
+ }
+
public final class WatchFaceConfigIntentHelper {
method public static String? getPeerIdExtra(android.content.Intent watchFaceIntent);
method public static android.content.ComponentName? getWatchFaceComponentExtra(android.content.Intent watchFaceIntent);
diff --git a/wear/wear-remote-interactions/build.gradle b/wear/wear-remote-interactions/build.gradle
index d61dcea..3b5f727 100644
--- a/wear/wear-remote-interactions/build.gradle
+++ b/wear/wear-remote-interactions/build.gradle
@@ -37,6 +37,12 @@
testImplementation(ANDROIDX_TEST_EXT_JUNIT)
testImplementation(ANDROIDX_TEST_CORE)
testImplementation(ANDROIDX_TEST_RULES)
+ testImplementation(ANDROIDX_TEST_RUNNER)
+ testImplementation(ROBOLECTRIC)
+ testImplementation(MOCKITO_CORE)
+ testImplementation(MOCKITO_KOTLIN)
+
+ implementation(PLAY_SERVICES_BASE)
}
android {
diff --git a/wear/wear-remote-interactions/src/main/java/androidx/wear/remote/interactions/PlayStoreAvailability.kt b/wear/wear-remote-interactions/src/main/java/androidx/wear/remote/interactions/PlayStoreAvailability.kt
new file mode 100644
index 0000000..02f04f9
--- /dev/null
+++ b/wear/wear-remote-interactions/src/main/java/androidx/wear/remote/interactions/PlayStoreAvailability.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.wear.remote.interactions
+
+import android.content.Context
+import android.net.Uri
+import android.os.Build
+import android.provider.Settings
+import androidx.annotation.IntDef
+import androidx.annotation.RequiresApi
+import com.google.android.gms.common.ConnectionResult
+import com.google.android.gms.common.GoogleApiAvailability
+
+/**
+ * Helper class for checking whether the phone paired to a given Wear OS device has the Play Store.
+ */
+@RequiresApi(Build.VERSION_CODES.N)
+public object PlayStoreAvailability {
+ /**
+ * This value means that there was an error in checking for whether the Play Store is available
+ * on the phone.
+ */
+ public const val PLAY_STORE_ERROR_UNKNOWN: Int = 0
+
+ /** This value means that the Play Store is available on the phone. */
+ public const val PLAY_STORE_AVAILABLE: Int = 1
+
+ /** This value means that the Play Store is not available on the phone. */
+ public const val PLAY_STORE_UNAVAILABLE: Int = 2
+
+ private const val PLAY_STORE_AVAILABILITY_PATH = "play_store_availability"
+ internal const val SETTINGS_AUTHORITY_URI = "com.google.android.wearable.settings"
+ internal val PLAY_STORE_AVAILABILITY_URI = Uri.Builder()
+ .scheme("content")
+ .authority(SETTINGS_AUTHORITY_URI)
+ .path(PLAY_STORE_AVAILABILITY_PATH)
+ .build()
+
+ // The name of the row which stores the play store availability setting in versions before R.
+ internal const val KEY_PLAY_STORE_AVAILABILITY = "play_store_availability"
+
+ // The name of the settings value which stores the play store availability setting in versions
+ // from R.
+ private const val SETTINGS_PLAY_STORE_AVAILABILITY = "phone_play_store_availability"
+
+ internal const val SYSTEM_FEATURE_WATCH: String = "android.hardware.type.watch"
+
+ /**
+ * Returns whether the Play Store is available on the Phone. If
+ * [PLAY_STORE_ERROR_UNKNOWN] is returned, the caller should try again later. This
+ * method should not be run on the main thread.
+ *
+ * @return One of three values: [PLAY_STORE_AVAILABLE],
+ * [PLAY_STORE_UNAVAILABLE], or [PLAY_STORE_ERROR_UNKNOWN].
+ */
+ @PlayStoreStatus
+ public fun getPlayStoreAvailabilityOnPhone(context: Context): Int {
+ val isCurrentDeviceAWatch = context.packageManager.hasSystemFeature(
+ SYSTEM_FEATURE_WATCH
+ )
+
+ if (!isCurrentDeviceAWatch) {
+ val isPlayServiceAvailable =
+ GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context)
+ return if (isPlayServiceAvailable == ConnectionResult.SUCCESS) PLAY_STORE_AVAILABLE
+ else PLAY_STORE_UNAVAILABLE
+ }
+
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
+ context.contentResolver.query(
+ PLAY_STORE_AVAILABILITY_URI, null, null, null,
+ null
+ )?.use { cursor ->
+ while (cursor.moveToNext()) {
+ if (KEY_PLAY_STORE_AVAILABILITY == cursor.getString(0)) {
+ return cursor.getInt(1)
+ }
+ }
+ }
+ } else {
+ return Settings.Global.getInt(
+ context.contentResolver, SETTINGS_PLAY_STORE_AVAILABILITY,
+ PLAY_STORE_ERROR_UNKNOWN
+ )
+ }
+ return PLAY_STORE_ERROR_UNKNOWN
+ }
+
+ /** @hide */
+ @IntDef(
+ PLAY_STORE_ERROR_UNKNOWN,
+ PLAY_STORE_AVAILABLE,
+ PLAY_STORE_UNAVAILABLE
+ )
+ @Retention(AnnotationRetention.SOURCE)
+ public annotation class PlayStoreStatus
+}
\ No newline at end of file
diff --git a/wear/wear-remote-interactions/src/test/java/androidx/wear/remote/interactions/PlayStoreAvailabilityTest.kt b/wear/wear-remote-interactions/src/test/java/androidx/wear/remote/interactions/PlayStoreAvailabilityTest.kt
new file mode 100644
index 0000000..2560004
--- /dev/null
+++ b/wear/wear-remote-interactions/src/test/java/androidx/wear/remote/interactions/PlayStoreAvailabilityTest.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.remote.interactions
+
+import android.content.ContentProvider
+import android.content.ContentResolver
+import android.content.Context
+import android.database.Cursor
+import android.database.MatrixCursor
+import androidx.test.core.app.ApplicationProvider
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+import org.robolectric.Shadows
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.internal.DoNotInstrument
+import org.robolectric.shadows.ShadowContentResolver
+import org.robolectric.shadows.ShadowPackageManager
+
+@RunWith(WearRemoteInteractionsTestRunner::class)
+@DoNotInstrument // Stop Robolectric instrumenting this class due to it being in package "android".
+class PlayStoreAvailabilityTest {
+ @Mock
+ private var mockContentProvider: ContentProvider? = null
+ private var contentResolver: ContentResolver? = null
+ private var shadowPackageManager: ShadowPackageManager? = null
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ ShadowContentResolver.registerProviderInternal(
+ PlayStoreAvailability.SETTINGS_AUTHORITY_URI,
+ mockContentProvider
+ )
+ val context: Context = ApplicationProvider.getApplicationContext()
+ contentResolver = context.contentResolver
+ shadowPackageManager = Shadows.shadowOf(context.packageManager)
+ shadowPackageManager?.setSystemFeature(PlayStoreAvailability.SYSTEM_FEATURE_WATCH, true)
+ }
+
+ @Test
+ @Config(sdk = [25, 26, 27, 28])
+ fun getPlayStoreAvailabilityOnPhone_returnsAvailable() {
+ createFakePlayStoreAvailabilityQuery(PlayStoreAvailability.PLAY_STORE_AVAILABLE)
+ assertEquals(
+ PlayStoreAvailability.getPlayStoreAvailabilityOnPhone(
+ ApplicationProvider.getApplicationContext()
+ ),
+ PlayStoreAvailability.PLAY_STORE_AVAILABLE
+ )
+ }
+
+ @Test
+ @Config(sdk = [25, 26, 27, 28])
+ fun getPlayStoreAvailabilityOnPhone_returnsUnavailable() {
+ createFakePlayStoreAvailabilityQuery(PlayStoreAvailability.PLAY_STORE_UNAVAILABLE)
+ assertEquals(
+ PlayStoreAvailability.getPlayStoreAvailabilityOnPhone(
+ ApplicationProvider.getApplicationContext()
+ ),
+ PlayStoreAvailability.PLAY_STORE_UNAVAILABLE
+ )
+ }
+
+ @Test
+ @Config(sdk = [25, 26, 27, 28])
+ fun getPlayStoreAvailabilityOnPhone_returnsError() {
+ assertEquals(
+ PlayStoreAvailability.getPlayStoreAvailabilityOnPhone(
+ ApplicationProvider.getApplicationContext()
+ ),
+ PlayStoreAvailability.PLAY_STORE_ERROR_UNKNOWN
+ )
+ }
+
+ /*
+ TODO(b/178086256): Since Roboelectric doesn't support API 30 for now, add tests for it when it
+ is supported.
+ */
+
+ companion object {
+ private fun createFakePlayStoreAvailabilityCursor(availability: Int): Cursor {
+ val cursor = MatrixCursor(arrayOf("key", "value"))
+ cursor.addRow(
+ arrayOf<Any>(PlayStoreAvailability.KEY_PLAY_STORE_AVAILABILITY, availability)
+ )
+ return cursor
+ }
+ }
+
+ private fun createFakePlayStoreAvailabilityQuery(availability: Int) {
+ Mockito.`when`(
+ mockContentProvider!!.query(
+ ArgumentMatchers.eq(PlayStoreAvailability.PLAY_STORE_AVAILABILITY_URI),
+ ArgumentMatchers.any(),
+ ArgumentMatchers.any(),
+ ArgumentMatchers.any(),
+ ArgumentMatchers.any()
+ )
+ )
+ .thenReturn(createFakePlayStoreAvailabilityCursor(availability))
+ }
+}
diff --git a/wear/wear-remote-interactions/src/test/java/androidx/wear/remote/interactions/WearRemoteInteractionsTestRunner.kt b/wear/wear-remote-interactions/src/test/java/androidx/wear/remote/interactions/WearRemoteInteractionsTestRunner.kt
new file mode 100644
index 0000000..c15dd4b
--- /dev/null
+++ b/wear/wear-remote-interactions/src/test/java/androidx/wear/remote/interactions/WearRemoteInteractionsTestRunner.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.wear.remote.interactions
+
+import org.junit.runners.model.FrameworkMethod
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.internal.bytecode.InstrumentationConfiguration
+
+/**
+ * A [RobolectricTestRunner] for [androidx.wear.remote.interactions] unit tests.
+ *
+ * It has instrumentation turned off for the [androidx.wear.remote.interactions] package.
+ *
+ * Robolectric tries to instrument Kotlin classes, and it throws errors when it encounters
+ * companion objects, constructors with default values for parameters, and data classes with
+ * inline classes. We don't need shadowing of our classes because we want to use the actual
+ * objects in our tests.
+ */
+class WearRemoteInteractionsTestRunner(testClass: Class<*>) : RobolectricTestRunner(testClass) {
+ override fun createClassLoaderConfig(method: FrameworkMethod): InstrumentationConfiguration =
+ InstrumentationConfiguration.Builder(
+ super.createClassLoaderConfig(method)
+ )
+ .doNotInstrumentPackage("androidx.wear.remote.interactions")
+ .build()
+}
\ No newline at end of file
diff --git a/wear/wear-remote-interactions/src/test/resources/robolectric.properties b/wear/wear-remote-interactions/src/test/resources/robolectric.properties
new file mode 100644
index 0000000..c0066c1
--- /dev/null
+++ b/wear/wear-remote-interactions/src/test/resources/robolectric.properties
@@ -0,0 +1,20 @@
+#
+# Copyright 2021 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Robolectric currently doesn't support API 30, so we have to explicitly the target sdk levels for
+# now.
+# TODO(b/177072877): Remove when no longer necessary.
+sdk=25,26,27,28
diff --git a/window/window-extensions/build.gradle b/window/window-extensions/build.gradle
index e4db938..5b1a650 100644
--- a/window/window-extensions/build.gradle
+++ b/window/window-extensions/build.gradle
@@ -49,7 +49,7 @@
androidx {
name = "Jetpack WindowManager library Extensions"
- publish = Publish.SNAPSHOT_ONLY
+ publish = Publish.SNAPSHOT_AND_RELEASE // Only to generate per-project-zips
runApiTasks = new RunApiTasks.Yes("Need to track API surface before moving to publish")
mavenGroup = LibraryGroups.WINDOW
mavenVersion = LibraryVersions.WINDOW_EXTENSIONS
diff --git a/window/window-sidecar/build.gradle b/window/window-sidecar/build.gradle
index 6c81f6e..d8b8fd3 100644
--- a/window/window-sidecar/build.gradle
+++ b/window/window-sidecar/build.gradle
@@ -36,7 +36,7 @@
androidx {
name = "Jetpack WindowManager library Sidecar"
- publish = Publish.NONE
+ publish = Publish.SNAPSHOT_AND_RELEASE // Only to generate per-project-zips
runApiTasks = new RunApiTasks.Yes("Need to track API surface but should never publish")
mavenGroup = LibraryGroups.WINDOW
mavenVersion = LibraryVersions.WINDOW_SIDECAR