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));