public class

WorkManagerImpl

extends WorkManager

 java.lang.Object

androidx.work.WorkManager

↳androidx.work.impl.WorkManagerImpl

Gradle dependencies

compile group: 'androidx.work', name: 'work-runtime', version: '2.8.0-alpha02'

  • groupId: androidx.work
  • artifactId: work-runtime
  • version: 2.8.0-alpha02

Artifact androidx.work:work-runtime:2.8.0-alpha02 it located at Google repository (https://maven.google.com/)

Overview

A concrete implementation of WorkManager.

Summary

Fields
public static final intMAX_PRE_JOB_SCHEDULER_API_LEVEL

public static final intMIN_JOB_SCHEDULER_API_LEVEL

public static final java.lang.StringREMOTE_WORK_MANAGER_CLIENT

Constructors
publicWorkManagerImpl(Context context, Configuration configuration, TaskExecutor workTaskExecutor)

Create an instance of WorkManagerImpl.

publicWorkManagerImpl(Context context, Configuration configuration, TaskExecutor workTaskExecutor, boolean useTestDatabase)

Create an instance of WorkManagerImpl.

publicWorkManagerImpl(Context context, Configuration configuration, TaskExecutor workTaskExecutor, WorkDatabase database)

Create an instance of WorkManagerImpl.

publicWorkManagerImpl(Context context, Configuration configuration, TaskExecutor workTaskExecutor, WorkDatabase workDatabase, java.util.List<Scheduler> schedulers, Processor processor)

Create an instance of WorkManagerImpl.

Methods
public abstract WorkContinuationbeginUniqueWork(java.lang.String uniqueWorkName, ExistingWorkPolicy existingWorkPolicy, java.util.List<OneTimeWorkRequest> work)

This method allows you to begin unique chains of work for situations where you only want one chain with a given name to be active at a time.

public abstract WorkContinuationbeginWith(java.util.List<OneTimeWorkRequest> work)

Begins a chain with one or more OneTimeWorkRequests, which can be enqueued together in the future using WorkContinuation.enqueue().

public abstract OperationcancelAllWork()

Cancels all unfinished work.

public abstract OperationcancelAllWorkByTag(java.lang.String tag)

Cancels all unfinished work with the given tag.

public abstract OperationcancelUniqueWork(java.lang.String uniqueWorkName)

Cancels all unfinished work in the work chain with the given name.

public abstract OperationcancelWorkById(java.util.UUID id)

Cancels work with the given id if it isn't finished.

public abstract PendingIntentcreateCancelPendingIntent(java.util.UUID id)

Creates a PendingIntent which can be used to cancel a WorkRequest with the given id.

public java.util.List<Scheduler>createSchedulers(Context context, Configuration configuration, Trackers trackers)

public WorkContinuationImplcreateWorkContinuationForUniquePeriodicWork(java.lang.String uniqueWorkName, ExistingPeriodicWorkPolicy existingPeriodicWorkPolicy, PeriodicWorkRequest periodicWork)

Creates a WorkContinuation for the given unique PeriodicWorkRequest.

public abstract Operationenqueue(java.util.List<WorkRequest> requests)

Enqueues one or more items for background processing.

public abstract OperationenqueueUniquePeriodicWork(java.lang.String uniqueWorkName, ExistingPeriodicWorkPolicy existingPeriodicWorkPolicy, PeriodicWorkRequest periodicWork)

This method allows you to enqueue a uniquely-named PeriodicWorkRequest, where only one PeriodicWorkRequest of a particular name can be active at a time.

public abstract OperationenqueueUniqueWork(java.lang.String uniqueWorkName, ExistingWorkPolicy existingWorkPolicy, java.util.List<OneTimeWorkRequest> work)

This method allows you to enqueue work requests to a uniquely-named WorkContinuation, where only one continuation of a particular name can be active at a time.

public ContextgetApplicationContext()

public ConfigurationgetConfiguration()

public static WorkManagerImplgetInstance()

Retrieves the singleton instance of WorkManagerImpl.

public static WorkManagerImplgetInstance(Context context)

Retrieves the singleton instance of WorkManagerImpl.

public abstract <any>getLastCancelAllTimeMillis()

Gets a of the last time all work was cancelled.

public abstract LiveData<java.lang.Long>getLastCancelAllTimeMillisLiveData()

Gets a LiveData of the last time all work was cancelled.

public PreferenceUtilsgetPreferenceUtils()

public ProcessorgetProcessor()

public RemoteWorkManagergetRemoteWorkManager()

public java.util.List<Scheduler>getSchedulers()

public TrackersgetTrackers()

public WorkDatabasegetWorkDatabase()

public abstract <any>getWorkInfoById(java.util.UUID id)

Gets a of the WorkInfo for a given work id.

public abstract LiveData<WorkInfo>getWorkInfoByIdLiveData(java.util.UUID id)

Gets a LiveData of the WorkInfo for a given work id.

public abstract <any>getWorkInfos(WorkQuery workQuery)

Gets the of the java.util.List of WorkInfo for all work referenced by the WorkQuery specification.

public abstract <any>getWorkInfosByTag(java.lang.String tag)

Gets a of the WorkInfo for all work for a given tag.

public abstract LiveData<java.util.List>getWorkInfosByTagLiveData(java.lang.String tag)

Gets a LiveData of the WorkInfo for all work for a given tag.

public abstract <any>getWorkInfosForUniqueWork(java.lang.String uniqueWorkName)

Gets a of the WorkInfo for all work in a work chain with a given unique name.

public abstract LiveData<java.util.List>getWorkInfosForUniqueWorkLiveData(java.lang.String uniqueWorkName)

Gets a LiveData of the WorkInfo for all work in a work chain with a given unique name.

public abstract LiveData<java.util.List>getWorkInfosLiveData(WorkQuery workQuery)

Gets the LiveData of the java.util.List of WorkInfo for all work referenced by the WorkQuery specification.

public TaskExecutorgetWorkTaskExecutor()

public static voidinitialize(Context context, Configuration configuration)

Initializes the singleton instance of WorkManagerImpl.

public static booleanisInitialized()

public voidonForceStopRunnableCompleted()

A way for ForceStopRunnable to tell WorkManagerImpl that it has completed.

public abstract OperationpruneWork()

Prunes all eligible finished work from the internal database.

public voidrescheduleEligibleWork()

Reschedules all the eligible work.

public static voidsetDelegate(WorkManagerImpl delegate)

public voidsetReschedulePendingResult(BroadcastReceiver.PendingResult rescheduleReceiverResult)

This method is invoked by RescheduleReceiver after a call to BroadcastReceiver.

public voidstartWork(java.lang.String workSpecId)

public voidstartWork(java.lang.String workSpecId, WorkerParameters.RuntimeExtras runtimeExtras)

public voidstopForegroundWork(java.lang.String workSpecId)

public voidstopWork(java.lang.String workSpecId)

from WorkManagerbeginUniqueWork, beginWith, enqueue, enqueueUniqueWork
from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Fields

public static final int MAX_PRE_JOB_SCHEDULER_API_LEVEL

public static final int MIN_JOB_SCHEDULER_API_LEVEL

public static final java.lang.String REMOTE_WORK_MANAGER_CLIENT

Constructors

public WorkManagerImpl(Context context, Configuration configuration, TaskExecutor workTaskExecutor)

Create an instance of WorkManagerImpl.

Parameters:

context: The application
configuration: The Configuration configuration
workTaskExecutor: The TaskExecutor for running "processing" jobs, such as enqueueing, scheduling, cancellation, etc.

public WorkManagerImpl(Context context, Configuration configuration, TaskExecutor workTaskExecutor, boolean useTestDatabase)

Create an instance of WorkManagerImpl.

Parameters:

context: The application
configuration: The Configuration configuration
workTaskExecutor: The TaskExecutor for running "processing" jobs, such as enqueueing, scheduling, cancellation, etc.
useTestDatabase: true If using an in-memory test database

public WorkManagerImpl(Context context, Configuration configuration, TaskExecutor workTaskExecutor, WorkDatabase database)

Create an instance of WorkManagerImpl.

Parameters:

context: The application
configuration: The Configuration configuration
workTaskExecutor: The TaskExecutor for running "processing" jobs, such as enqueueing, scheduling, cancellation, etc.
database: The WorkDatabase

public WorkManagerImpl(Context context, Configuration configuration, TaskExecutor workTaskExecutor, WorkDatabase workDatabase, java.util.List<Scheduler> schedulers, Processor processor)

Create an instance of WorkManagerImpl.

Parameters:

context: The application
configuration: The Configuration configuration
workTaskExecutor: The TaskExecutor for running "processing" jobs, such as enqueueing, scheduling, cancellation, etc.
workDatabase: The WorkDatabase instance
processor: The Processor instance

Methods

public static void setDelegate(WorkManagerImpl delegate)

Parameters:

delegate: The delegate for WorkManagerImpl for testing; null to use the default instance

public static WorkManagerImpl getInstance()

Deprecated: Call WorkManagerImpl.getInstance(Context) instead.

Retrieves the singleton instance of WorkManagerImpl.

Returns:

The singleton instance of WorkManagerImpl

public static boolean isInitialized()

public static WorkManagerImpl getInstance(Context context)

Retrieves the singleton instance of WorkManagerImpl.

Parameters:

context: A context for on-demand initialization.

Returns:

The singleton instance of WorkManagerImpl

public static void initialize(Context context, Configuration configuration)

Initializes the singleton instance of WorkManagerImpl. You should only do this if you want to use a custom Configuration object and have disabled WorkManagerInitializer.

Parameters:

context: A object for configuration purposes. Internally, this class will call , so you may safely pass in any Context without risking a memory leak.
configuration: The Configuration for used to set up WorkManager.

public Context getApplicationContext()

Returns:

The application associated with this WorkManager.

public WorkDatabase getWorkDatabase()

Returns:

The WorkDatabase instance associated with this WorkManager.

public Configuration getConfiguration()

Returns:

The Configuration instance associated with this WorkManager.

public java.util.List<Scheduler> getSchedulers()

Returns:

The Schedulers associated with this WorkManager based on the device's capabilities, SDK version, etc.

public Processor getProcessor()

Returns:

The Processor used to process background work.

public TaskExecutor getWorkTaskExecutor()

Returns:

the TaskExecutor used by the instance of WorkManager.

public PreferenceUtils getPreferenceUtils()

Returns:

the PreferenceUtils used by the instance of WorkManager.

public Trackers getTrackers()

Returns:

the Trackers used by WorkManager

public abstract Operation enqueue(java.util.List<WorkRequest> requests)

Enqueues one or more items for background processing.

Parameters:

requests: One or more WorkRequest to enqueue

Returns:

An Operation that can be used to determine when the enqueue has completed

public abstract WorkContinuation beginWith(java.util.List<OneTimeWorkRequest> work)

Begins a chain with one or more OneTimeWorkRequests, which can be enqueued together in the future using WorkContinuation.enqueue().

If any work in the chain fails or is cancelled, all of its dependent work inherits that state and will never run.

Parameters:

work: One or more OneTimeWorkRequest to start a chain of work

Returns:

A WorkContinuation that allows for further chaining of dependent OneTimeWorkRequest

public abstract WorkContinuation beginUniqueWork(java.lang.String uniqueWorkName, ExistingWorkPolicy existingWorkPolicy, java.util.List<OneTimeWorkRequest> work)

This method allows you to begin unique chains of work for situations where you only want one chain with a given name to be active at a time. For example, you may only want one sync operation to be active. If there is one pending, you can choose to let it run or replace it with your new work.

The uniqueWorkName uniquely identifies this set of work.

If this method determines that new work should be enqueued and run, all records of previous work with uniqueWorkName will be pruned. If this method determines that new work should NOT be run, then the entire chain will be considered a no-op.

If any work in the chain fails or is cancelled, all of its dependent work inherits that state and will never run. This is particularly important if you are using APPEND as your ExistingWorkPolicy.

Parameters:

uniqueWorkName: A unique name which for this chain of work
existingWorkPolicy: An ExistingWorkPolicy; see below for more information
work: One or more OneTimeWorkRequest to enqueue. REPLACE ensures that if there is pending work labelled with uniqueWorkName, it will be cancelled and the new work will run. KEEP will run the new sequence of work only if there is no pending work labelled with uniqueWorkName. APPEND will create a new sequence of work if there is no existing work with uniqueWorkName; otherwise, work will be added as a child of all leaf nodes labelled with uniqueWorkName.

Returns:

A WorkContinuation that allows further chaining

public abstract Operation enqueueUniqueWork(java.lang.String uniqueWorkName, ExistingWorkPolicy existingWorkPolicy, java.util.List<OneTimeWorkRequest> work)

This method allows you to enqueue work requests to a uniquely-named WorkContinuation, where only one continuation of a particular name can be active at a time. For example, you may only want one sync operation to be active. If there is one pending, you can choose to let it run or replace it with your new work.

The uniqueWorkName uniquely identifies this WorkContinuation.

Parameters:

uniqueWorkName: A unique name which for this operation
existingWorkPolicy: An ExistingWorkPolicy
work: OneTimeWorkRequests to enqueue. REPLACE ensures that if there is pending work labelled with uniqueWorkName, it will be cancelled and the new work will run. KEEP will run the new OneTimeWorkRequests only if there is no pending work labelled with uniqueWorkName. APPEND will append the OneTimeWorkRequests as leaf nodes labelled with uniqueWorkName.

Returns:

An Operation that can be used to determine when the enqueue has completed

public abstract Operation enqueueUniquePeriodicWork(java.lang.String uniqueWorkName, ExistingPeriodicWorkPolicy existingPeriodicWorkPolicy, PeriodicWorkRequest periodicWork)

This method allows you to enqueue a uniquely-named PeriodicWorkRequest, where only one PeriodicWorkRequest of a particular name can be active at a time. For example, you may only want one sync operation to be active. If there is one pending, you can choose to let it run or replace it with your new work.

The uniqueWorkName uniquely identifies this PeriodicWorkRequest.

Parameters:

uniqueWorkName: A unique name which for this operation
existingPeriodicWorkPolicy: An ExistingPeriodicWorkPolicy
periodicWork: A PeriodicWorkRequest to enqueue. REPLACE ensures that if there is pending work labelled with uniqueWorkName, it will be cancelled and the new work will run. KEEP will run the new PeriodicWorkRequest only if there is no pending work labelled with uniqueWorkName.

Returns:

An Operation that can be used to determine when the enqueue has completed

public WorkContinuationImpl createWorkContinuationForUniquePeriodicWork(java.lang.String uniqueWorkName, ExistingPeriodicWorkPolicy existingPeriodicWorkPolicy, PeriodicWorkRequest periodicWork)

Creates a WorkContinuation for the given unique PeriodicWorkRequest.

public abstract Operation cancelWorkById(java.util.UUID id)

Cancels work with the given id if it isn't finished. Note that cancellation is a best-effort policy and work that is already executing may continue to run. Upon cancellation, ListenableWorker.onStopped() will be invoked for any affected workers.

Parameters:

id: The id of the work

Returns:

An Operation that can be used to determine when the cancelWorkById has completed

public abstract Operation cancelAllWorkByTag(java.lang.String tag)

Cancels all unfinished work with the given tag. Note that cancellation is a best-effort policy and work that is already executing may continue to run. Upon cancellation, ListenableWorker.onStopped() will be invoked for any affected workers.

Parameters:

tag: The tag used to identify the work

Returns:

An Operation that can be used to determine when the cancelAllWorkByTag has completed

public abstract Operation cancelUniqueWork(java.lang.String uniqueWorkName)

Cancels all unfinished work in the work chain with the given name. Note that cancellation is a best-effort policy and work that is already executing may continue to run. Upon cancellation, ListenableWorker.onStopped() will be invoked for any affected workers.

Parameters:

uniqueWorkName: The unique name used to identify the chain of work

Returns:

An Operation that can be used to determine when the cancelUniqueWork has completed

public abstract Operation cancelAllWork()

Cancels all unfinished work. Use this method with extreme caution! By invoking it, you will potentially affect other modules or libraries in your codebase. It is strongly recommended that you use one of the other cancellation methods at your disposal.

Upon cancellation, ListenableWorker.onStopped() will be invoked for any affected workers.

Returns:

An Operation that can be used to determine when the cancelAllWork has completed

public abstract PendingIntent createCancelPendingIntent(java.util.UUID id)

Creates a PendingIntent which can be used to cancel a WorkRequest with the given id.

Parameters:

id: The WorkRequest id.

Returns:

The PendingIntent that can be used to cancel the WorkRequest.

public abstract LiveData<java.lang.Long> getLastCancelAllTimeMillisLiveData()

Gets a LiveData of the last time all work was cancelled. This method is intended for use by library and module developers who have dependent data in their own repository that must be updated or deleted in case someone cancels their work without their prior knowledge.

Returns:

A LiveData of the timestamp (System#getCurrentTimeMillis()) when WorkManager.cancelAllWork() was last invoked; this timestamp may be 0L if this never occurred

public abstract <any> getLastCancelAllTimeMillis()

Gets a of the last time all work was cancelled. This method is intended for use by library and module developers who have dependent data in their own repository that must be updated or deleted in case someone cancels their work without their prior knowledge.

Returns:

A of the timestamp (System#getCurrentTimeMillis()) when WorkManager.cancelAllWork() was last invoked; this timestamp may be 0L if this never occurred

public abstract Operation pruneWork()

Prunes all eligible finished work from the internal database. Eligible work must be finished (WorkInfo.State.SUCCEEDED, WorkInfo.State.FAILED, or WorkInfo.State.CANCELLED), with zero unfinished dependents.

Use this method with caution; by invoking it, you (and any modules and libraries in your codebase) will no longer be able to observe the WorkInfo of the pruned work. You do not normally need to call this method - WorkManager takes care to auto-prune its work after a sane period of time. This method also ignores the WorkRequest.Builder.keepResultsForAtLeast(long, TimeUnit) policy.

Returns:

An Operation that can be used to determine when the pruneWork has completed

public abstract LiveData<WorkInfo> getWorkInfoByIdLiveData(java.util.UUID id)

Gets a LiveData of the WorkInfo for a given work id.

Parameters:

id: The id of the work

Returns:

A LiveData of the WorkInfo associated with id; note that this WorkInfo may be null if id is not known to WorkManager.

public abstract <any> getWorkInfoById(java.util.UUID id)

Gets a of the WorkInfo for a given work id.

Parameters:

id: The id of the work

Returns:

A of the WorkInfo associated with id; note that this WorkInfo may be null if id is not known to WorkManager

public abstract LiveData<java.util.List> getWorkInfosByTagLiveData(java.lang.String tag)

Gets a LiveData of the WorkInfo for all work for a given tag.

Parameters:

tag: The tag of the work

Returns:

A LiveData list of WorkInfo for work tagged with tag

public abstract <any> getWorkInfosByTag(java.lang.String tag)

Gets a of the WorkInfo for all work for a given tag.

Parameters:

tag: The tag of the work

Returns:

A list of WorkInfo for work tagged with tag

public abstract LiveData<java.util.List> getWorkInfosForUniqueWorkLiveData(java.lang.String uniqueWorkName)

Gets a LiveData of the WorkInfo for all work in a work chain with a given unique name.

Parameters:

uniqueWorkName: The unique name used to identify the chain of work

Returns:

A LiveData of the WorkInfo for work in the chain named uniqueWorkName

public abstract <any> getWorkInfosForUniqueWork(java.lang.String uniqueWorkName)

Gets a of the WorkInfo for all work in a work chain with a given unique name.

Parameters:

uniqueWorkName: The unique name used to identify the chain of work

Returns:

A of the WorkInfo for work in the chain named uniqueWorkName

public abstract LiveData<java.util.List> getWorkInfosLiveData(WorkQuery workQuery)

Gets the LiveData of the java.util.List of WorkInfo for all work referenced by the WorkQuery specification.

Parameters:

workQuery: The work query specification

Returns:

A LiveData of the java.util.List of WorkInfo for work referenced by this WorkQuery.

public abstract <any> getWorkInfos(WorkQuery workQuery)

Gets the of the java.util.List of WorkInfo for all work referenced by the WorkQuery specification.

Parameters:

workQuery: The work query specification

Returns:

A of the java.util.List of WorkInfo for work referenced by this WorkQuery.

public RemoteWorkManager getRemoteWorkManager()

public void startWork(java.lang.String workSpecId)

Parameters:

workSpecId: The WorkSpec id to start

public void startWork(java.lang.String workSpecId, WorkerParameters.RuntimeExtras runtimeExtras)

Parameters:

workSpecId: The WorkSpec id to start
runtimeExtras: The associated with this work

public void stopWork(java.lang.String workSpecId)

Parameters:

workSpecId: The WorkSpec id to stop

public void stopForegroundWork(java.lang.String workSpecId)

Parameters:

workSpecId: The WorkSpec id to stop when running in the context of a foreground service.

public void rescheduleEligibleWork()

Reschedules all the eligible work. Useful for cases like, app was force stopped or BOOT_COMPLETED, TIMEZONE_CHANGED and TIME_SET for AlarmManager.

public void onForceStopRunnableCompleted()

A way for ForceStopRunnable to tell WorkManagerImpl that it has completed.

public void setReschedulePendingResult(BroadcastReceiver.PendingResult rescheduleReceiverResult)

This method is invoked by RescheduleReceiver after a call to BroadcastReceiver. Once ForceStopRunnable is done, we can safely call .

public java.util.List<Scheduler> createSchedulers(Context context, Configuration configuration, Trackers trackers)

Source

/*
 * Copyright 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.work.impl;

import static android.app.PendingIntent.FLAG_MUTABLE;
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
import static android.text.TextUtils.isEmpty;

import static androidx.work.impl.foreground.SystemForegroundDispatcher.createCancelWorkIntent;

import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Build;

import androidx.annotation.DoNotInline;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.arch.core.util.Function;
import androidx.lifecycle.LiveData;
import androidx.work.Configuration;
import androidx.work.ExistingPeriodicWorkPolicy;
import androidx.work.ExistingWorkPolicy;
import androidx.work.Logger;
import androidx.work.OneTimeWorkRequest;
import androidx.work.Operation;
import androidx.work.PeriodicWorkRequest;
import androidx.work.R;
import androidx.work.WorkContinuation;
import androidx.work.WorkInfo;
import androidx.work.WorkManager;
import androidx.work.WorkQuery;
import androidx.work.WorkRequest;
import androidx.work.WorkerParameters;
import androidx.work.impl.background.greedy.GreedyScheduler;
import androidx.work.impl.background.systemalarm.RescheduleReceiver;
import androidx.work.impl.background.systemjob.SystemJobScheduler;
import androidx.work.impl.constraints.trackers.Trackers;
import androidx.work.impl.model.RawWorkInfoDao;
import androidx.work.impl.model.WorkSpec;
import androidx.work.impl.model.WorkSpecDao;
import androidx.work.impl.utils.CancelWorkRunnable;
import androidx.work.impl.utils.ForceStopRunnable;
import androidx.work.impl.utils.LiveDataUtils;
import androidx.work.impl.utils.PreferenceUtils;
import androidx.work.impl.utils.PruneWorkRunnable;
import androidx.work.impl.utils.RawQueries;
import androidx.work.impl.utils.StartWorkRunnable;
import androidx.work.impl.utils.StatusRunnable;
import androidx.work.impl.utils.StopWorkRunnable;
import androidx.work.impl.utils.futures.SettableFuture;
import androidx.work.impl.utils.taskexecutor.TaskExecutor;
import androidx.work.impl.utils.taskexecutor.WorkManagerTaskExecutor;
import androidx.work.multiprocess.RemoteWorkManager;

import com.google.common.util.concurrent.ListenableFuture;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.UUID;

/**
 * A concrete implementation of {@link WorkManager}.
 *
 * @hide
 */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public class WorkManagerImpl extends WorkManager {

    private static final String TAG = Logger.tagWithPrefix("WorkManagerImpl");
    public static final int MAX_PRE_JOB_SCHEDULER_API_LEVEL = 22;
    public static final int MIN_JOB_SCHEDULER_API_LEVEL = 23;
    public static final String REMOTE_WORK_MANAGER_CLIENT =
            "androidx.work.multiprocess.RemoteWorkManagerClient";

    private Context mContext;
    private Configuration mConfiguration;
    private WorkDatabase mWorkDatabase;
    private TaskExecutor mWorkTaskExecutor;
    private List<Scheduler> mSchedulers;
    private Processor mProcessor;
    private PreferenceUtils mPreferenceUtils;
    private boolean mForceStopRunnableCompleted;
    private BroadcastReceiver.PendingResult mRescheduleReceiverResult;
    private volatile RemoteWorkManager mRemoteWorkManager;
    private final Trackers mTrackers;
    private static WorkManagerImpl sDelegatedInstance = null;
    private static WorkManagerImpl sDefaultInstance = null;
    private static final Object sLock = new Object();


    /**
     * @param delegate The delegate for {@link WorkManagerImpl} for testing; {@code null} to use the
     *                 default instance
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public static void setDelegate(@Nullable WorkManagerImpl delegate) {
        synchronized (sLock) {
            sDelegatedInstance = delegate;
        }
    }

    /**
     * Retrieves the singleton instance of {@link WorkManagerImpl}.
     *
     * @return The singleton instance of {@link WorkManagerImpl}
     * @hide
     * @deprecated Call {@link WorkManagerImpl#getInstance(Context)} instead.
     */
    @Deprecated
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    @SuppressWarnings("NullableProblems")
    public static @Nullable WorkManagerImpl getInstance() {
        synchronized (sLock) {
            if (sDelegatedInstance != null) {
                return sDelegatedInstance;
            }

            return sDefaultInstance;
        }
    }

    /**
     * @hide
     */
    @SuppressWarnings("deprecation")
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public static boolean isInitialized() {
        WorkManagerImpl instance = getInstance();
        return instance != null;
    }

    /**
     * Retrieves the singleton instance of {@link WorkManagerImpl}.
     *
     * @param context A context for on-demand initialization.
     * @return The singleton instance of {@link WorkManagerImpl}
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public static @NonNull WorkManagerImpl getInstance(@NonNull Context context) {
        synchronized (sLock) {
            WorkManagerImpl instance = getInstance();
            if (instance == null) {
                Context appContext = context.getApplicationContext();
                if (appContext instanceof Configuration.Provider) {
                    initialize(
                            appContext,
                            ((Configuration.Provider) appContext).getWorkManagerConfiguration());
                    instance = getInstance(appContext);
                } else {
                    throw new IllegalStateException("WorkManager is not initialized properly.  You "
                            + "have explicitly disabled WorkManagerInitializer in your manifest, "
                            + "have not manually called WorkManager#initialize at this point, and "
                            + "your Application does not implement Configuration.Provider.");
                }
            }

            return instance;
        }
    }

    /**
     * Initializes the singleton instance of {@link WorkManagerImpl}.  You should only do this if
     * you want to use a custom {@link Configuration} object and have disabled
     * WorkManagerInitializer.
     *
     * @param context A {@link Context} object for configuration purposes. Internally, this class
     *                will call {@link Context#getApplicationContext()}, so you may safely pass in
     *                any Context without risking a memory leak.
     * @param configuration The {@link Configuration} for used to set up WorkManager.
     *
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public static void initialize(@NonNull Context context, @NonNull Configuration configuration) {
        synchronized (sLock) {
            if (sDelegatedInstance != null && sDefaultInstance != null) {
                throw new IllegalStateException("WorkManager is already initialized.  Did you "
                        + "try to initialize it manually without disabling "
                        + "WorkManagerInitializer? See "
                        + "WorkManager#initialize(Context, Configuration) or the class level "
                        + "Javadoc for more information.");
            }

            if (sDelegatedInstance == null) {
                context = context.getApplicationContext();
                if (sDefaultInstance == null) {
                    sDefaultInstance = new WorkManagerImpl(
                            context,
                            configuration,
                            new WorkManagerTaskExecutor(configuration.getTaskExecutor()));
                }
                sDelegatedInstance = sDefaultInstance;
            }
        }
    }

    /**
     * Create an instance of {@link WorkManagerImpl}.
     *
     * @param context The application {@link Context}
     * @param configuration The {@link Configuration} configuration
     * @param workTaskExecutor The {@link TaskExecutor} for running "processing" jobs, such as
     *                         enqueueing, scheduling, cancellation, etc.
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public WorkManagerImpl(
            @NonNull Context context,
            @NonNull Configuration configuration,
            @NonNull TaskExecutor workTaskExecutor) {
        this(context,
                configuration,
                workTaskExecutor,
                context.getResources().getBoolean(R.bool.workmanager_test_configuration));
    }

    /**
     * Create an instance of {@link WorkManagerImpl}.
     *
     * @param context The application {@link Context}
     * @param configuration The {@link Configuration} configuration
     * @param workTaskExecutor The {@link TaskExecutor} for running "processing" jobs, such as
     *                         enqueueing, scheduling, cancellation, etc.
     * @param useTestDatabase {@code true} If using an in-memory test database
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public WorkManagerImpl(
            @NonNull Context context,
            @NonNull Configuration configuration,
            @NonNull TaskExecutor workTaskExecutor,
            boolean useTestDatabase) {
        this(context,
                configuration,
                workTaskExecutor,
                WorkDatabase.create(
                        context.getApplicationContext(),
                        workTaskExecutor.getSerialTaskExecutor(),
                        useTestDatabase)
        );
    }

    /**
     * Create an instance of {@link WorkManagerImpl}.
     *
     * @param context          The application {@link Context}
     * @param configuration    The {@link Configuration} configuration
     * @param workTaskExecutor The {@link TaskExecutor} for running "processing" jobs, such as
     *                         enqueueing, scheduling, cancellation, etc.
     * @param database         The {@link WorkDatabase}
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public WorkManagerImpl(
            @NonNull Context context,
            @NonNull Configuration configuration,
            @NonNull TaskExecutor workTaskExecutor,
            @NonNull WorkDatabase database) {
        Context applicationContext = context.getApplicationContext();
        Logger.setLogger(new Logger.LogcatLogger(configuration.getMinimumLoggingLevel()));
        mTrackers = new Trackers(applicationContext, workTaskExecutor);
        List<Scheduler> schedulers =
                createSchedulers(applicationContext, configuration, mTrackers);
        Processor processor = new Processor(
                context,
                configuration,
                workTaskExecutor,
                database,
                schedulers);
        internalInit(context, configuration, workTaskExecutor, database, schedulers, processor);
    }

    /**
     * Create an instance of {@link WorkManagerImpl}.
     *
     * @param context The application {@link Context}
     * @param configuration The {@link Configuration} configuration
     * @param workTaskExecutor The {@link TaskExecutor} for running "processing" jobs, such as
     *                         enqueueing, scheduling, cancellation, etc.
     * @param workDatabase The {@link WorkDatabase} instance
     * @param processor The {@link Processor} instance
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public WorkManagerImpl(
            @NonNull Context context,
            @NonNull Configuration configuration,
            @NonNull TaskExecutor workTaskExecutor,
            @NonNull WorkDatabase workDatabase,
            @NonNull List<Scheduler> schedulers,
            @NonNull Processor processor) {
        mTrackers = new Trackers(context.getApplicationContext(), workTaskExecutor);
        internalInit(context, configuration, workTaskExecutor, workDatabase, schedulers, processor);
    }

    /**
     * @return The application {@link Context} associated with this WorkManager.
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    @NonNull
    public Context getApplicationContext() {
        return mContext;
    }

    /**
     * @return The {@link WorkDatabase} instance associated with this WorkManager.
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    @NonNull
    public WorkDatabase getWorkDatabase() {
        return mWorkDatabase;
    }

    /**
     * @return The {@link Configuration} instance associated with this WorkManager.
     */
    @NonNull
    @Override
    public Configuration getConfiguration() {
        return mConfiguration;
    }

    /**
     * @return The {@link Scheduler}s associated with this WorkManager based on the device's
     * capabilities, SDK version, etc.
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public @NonNull List<Scheduler> getSchedulers() {
        return mSchedulers;
    }

    /**
     * @return The {@link Processor} used to process background work.
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public @NonNull Processor getProcessor() {
        return mProcessor;
    }

    /**
     * @return the {@link TaskExecutor} used by the instance of {@link WorkManager}.
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public @NonNull TaskExecutor getWorkTaskExecutor() {
        return mWorkTaskExecutor;
    }

    /**
     * @return the {@link PreferenceUtils} used by the instance of {@link WorkManager}.
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public @NonNull PreferenceUtils getPreferenceUtils() {
        return mPreferenceUtils;
    }

    /**
     * @return the {@link Trackers} used by {@link WorkManager}
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    @NonNull
    public Trackers getTrackers() {
        return mTrackers;
    }

    @Override
    @NonNull
    public Operation enqueue(
            @NonNull List<? extends WorkRequest> requests) {

        // This error is not being propagated as part of the Operation, as we want the
        // app to crash during development. Having no workRequests is always a developer error.
        if (requests.isEmpty()) {
            throw new IllegalArgumentException(
                    "enqueue needs at least one WorkRequest.");
        }
        return new WorkContinuationImpl(this, requests).enqueue();
    }

    @Override
    public @NonNull WorkContinuation beginWith(@NonNull List<OneTimeWorkRequest> work) {
        if (work.isEmpty()) {
            throw new IllegalArgumentException(
                    "beginWith needs at least one OneTimeWorkRequest.");
        }
        return new WorkContinuationImpl(this, work);
    }

    @Override
    public @NonNull WorkContinuation beginUniqueWork(
            @NonNull String uniqueWorkName,
            @NonNull ExistingWorkPolicy existingWorkPolicy,
            @NonNull List<OneTimeWorkRequest> work) {
        if (work.isEmpty()) {
            throw new IllegalArgumentException(
                    "beginUniqueWork needs at least one OneTimeWorkRequest.");
        }
        return new WorkContinuationImpl(this, uniqueWorkName, existingWorkPolicy, work);
    }

    @NonNull
    @Override
    public Operation enqueueUniqueWork(@NonNull String uniqueWorkName,
            @NonNull ExistingWorkPolicy existingWorkPolicy,
            @NonNull List<OneTimeWorkRequest> work) {
        return new WorkContinuationImpl(this, uniqueWorkName, existingWorkPolicy, work).enqueue();
    }

    @Override
    @NonNull
    public Operation enqueueUniquePeriodicWork(
            @NonNull String uniqueWorkName,
            @NonNull ExistingPeriodicWorkPolicy existingPeriodicWorkPolicy,
            @NonNull PeriodicWorkRequest periodicWork) {

        return createWorkContinuationForUniquePeriodicWork(
                uniqueWorkName,
                existingPeriodicWorkPolicy,
                periodicWork)
                .enqueue();
    }

    /**
     * Creates a {@link WorkContinuation} for the given unique {@link PeriodicWorkRequest}.
     */
    @NonNull
    public WorkContinuationImpl createWorkContinuationForUniquePeriodicWork(
            @NonNull String uniqueWorkName,
            @NonNull ExistingPeriodicWorkPolicy existingPeriodicWorkPolicy,
            @NonNull PeriodicWorkRequest periodicWork) {
        ExistingWorkPolicy existingWorkPolicy;
        if (existingPeriodicWorkPolicy == ExistingPeriodicWorkPolicy.KEEP) {
            existingWorkPolicy = ExistingWorkPolicy.KEEP;
        } else {
            existingWorkPolicy = ExistingWorkPolicy.REPLACE;
        }
        return new WorkContinuationImpl(
                this,
                uniqueWorkName,
                existingWorkPolicy,
                Collections.singletonList(periodicWork));
    }

    @Override
    public @NonNull Operation cancelWorkById(@NonNull UUID id) {
        CancelWorkRunnable runnable = CancelWorkRunnable.forId(id, this);
        mWorkTaskExecutor.executeOnTaskThread(runnable);
        return runnable.getOperation();
    }

    @Override
    public @NonNull Operation cancelAllWorkByTag(@NonNull final String tag) {
        CancelWorkRunnable runnable = CancelWorkRunnable.forTag(tag, this);
        mWorkTaskExecutor.executeOnTaskThread(runnable);
        return runnable.getOperation();
    }

    @Override
    @NonNull
    public Operation cancelUniqueWork(@NonNull String uniqueWorkName) {
        CancelWorkRunnable runnable = CancelWorkRunnable.forName(uniqueWorkName, this, true);
        mWorkTaskExecutor.executeOnTaskThread(runnable);
        return runnable.getOperation();
    }

    @Override
    public @NonNull Operation cancelAllWork() {
        CancelWorkRunnable runnable = CancelWorkRunnable.forAll(this);
        mWorkTaskExecutor.executeOnTaskThread(runnable);
        return runnable.getOperation();
    }

    @NonNull
    @Override
    public PendingIntent createCancelPendingIntent(@NonNull UUID id) {
        Intent intent = createCancelWorkIntent(mContext, id.toString());
        int flags = FLAG_UPDATE_CURRENT;
        if (Build.VERSION.SDK_INT >= 31) {
            flags |= FLAG_MUTABLE;
        }
        return PendingIntent.getService(mContext, 0, intent, flags);
    }

    @Override
    public @NonNull LiveData<Long> getLastCancelAllTimeMillisLiveData() {
        return mPreferenceUtils.getLastCancelAllTimeMillisLiveData();
    }

    @Override
    public @NonNull ListenableFuture<Long> getLastCancelAllTimeMillis() {
        final SettableFuture<Long> future = SettableFuture.create();
        // Avoiding synthetic accessors.
        final PreferenceUtils preferenceUtils = mPreferenceUtils;
        mWorkTaskExecutor.executeOnTaskThread(new Runnable() {
            @Override
            public void run() {
                try {
                    future.set(preferenceUtils.getLastCancelAllTimeMillis());
                } catch (Throwable throwable) {
                    future.setException(throwable);
                }
            }
        });
        return future;
    }

    @Override
    public @NonNull Operation pruneWork() {
        PruneWorkRunnable runnable = new PruneWorkRunnable(this);
        mWorkTaskExecutor.executeOnTaskThread(runnable);
        return runnable.getOperation();
    }

    @Override
    public @NonNull LiveData<WorkInfo> getWorkInfoByIdLiveData(@NonNull UUID id) {
        WorkSpecDao dao = mWorkDatabase.workSpecDao();
        LiveData<List<WorkSpec.WorkInfoPojo>> inputLiveData =
                dao.getWorkStatusPojoLiveDataForIds(Collections.singletonList(id.toString()));
        return LiveDataUtils.dedupedMappedLiveDataFor(inputLiveData,
                new Function<List<WorkSpec.WorkInfoPojo>, WorkInfo>() {
                    @Override
                    public WorkInfo apply(List<WorkSpec.WorkInfoPojo> input) {
                        WorkInfo workInfo = null;
                        if (input != null && input.size() > 0) {
                            workInfo = input.get(0).toWorkInfo();
                        }
                        return workInfo;
                    }
                },
                mWorkTaskExecutor);
    }

    @Override
    public @NonNull ListenableFuture<WorkInfo> getWorkInfoById(@NonNull UUID id) {
        StatusRunnable<WorkInfo> runnable = StatusRunnable.forUUID(this, id);
        mWorkTaskExecutor.getSerialTaskExecutor().execute(runnable);
        return runnable.getFuture();
    }

    @Override
    public @NonNull LiveData<List<WorkInfo>> getWorkInfosByTagLiveData(@NonNull String tag) {
        WorkSpecDao workSpecDao = mWorkDatabase.workSpecDao();
        LiveData<List<WorkSpec.WorkInfoPojo>> inputLiveData =
                workSpecDao.getWorkStatusPojoLiveDataForTag(tag);
        return LiveDataUtils.dedupedMappedLiveDataFor(
                inputLiveData,
                WorkSpec.WORK_INFO_MAPPER,
                mWorkTaskExecutor);
    }

    @Override
    public @NonNull ListenableFuture<List<WorkInfo>> getWorkInfosByTag(@NonNull String tag) {
        StatusRunnable<List<WorkInfo>> runnable = StatusRunnable.forTag(this, tag);
        mWorkTaskExecutor.getSerialTaskExecutor().execute(runnable);
        return runnable.getFuture();
    }

    @Override
    @NonNull
    public LiveData<List<WorkInfo>> getWorkInfosForUniqueWorkLiveData(
            @NonNull String uniqueWorkName) {
        WorkSpecDao workSpecDao = mWorkDatabase.workSpecDao();
        LiveData<List<WorkSpec.WorkInfoPojo>> inputLiveData =
                workSpecDao.getWorkStatusPojoLiveDataForName(uniqueWorkName);
        return LiveDataUtils.dedupedMappedLiveDataFor(
                inputLiveData,
                WorkSpec.WORK_INFO_MAPPER,
                mWorkTaskExecutor);
    }

    @Override
    @NonNull
    public ListenableFuture<List<WorkInfo>> getWorkInfosForUniqueWork(
            @NonNull String uniqueWorkName) {
        StatusRunnable<List<WorkInfo>> runnable =
                StatusRunnable.forUniqueWork(this, uniqueWorkName);
        mWorkTaskExecutor.getSerialTaskExecutor().execute(runnable);
        return runnable.getFuture();
    }

    @NonNull
    @Override
    public LiveData<List<WorkInfo>> getWorkInfosLiveData(
            @NonNull WorkQuery workQuery) {
        RawWorkInfoDao rawWorkInfoDao = mWorkDatabase.rawWorkInfoDao();
        LiveData<List<WorkSpec.WorkInfoPojo>> inputLiveData =
                rawWorkInfoDao.getWorkInfoPojosLiveData(
                        RawQueries.toRawQuery(workQuery));
        return LiveDataUtils.dedupedMappedLiveDataFor(
                inputLiveData,
                WorkSpec.WORK_INFO_MAPPER,
                mWorkTaskExecutor);
    }

    @NonNull
    @Override
    public ListenableFuture<List<WorkInfo>> getWorkInfos(
            @NonNull WorkQuery workQuery) {
        StatusRunnable<List<WorkInfo>> runnable =
                StatusRunnable.forWorkQuerySpec(this, workQuery);
        mWorkTaskExecutor.getSerialTaskExecutor().execute(runnable);
        return runnable.getFuture();
    }

    LiveData<List<WorkInfo>> getWorkInfosById(@NonNull List<String> workSpecIds) {
        WorkSpecDao dao = mWorkDatabase.workSpecDao();
        LiveData<List<WorkSpec.WorkInfoPojo>> inputLiveData =
                dao.getWorkStatusPojoLiveDataForIds(workSpecIds);
        return LiveDataUtils.dedupedMappedLiveDataFor(
                inputLiveData,
                WorkSpec.WORK_INFO_MAPPER,
                mWorkTaskExecutor);
    }

    /**
     * @hide
     */
    @Nullable
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public RemoteWorkManager getRemoteWorkManager() {
        if (mRemoteWorkManager == null) {
            synchronized (sLock) {
                if (mRemoteWorkManager == null) {
                    // Initialize multi-process support.
                    tryInitializeMultiProcessSupport();
                    if (mRemoteWorkManager == null && !isEmpty(
                            mConfiguration.getDefaultProcessName())) {
                        String message = "Invalid multiprocess configuration. Define an "
                                + "`implementation` dependency on :work:work-multiprocess library";
                        throw new IllegalStateException(message);
                    }
                }
            }
        }
        return mRemoteWorkManager;
    }

    /**
     * @param workSpecId The {@link WorkSpec} id to start
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public void startWork(@NonNull String workSpecId) {
        startWork(workSpecId, null);
    }

    /**
     * @param workSpecId The {@link WorkSpec} id to start
     * @param runtimeExtras The {@link WorkerParameters.RuntimeExtras} associated with this work
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public void startWork(
            @NonNull String workSpecId,
            @Nullable WorkerParameters.RuntimeExtras runtimeExtras) {
        mWorkTaskExecutor
                .executeOnTaskThread(
                        new StartWorkRunnable(this, workSpecId, runtimeExtras));
    }

    /**
     * @param workSpecId The {@link WorkSpec} id to stop
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public void stopWork(@NonNull String workSpecId) {
        mWorkTaskExecutor.executeOnTaskThread(new StopWorkRunnable(this, workSpecId, false));
    }

    /**
     * @param workSpecId The {@link WorkSpec} id to stop when running in the context of a
     *                   foreground service.
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public void stopForegroundWork(@NonNull String workSpecId) {
        mWorkTaskExecutor.executeOnTaskThread(new StopWorkRunnable(this, workSpecId, true));
    }

    /**
     * Reschedules all the eligible work. Useful for cases like, app was force stopped or
     * BOOT_COMPLETED, TIMEZONE_CHANGED and TIME_SET for AlarmManager.
     *
     * @hide
     */
    public void rescheduleEligibleWork() {
        // TODO (rahulrav@) Make every scheduler do its own cancelAll().
        if (Build.VERSION.SDK_INT >= WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL) {
            SystemJobScheduler.cancelAll(getApplicationContext());
        }

        // Reset scheduled state.
        getWorkDatabase().workSpecDao().resetScheduledState();

        // Delegate to the WorkManager's schedulers.
        // Using getters here so we can use from a mocked instance
        // of WorkManagerImpl.
        Schedulers.schedule(getConfiguration(), getWorkDatabase(), getSchedulers());
    }

    /**
     * A way for {@link ForceStopRunnable} to tell {@link WorkManagerImpl} that it has completed.
     *
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public void onForceStopRunnableCompleted() {
        synchronized (sLock) {
            mForceStopRunnableCompleted = true;
            if (mRescheduleReceiverResult != null) {
                mRescheduleReceiverResult.finish();
                mRescheduleReceiverResult = null;
            }
        }
    }

    /**
     * This method is invoked by
     * {@link RescheduleReceiver}
     * after a call to {@link BroadcastReceiver#goAsync()}. Once {@link ForceStopRunnable} is done,
     * we can safely call {@link BroadcastReceiver.PendingResult#finish()}.
     *
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public void setReschedulePendingResult(
            @NonNull BroadcastReceiver.PendingResult rescheduleReceiverResult) {
        synchronized (sLock) {
            mRescheduleReceiverResult = rescheduleReceiverResult;
            if (mForceStopRunnableCompleted) {
                mRescheduleReceiverResult.finish();
                mRescheduleReceiverResult = null;
            }
        }
    }

    /**
     * Initializes an instance of {@link WorkManagerImpl}.
     *
     * @param context The application {@link Context}
     * @param configuration The {@link Configuration} configuration
     * @param workDatabase The {@link WorkDatabase} instance
     * @param schedulers The {@link List} of {@link Scheduler}s to use
     * @param processor The {@link Processor} instance
     */
    private void internalInit(@NonNull Context context,
            @NonNull Configuration configuration,
            @NonNull TaskExecutor workTaskExecutor,
            @NonNull WorkDatabase workDatabase,
            @NonNull List<Scheduler> schedulers,
            @NonNull Processor processor) {

        context = context.getApplicationContext();
        mContext = context;
        mConfiguration = configuration;
        mWorkTaskExecutor = workTaskExecutor;
        mWorkDatabase = workDatabase;
        mSchedulers = schedulers;
        mProcessor = processor;
        mPreferenceUtils = new PreferenceUtils(workDatabase);
        mForceStopRunnableCompleted = false;

        // Check for direct boot mode
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && Api24Impl.isDeviceProtectedStorage(
                context)) {
            throw new IllegalStateException("Cannot initialize WorkManager in direct boot mode");
        }

        // Checks for app force stops.
        mWorkTaskExecutor.executeOnTaskThread(new ForceStopRunnable(context, this));
    }

    /**
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    @NonNull
    public List<Scheduler> createSchedulers(
            @NonNull Context context,
            @NonNull Configuration configuration,
            @NonNull Trackers trackers
    ) {

        return Arrays.asList(
                Schedulers.createBestAvailableBackgroundScheduler(context, this),
                // Specify the task executor directly here as this happens before internalInit.
                // GreedyScheduler creates ConstraintTrackers and controllers eagerly.
                new GreedyScheduler(context, configuration, trackers, this));
    }

    /**
     * Tries to find a multi-process safe implementation for  {@link WorkManager}.
     */
    private void tryInitializeMultiProcessSupport() {
        try {
            Class<?> klass = Class.forName(REMOTE_WORK_MANAGER_CLIENT);
            mRemoteWorkManager = (RemoteWorkManager) klass.getConstructor(
                    Context.class, WorkManagerImpl.class
            ).newInstance(mContext, this);
        } catch (Throwable throwable) {
            Logger.get().debug(TAG, "Unable to initialize multi-process support", throwable);
        }
    }

    @RequiresApi(24)
    static class Api24Impl {
        private Api24Impl() {
            // This class is not instantiable.
        }

        @DoNotInline
        static boolean isDeviceProtectedStorage(Context context) {
            return context.isDeviceProtectedStorage();
        }
    }
}