Adds a customizable InputMergerFactory.
Fixes: b/133273159
Test: Added unit tests for InputMergerFactory with fallback behavior
Change-Id: I94dd8518fb33f5433484106e69d2cf014f0e53f8
diff --git a/work/workmanager/api/2.3.0-alpha01.txt b/work/workmanager/api/2.3.0-alpha01.txt
index f0bff1a..8d3b024 100644
--- a/work/workmanager/api/2.3.0-alpha01.txt
+++ b/work/workmanager/api/2.3.0-alpha01.txt
@@ -13,6 +13,7 @@
public final class Configuration {
method public java.util.concurrent.Executor getExecutor();
+ method public androidx.work.InputMergerFactory getInputMergerFactory();
method public int getMaxJobSchedulerId();
method public int getMinJobSchedulerId();
method public java.util.concurrent.Executor getTaskExecutor();
@@ -24,6 +25,7 @@
ctor public Configuration.Builder();
method public androidx.work.Configuration build();
method public androidx.work.Configuration.Builder setExecutor(java.util.concurrent.Executor);
+ method public androidx.work.Configuration.Builder setInputMergerFactory(androidx.work.InputMergerFactory);
method public androidx.work.Configuration.Builder setJobSchedulerJobIdRange(int, int);
method public androidx.work.Configuration.Builder setMaxSchedulerLimit(int);
method public androidx.work.Configuration.Builder setMinimumLoggingLevel(int);
@@ -124,6 +126,11 @@
method public abstract androidx.work.Data merge(java.util.List<androidx.work.Data!>);
}
+ public abstract class InputMergerFactory {
+ ctor public InputMergerFactory();
+ method public abstract androidx.work.InputMerger? createInputMerger(String);
+ }
+
public abstract class ListenableWorker {
ctor @Keep public ListenableWorker(android.content.Context, androidx.work.WorkerParameters);
method public final android.content.Context getApplicationContext();
diff --git a/work/workmanager/api/current.txt b/work/workmanager/api/current.txt
index f0bff1a..8d3b024 100644
--- a/work/workmanager/api/current.txt
+++ b/work/workmanager/api/current.txt
@@ -13,6 +13,7 @@
public final class Configuration {
method public java.util.concurrent.Executor getExecutor();
+ method public androidx.work.InputMergerFactory getInputMergerFactory();
method public int getMaxJobSchedulerId();
method public int getMinJobSchedulerId();
method public java.util.concurrent.Executor getTaskExecutor();
@@ -24,6 +25,7 @@
ctor public Configuration.Builder();
method public androidx.work.Configuration build();
method public androidx.work.Configuration.Builder setExecutor(java.util.concurrent.Executor);
+ method public androidx.work.Configuration.Builder setInputMergerFactory(androidx.work.InputMergerFactory);
method public androidx.work.Configuration.Builder setJobSchedulerJobIdRange(int, int);
method public androidx.work.Configuration.Builder setMaxSchedulerLimit(int);
method public androidx.work.Configuration.Builder setMinimumLoggingLevel(int);
@@ -124,6 +126,11 @@
method public abstract androidx.work.Data merge(java.util.List<androidx.work.Data!>);
}
+ public abstract class InputMergerFactory {
+ ctor public InputMergerFactory();
+ method public abstract androidx.work.InputMerger? createInputMerger(String);
+ }
+
public abstract class ListenableWorker {
ctor @Keep public ListenableWorker(android.content.Context, androidx.work.WorkerParameters);
method public final android.content.Context getApplicationContext();
diff --git a/work/workmanager/src/androidTest/java/androidx/work/InputMergerFactoryTest.kt b/work/workmanager/src/androidTest/java/androidx/work/InputMergerFactoryTest.kt
new file mode 100644
index 0000000..8104d5f
--- /dev/null
+++ b/work/workmanager/src/androidTest/java/androidx/work/InputMergerFactoryTest.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.work
+
+import android.content.Context
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import org.hamcrest.CoreMatchers.notNullValue
+import org.hamcrest.MatcherAssert.assertThat
+import org.hamcrest.Matchers.`is`
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class InputMergerFactoryTest {
+ private lateinit var context: Context
+ private lateinit var factory: InputMergerFactory
+ private lateinit var configuration: Configuration
+
+ @Before
+ fun setUp() {
+ context = ApplicationProvider.getApplicationContext()
+ factory = mock(InputMergerFactory::class.java)
+ configuration = Configuration.Builder()
+ .setInputMergerFactory(factory)
+ .build()
+ }
+
+ @Test
+ fun testInputMergerFactory() {
+ val name = ArrayCreatingInputMerger::class.java.name
+ val merger = configuration.inputMergerFactory.createInputMergerWithDefaultFallback(name)
+ assertThat(merger, notNullValue())
+ verify(factory, times(1)).createInputMerger(name)
+ }
+
+ @Test
+ fun testInputMergerFactory2() {
+ factory = object : InputMergerFactory() {
+ override fun createInputMerger(className: String): InputMerger? {
+ return OverwritingInputMerger()
+ }
+ }
+ configuration = Configuration.Builder()
+ .setInputMergerFactory(factory)
+ .build()
+
+ val name = ArrayCreatingInputMerger::class.java.name
+ val merger = configuration.inputMergerFactory.createInputMergerWithDefaultFallback(name)
+ val instanceCheck = merger is OverwritingInputMerger
+ assertThat(merger, notNullValue())
+ assertThat(instanceCheck, `is`(true))
+ }
+}
diff --git a/work/workmanager/src/main/java/androidx/work/Configuration.java b/work/workmanager/src/main/java/androidx/work/Configuration.java
index 9246dcce..c24f643 100644
--- a/work/workmanager/src/main/java/androidx/work/Configuration.java
+++ b/work/workmanager/src/main/java/androidx/work/Configuration.java
@@ -51,6 +51,7 @@
private final @NonNull Executor mExecutor;
private final @NonNull Executor mTaskExecutor;
private final @NonNull WorkerFactory mWorkerFactory;
+ private final @NonNull InputMergerFactory mInputMergerFactory;
private final int mLoggingLevel;
private final int mMinJobSchedulerId;
private final int mMaxJobSchedulerId;
@@ -78,6 +79,12 @@
mWorkerFactory = builder.mWorkerFactory;
}
+ if (builder.mInputMergerFactory == null) {
+ mInputMergerFactory = InputMergerFactory.getDefaultInputMergerFactory();
+ } else {
+ mInputMergerFactory = builder.mInputMergerFactory;
+ }
+
mLoggingLevel = builder.mLoggingLevel;
mMinJobSchedulerId = builder.mMinJobSchedulerId;
mMaxJobSchedulerId = builder.mMaxJobSchedulerId;
@@ -115,6 +122,14 @@
}
/**
+ * @return The {@link InputMergerFactory} used by {@link WorkManager} to create instances of
+ * {@link InputMerger}s.
+ */
+ public @NonNull InputMergerFactory getInputMergerFactory() {
+ return mInputMergerFactory;
+ }
+
+ /**
* Gets the minimum logging level for {@link WorkManager}.
*
* @return The minimum logging level, corresponding to the constants found in
@@ -186,6 +201,7 @@
Executor mExecutor;
WorkerFactory mWorkerFactory;
+ InputMergerFactory mInputMergerFactory;
Executor mTaskExecutor;
int mLoggingLevel = Log.INFO;
@@ -205,6 +221,17 @@
}
/**
+ * Specifies a custom {@link InputMergerFactory} for WorkManager.
+ * @param inputMergerFactory A {@link InputMergerFactory} for creating {@link InputMerger}s
+ * @return This {@link Builder} instance
+ */
+ @NonNull
+ public Builder setInputMergerFactory(@NonNull InputMergerFactory inputMergerFactory) {
+ mInputMergerFactory = inputMergerFactory;
+ return this;
+ }
+
+ /**
* Specifies a custom {@link Executor} for WorkManager.
*
* @param executor An {@link Executor} for running {@link Worker}s
diff --git a/work/workmanager/src/main/java/androidx/work/InputMergerFactory.java b/work/workmanager/src/main/java/androidx/work/InputMergerFactory.java
new file mode 100644
index 0000000..ac30624
--- /dev/null
+++ b/work/workmanager/src/main/java/androidx/work/InputMergerFactory.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.work;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+
+/**
+ * A factory object that creates {@link InputMerger} instances. The factory is invoked every
+ * time a work runs. You can override the default implementation of this factory by manually
+ * initializing {@link WorkManager} (see {@link WorkManager#initialize(Context, Configuration)}
+ * and specifying a new {@link InputMergerFactory} in
+ * {@link Configuration.Builder#setInputMergerFactory(InputMergerFactory)}.
+ */
+public abstract class InputMergerFactory {
+ /**
+ * Override this method to create an instance of a {@link InputMerger} given its fully
+ * qualified class name.
+ * <p></p>
+ * Throwing an {@link Exception} here will crash the application. If an
+ * {@link InputMergerFactory} is unable to create an instance of a {@link InputMerger}, it
+ * should return {@code null} so it can delegate to the default {@link InputMergerFactory}.
+ *
+ * @param className The fully qualified class name for the {@link InputMerger}
+ * @return an instance of {@link InputMerger}
+ */
+ @Nullable
+ public abstract InputMerger createInputMerger(@NonNull String className);
+
+ /**
+ * Creates an instance of a {@link InputMerger} given its fully
+ * qualified class name with the correct fallback behavior.
+ *
+ * @param className The fully qualified class name for the {@link InputMerger}
+ * @return an instance of {@link InputMerger}
+ *
+ * @hide
+ */
+ @Nullable
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ public final InputMerger createInputMergerWithDefaultFallback(@NonNull String className) {
+ InputMerger inputMerger = createInputMerger(className);
+ if (inputMerger == null) {
+ inputMerger = InputMerger.fromClassName(className);
+ }
+ return inputMerger;
+ }
+
+ /**
+ * @return A default {@link InputMergerFactory} with no custom behavior.
+ *
+ * @hide
+ */
+ @NonNull
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ public static InputMergerFactory getDefaultInputMergerFactory() {
+ return new InputMergerFactory() {
+ @Nullable
+ @Override
+ public InputMerger createInputMerger(@NonNull String className) {
+ return null;
+ }
+ };
+ }
+}
diff --git a/work/workmanager/src/main/java/androidx/work/impl/WorkerWrapper.java b/work/workmanager/src/main/java/androidx/work/impl/WorkerWrapper.java
index efa3b4c..de5b1c5 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/WorkerWrapper.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/WorkerWrapper.java
@@ -35,6 +35,7 @@
import androidx.work.Configuration;
import androidx.work.Data;
import androidx.work.InputMerger;
+import androidx.work.InputMergerFactory;
import androidx.work.ListenableWorker;
import androidx.work.Logger;
import androidx.work.WorkInfo;
@@ -199,7 +200,10 @@
if (mWorkSpec.isPeriodic()) {
input = mWorkSpec.input;
} else {
- InputMerger inputMerger = InputMerger.fromClassName(mWorkSpec.inputMergerClassName);
+ InputMergerFactory inputMergerFactory = mConfiguration.getInputMergerFactory();
+ String inputMergerClassName = mWorkSpec.inputMergerClassName;
+ InputMerger inputMerger =
+ inputMergerFactory.createInputMergerWithDefaultFallback(inputMergerClassName);
if (inputMerger == null) {
Logger.get().error(TAG, String.format("Could not create Input Merger %s",
mWorkSpec.inputMergerClassName));