public class

RemoteWorkManagerClient

extends RemoteWorkManager

 java.lang.Object

androidx.work.multiprocess.RemoteWorkManager

↳androidx.work.multiprocess.RemoteWorkManagerClient

Gradle dependencies

compile group: 'androidx.work', name: 'work-multiprocess', version: '2.10.0-alpha03'

  • groupId: androidx.work
  • artifactId: work-multiprocess
  • version: 2.10.0-alpha03

Artifact androidx.work:work-multiprocess:2.10.0-alpha03 it located at Google repository (https://maven.google.com/)

Overview

The implementation of the RemoteWorkManager which sets up the and dispatches the request.

Summary

Fields
public static final Function<UnknownReference, java.lang.Void>sVoidMapper

A mapper that essentially drops the byte[].

Constructors
publicRemoteWorkManagerClient(Context context, WorkManagerImpl workManager)

publicRemoteWorkManagerClient(Context context, WorkManagerImpl workManager, long sessionTimeout)

Methods
public abstract RemoteWorkContinuationbeginUniqueWork(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 RemoteWorkContinuationbeginWith(java.util.List<OneTimeWorkRequest> work)

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

public abstract <any>cancelAllWork()

Cancels all unfinished work.

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

Cancels all unfinished work with the given tag.

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

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

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

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

public voidcleanUp()

Cleans up a session.

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

Enqueues one or more items for background processing.

public abstract <any>enqueue(WorkContinuation continuation)

Enqueues the instance of WorkContinuation for background processing.

public abstract <any>enqueue(WorkRequest request)

Enqueues one item for background processing.

public abstract <any>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.

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

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

public <any>execute(RemoteDispatcher<IWorkManagerImpl> dispatcher)

Executes a RemoteDispatcher after having negotiated a service connection.

public ContextgetContext()

public RemoteWorkManagerClient.SessiongetCurrentSession()

public java.util.concurrent.ExecutorgetExecutor()

public <any>getSession()

Gets a handle to an instance of IWorkManagerImpl by binding to the RemoteWorkManagerService if necessary.

public longgetSessionIndex()

public java.lang.ObjectgetSessionLock()

public longgetSessionTimeout()

public RemoteWorkManagerClient.SessionTrackergetSessionTracker()

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>setForegroundAsync(java.lang.String id, ForegroundInfo foregroundInfo)

Delegates the call to ListenableWorker.setForegroundAsync(ForegroundInfo) to the designated process.

public abstract <any>setProgress(java.util.UUID id, Data data)

Updates progress information for a ListenableWorker.

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

Fields

public static final Function<UnknownReference, java.lang.Void> sVoidMapper

A mapper that essentially drops the byte[].

Constructors

public RemoteWorkManagerClient(Context context, WorkManagerImpl workManager)

public RemoteWorkManagerClient(Context context, WorkManagerImpl workManager, long sessionTimeout)

Methods

public abstract <any> enqueue(WorkRequest request)

Enqueues one item for background processing.

Parameters:

request: The WorkRequest to enqueue

Returns:

A that can be used to determine when the enqueue has completed

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

Enqueues one or more items for background processing.

Parameters:

requests: One or more WorkRequest to enqueue

Returns:

A that can be used to determine when the enqueue has completed

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

This method allows you to enqueue work requests to a uniquely-named RemoteWorkContinuation, 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 RemoteWorkContinuation.

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:

A that can be used to determine when the enqueue has completed

public abstract <any> 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 that can be used to determine when the enqueue has completed

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

Begins a chain with one or more OneTimeWorkRequests, which can be enqueued together in the future using RemoteWorkContinuation.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 RemoteWorkContinuation that allows for further chaining of dependent OneTimeWorkRequest

public abstract RemoteWorkContinuation 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 RemoteWorkContinuation that allows further chaining

public abstract <any> enqueue(WorkContinuation continuation)

Enqueues the instance of WorkContinuation for background processing.

Returns:

A that can be used to determine when the enqueue has completed

public abstract <any> 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:

A that can be used to determine when the cancelWorkById has completed

public abstract <any> 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 that can be used to determine when the cancelAllWorkByTag has completed

public abstract <any> 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:

A that can be used to determine when the cancelUniqueWork has completed

public abstract <any> 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:

A that can be used to determine when the cancelAllWork has completed

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 abstract <any> setProgress(java.util.UUID id, Data data)

Updates progress information for a ListenableWorker.

Parameters:

id: The WorkRequest id
data: The progress Data

Returns:

A that can be used to determine when the setProgress has completed.

public abstract <any> setForegroundAsync(java.lang.String id, ForegroundInfo foregroundInfo)

Delegates the call to ListenableWorker.setForegroundAsync(ForegroundInfo) to the designated process.

Parameters:

id: The WorkRequest id
foregroundInfo: THe ForegroundInfo instance

Returns:

A that can be used to determine when the setForeground has completed.

public <any> execute(RemoteDispatcher<IWorkManagerImpl> dispatcher)

Executes a RemoteDispatcher after having negotiated a service connection.

Parameters:

dispatcher: The RemoteDispatcher instance.

Returns:

The instance.

public <any> getSession()

Gets a handle to an instance of IWorkManagerImpl by binding to the RemoteWorkManagerService if necessary.

public Context getContext()

Returns:

The application .

public long getSessionTimeout()

Returns:

The session timeout in milliseconds.

public RemoteWorkManagerClient.Session getCurrentSession()

Returns:

The current RemoteWorkManagerClient.Session in use by RemoteWorkManagerClient.

public RemoteWorkManagerClient.SessionTracker getSessionTracker()

Returns:

the RemoteWorkManagerClient.SessionTracker instance.

public java.lang.Object getSessionLock()

Returns:

The java.lang.Object session lock.

public java.util.concurrent.Executor getExecutor()

Returns:

The background java.util.concurrent.Executor used by RemoteWorkManagerClient.

public long getSessionIndex()

Returns:

The session index.

public void cleanUp()

Cleans up a session. This could happen when we are unable to bind to the service or we get disconnected.

Source

/*
 * Copyright 2020 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.multiprocess;

import static android.content.Context.BIND_AUTO_CREATE;

import static androidx.work.multiprocess.RemoteClientUtilsKt.map;

import android.annotation.SuppressLint;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.annotation.VisibleForTesting;
import androidx.arch.core.util.Function;
import androidx.work.Data;
import androidx.work.ExistingPeriodicWorkPolicy;
import androidx.work.ExistingWorkPolicy;
import androidx.work.ForegroundInfo;
import androidx.work.Logger;
import androidx.work.OneTimeWorkRequest;
import androidx.work.PeriodicWorkRequest;
import androidx.work.RunnableScheduler;
import androidx.work.WorkContinuation;
import androidx.work.WorkInfo;
import androidx.work.WorkQuery;
import androidx.work.WorkRequest;
import androidx.work.impl.WorkContinuationImpl;
import androidx.work.impl.WorkManagerImpl;
import androidx.work.impl.utils.futures.SettableFuture;
import androidx.work.multiprocess.parcelable.ParcelConverters;
import androidx.work.multiprocess.parcelable.ParcelableForegroundRequestInfo;
import androidx.work.multiprocess.parcelable.ParcelableUpdateRequest;
import androidx.work.multiprocess.parcelable.ParcelableWorkContinuationImpl;
import androidx.work.multiprocess.parcelable.ParcelableWorkInfos;
import androidx.work.multiprocess.parcelable.ParcelableWorkQuery;
import androidx.work.multiprocess.parcelable.ParcelableWorkRequest;
import androidx.work.multiprocess.parcelable.ParcelableWorkRequests;

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

import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;

/**
 * The implementation of the {@link RemoteWorkManager} which sets up the
 * {@link android.content.ServiceConnection} and dispatches the request.
 */
@SuppressLint("BanKeepAnnotation")
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public class RemoteWorkManagerClient extends RemoteWorkManager {

    /* The session timeout. */
    private static final long SESSION_TIMEOUT_MILLIS = 6000 * 1000;

    // Synthetic access
    static final String TAG = Logger.tagWithPrefix("RemoteWorkManagerClient");

    /**
     * A mapper that essentially drops the byte[].
     */
    public static final Function<byte[], Void> sVoidMapper = input -> null;

    // Synthetic access
    Session mSession;

    final Context mContext;
    final WorkManagerImpl mWorkManager;
    final Executor mExecutor;
    final Object mLock;

    private volatile long mSessionIndex;
    private final long mSessionTimeout;
    private final RunnableScheduler mRunnableScheduler;
    private final SessionTracker mSessionTracker;

    public RemoteWorkManagerClient(@NonNull Context context, @NonNull WorkManagerImpl workManager) {
        this(context, workManager, SESSION_TIMEOUT_MILLIS);
    }

    public RemoteWorkManagerClient(
            @NonNull Context context,
            @NonNull WorkManagerImpl workManager,
            long sessionTimeout) {
        mContext = context.getApplicationContext();
        mWorkManager = workManager;
        mExecutor = mWorkManager.getWorkTaskExecutor().getSerialTaskExecutor();
        mLock = new Object();
        mSession = null;
        mSessionTracker = new SessionTracker(this);
        mSessionTimeout = sessionTimeout;
        mRunnableScheduler = mWorkManager.getConfiguration().getRunnableScheduler();
    }

    @NonNull
    @Override
    public ListenableFuture<Void> enqueue(@NonNull WorkRequest request) {
        return enqueue(Collections.singletonList(request));
    }

    @NonNull
    @Override
    public ListenableFuture<Void> enqueue(@NonNull final List<WorkRequest> requests) {
        ListenableFuture<byte[]> result = execute(new RemoteDispatcher<IWorkManagerImpl>() {
            @Override
            public void execute(
                    @NonNull IWorkManagerImpl iWorkManagerImpl,
                    @NonNull IWorkManagerImplCallback callback) throws RemoteException {
                byte[] request = ParcelConverters.marshall(new ParcelableWorkRequests(requests));
                iWorkManagerImpl.enqueueWorkRequests(request, callback);
            }
        });
        return map(result, sVoidMapper, mExecutor);
    }

    @NonNull
    @Override
    public ListenableFuture<Void> enqueueUniqueWork(
            @NonNull String uniqueWorkName,
            @NonNull ExistingWorkPolicy existingWorkPolicy,
            @NonNull List<OneTimeWorkRequest> work) {
        return beginUniqueWork(uniqueWorkName, existingWorkPolicy, work).enqueue();
    }

    @NonNull
    @Override
    public ListenableFuture<Void> enqueueUniquePeriodicWork(
            @NonNull String uniqueWorkName,
            @NonNull ExistingPeriodicWorkPolicy existingPeriodicWorkPolicy,
            @NonNull PeriodicWorkRequest periodicWork) {
        if (existingPeriodicWorkPolicy == ExistingPeriodicWorkPolicy.UPDATE) {
            ListenableFuture<byte[]> result = execute((iWorkManagerImpl, callback) -> {
                byte[] request = ParcelConverters.marshall(new ParcelableWorkRequest(periodicWork));
                iWorkManagerImpl.updateUniquePeriodicWorkRequest(uniqueWorkName, request, callback);
            });
            return map(result, sVoidMapper, mExecutor);
        }
        WorkContinuation continuation = mWorkManager.createWorkContinuationForUniquePeriodicWork(
                uniqueWorkName,
                existingPeriodicWorkPolicy,
                periodicWork
        );
        return enqueue(continuation);
    }

    @NonNull
    @Override
    public RemoteWorkContinuation beginWith(@NonNull List<OneTimeWorkRequest> work) {
        return new RemoteWorkContinuationImpl(this, mWorkManager.beginWith(work));
    }

    @NonNull
    @Override
    public RemoteWorkContinuation beginUniqueWork(
            @NonNull String uniqueWorkName,
            @NonNull ExistingWorkPolicy existingWorkPolicy,
            @NonNull List<OneTimeWorkRequest> work) {
        return new RemoteWorkContinuationImpl(this,
                mWorkManager.beginUniqueWork(uniqueWorkName, existingWorkPolicy, work));
    }

    @NonNull
    @Override
    public ListenableFuture<Void> enqueue(@NonNull final WorkContinuation continuation) {
        ListenableFuture<byte[]> result = execute(new RemoteDispatcher<IWorkManagerImpl>() {
            @Override
            public void execute(@NonNull IWorkManagerImpl iWorkManagerImpl,
                    @NonNull IWorkManagerImplCallback callback) throws Throwable {
                WorkContinuationImpl workContinuation = (WorkContinuationImpl) continuation;
                byte[] request = ParcelConverters.marshall(
                        new ParcelableWorkContinuationImpl(workContinuation));
                iWorkManagerImpl.enqueueContinuation(request, callback);
            }
        });
        return map(result, sVoidMapper, mExecutor);
    }

    @NonNull
    @Override
    public ListenableFuture<Void> cancelWorkById(@NonNull final UUID id) {
        ListenableFuture<byte[]> result = execute(new RemoteDispatcher<IWorkManagerImpl>() {
            @Override
            public void execute(@NonNull IWorkManagerImpl iWorkManagerImpl,
                    @NonNull IWorkManagerImplCallback callback) throws Throwable {
                iWorkManagerImpl.cancelWorkById(id.toString(), callback);
            }
        });
        return map(result, sVoidMapper, mExecutor);
    }

    @NonNull
    @Override
    public ListenableFuture<Void> cancelAllWorkByTag(@NonNull final String tag) {
        ListenableFuture<byte[]> result = execute(new RemoteDispatcher<IWorkManagerImpl>() {
            @Override
            public void execute(@NonNull IWorkManagerImpl iWorkManagerImpl,
                    @NonNull IWorkManagerImplCallback callback) throws Throwable {
                iWorkManagerImpl.cancelAllWorkByTag(tag, callback);
            }
        });
        return map(result, sVoidMapper, mExecutor);
    }

    @NonNull
    @Override
    public ListenableFuture<Void> cancelUniqueWork(@NonNull final String uniqueWorkName) {
        ListenableFuture<byte[]> result = execute(new RemoteDispatcher<IWorkManagerImpl>() {
            @Override
            public void execute(@NonNull IWorkManagerImpl iWorkManagerImpl,
                    @NonNull IWorkManagerImplCallback callback) throws Throwable {
                iWorkManagerImpl.cancelUniqueWork(uniqueWorkName, callback);
            }
        });
        return map(result, sVoidMapper, mExecutor);
    }

    @NonNull
    @Override
    public ListenableFuture<Void> cancelAllWork() {
        ListenableFuture<byte[]> result = execute(new RemoteDispatcher<IWorkManagerImpl>() {
            @Override
            public void execute(@NonNull IWorkManagerImpl iWorkManagerImpl,
                    @NonNull IWorkManagerImplCallback callback) throws Throwable {
                iWorkManagerImpl.cancelAllWork(callback);
            }
        });
        return map(result, sVoidMapper, mExecutor);
    }

    @NonNull
    @Override
    public ListenableFuture<List<WorkInfo>> getWorkInfos(@NonNull final WorkQuery workQuery) {
        ListenableFuture<byte[]> result = execute(new RemoteDispatcher<IWorkManagerImpl>() {
            @Override
            public void execute(
                    @NonNull IWorkManagerImpl iWorkManagerImpl,
                    @NonNull IWorkManagerImplCallback callback) throws Throwable {
                byte[] request = ParcelConverters.marshall(new ParcelableWorkQuery(workQuery));
                iWorkManagerImpl.queryWorkInfo(request, callback);
            }
        });
        return map(result, new Function<byte[], List<WorkInfo>>() {
            @Override
            public List<WorkInfo> apply(byte[] input) {
                ParcelableWorkInfos infos =
                        ParcelConverters.unmarshall(input, ParcelableWorkInfos.CREATOR);
                return infos.getWorkInfos();
            }
        }, mExecutor);
    }

    @NonNull
    @Override
    public ListenableFuture<Void> setProgress(@NonNull final UUID id, @NonNull final Data data) {
        ListenableFuture<byte[]> result = execute(new RemoteDispatcher<IWorkManagerImpl>() {
            @Override
            public void execute(
                    @NonNull IWorkManagerImpl iWorkManagerImpl,
                    @NonNull IWorkManagerImplCallback callback) throws Throwable {
                byte[] request = ParcelConverters.marshall(new ParcelableUpdateRequest(id, data));
                iWorkManagerImpl.setProgress(request, callback);
            }
        });
        return map(result, sVoidMapper, mExecutor);
    }

    @NonNull
    @Override
    public ListenableFuture<Void> setForegroundAsync(
            @NonNull String id,
            @NonNull ForegroundInfo foregroundInfo) {
        ListenableFuture<byte[]> result = execute(new RemoteDispatcher<IWorkManagerImpl>() {
            @Override
            public void execute(
                    @NonNull IWorkManagerImpl iWorkManagerImpl,
                    @NonNull IWorkManagerImplCallback callback) throws Throwable {
                byte[] request = ParcelConverters.marshall(
                        new ParcelableForegroundRequestInfo(id, foregroundInfo));
                iWorkManagerImpl.setForegroundAsync(request, callback);
            }
        });
        return map(result, sVoidMapper, mExecutor);
    }

    /**
     * Executes a {@link RemoteDispatcher} after having negotiated a service connection.
     *
     * @param dispatcher The {@link RemoteDispatcher} instance.
     * @return The {@link ListenableFuture} instance.
     */
    @NonNull
    public ListenableFuture<byte[]> execute(
            @NonNull final RemoteDispatcher<IWorkManagerImpl> dispatcher) {
        return execute(getSession(), dispatcher);
    }

    /**
     * Gets a handle to an instance of {@link IWorkManagerImpl} by binding to the
     * {@link RemoteWorkManagerService} if necessary.
     */
    @NonNull
    public ListenableFuture<IWorkManagerImpl> getSession() {
        return getSession(newIntent(mContext));
    }

    /**
     * @return The application {@link Context}.
     */
    @NonNull
    public Context getContext() {
        return mContext;
    }

    /**
     * @return The session timeout in milliseconds.
     */
    public long getSessionTimeout() {
        return mSessionTimeout;
    }

    /**
     * @return The current {@link Session} in use by {@link RemoteWorkManagerClient}.
     */
    @Nullable
    public Session getCurrentSession() {
        return mSession;
    }

    /**
     * @return the {@link SessionTracker} instance.
     */
    @NonNull
    public SessionTracker getSessionTracker() {
        return mSessionTracker;
    }

    /**
     * @return The {@link Object} session lock.
     */
    @NonNull
    public Object getSessionLock() {
        return mLock;
    }

    /**
     * @return The background {@link Executor} used by {@link RemoteWorkManagerClient}.
     */
    @NonNull
    public Executor getExecutor() {
        return mExecutor;
    }

    /**
     * @return The session index.
     */
    public long getSessionIndex() {
        return mSessionIndex;
    }

    @NonNull
    @VisibleForTesting
    ListenableFuture<byte[]> execute(
            @NonNull final ListenableFuture<IWorkManagerImpl> session,
            @NonNull final RemoteDispatcher<IWorkManagerImpl> dispatcher) {
        session.addListener(() -> {
            try {
                session.get();
            } catch (ExecutionException | InterruptedException exception) {
                cleanUp();
            }
        }, mExecutor);
        ListenableFuture<byte[]> future = RemoteExecuteKt.execute(mExecutor, session,
                dispatcher);
        future.addListener(() -> {
            SessionTracker tracker = getSessionTracker();
            // Start tracking for session timeout.
            // These callbacks are removed when the session timeout has expired or when getSession()
            // is called.
            mRunnableScheduler.scheduleWithDelay(getSessionTimeout(), tracker);
        }, mExecutor);
        return future;
    }

    @NonNull
    @VisibleForTesting
    ListenableFuture<IWorkManagerImpl> getSession(@NonNull Intent intent) {
        synchronized (mLock) {
            mSessionIndex += 1;
            if (mSession == null) {
                Logger.get().debug(TAG, "Creating a new session");
                mSession = new Session(this);
                try {
                    boolean bound = mContext.bindService(intent, mSession, BIND_AUTO_CREATE);
                    if (!bound) {
                        unableToBind(mSession, new RuntimeException("Unable to bind to service"));
                    }
                } catch (Throwable throwable) {
                    unableToBind(mSession, throwable);
                }
            }
            // Reset session tracker.
            mRunnableScheduler.cancel(mSessionTracker);
            return mSession.mFuture;
        }
    }

    /**
     * Cleans up a session. This could happen when we are unable to bind to the service or
     * we get disconnected.
     */
    public void cleanUp() {
        synchronized (mLock) {
            Logger.get().debug(TAG, "Cleaning up.");
            mSession = null;
        }
    }

    private void unableToBind(@NonNull Session session, @NonNull Throwable throwable) {
        Logger.get().error(TAG, "Unable to bind to service", throwable);
        session.mFuture.setException(throwable);
    }

    /**
     * @return the intent that is used to bind to the instance of {@link IWorkManagerImpl}.
     */
    private static Intent newIntent(@NonNull Context context) {
        return new Intent(context, RemoteWorkManagerService.class);
    }

    /**
     * The implementation of {@link ServiceConnection} that handles changes in the connection.
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public static class Session implements ServiceConnection {
        private static final String TAG = Logger.tagWithPrefix("RemoteWMgr.Connection");

        final SettableFuture<IWorkManagerImpl> mFuture;
        final RemoteWorkManagerClient mClient;

        public Session(@NonNull RemoteWorkManagerClient client) {
            mClient = client;
            mFuture = SettableFuture.create();
        }

        @Override
        public void onServiceConnected(
                @NonNull ComponentName componentName,
                @NonNull IBinder iBinder) {
            Logger.get().debug(TAG, "Service connected");
            IWorkManagerImpl iWorkManagerImpl = IWorkManagerImpl.Stub.asInterface(iBinder);
            mFuture.set(iWorkManagerImpl);
        }

        @Override
        public void onServiceDisconnected(@NonNull ComponentName componentName) {
            Logger.get().debug(TAG, "Service disconnected");
            mFuture.setException(new RuntimeException("Service disconnected"));
            mClient.cleanUp();
        }

        @Override
        public void onBindingDied(@NonNull ComponentName name) {
            onBindingDied();
        }

        /**
         * Clean-up client when a binding dies.
         */
        public void onBindingDied() {
            Logger.get().debug(TAG, "Binding died");
            mFuture.setException(new RuntimeException("Binding died"));
            mClient.cleanUp();
        }

        @Override
        public void onNullBinding(@NonNull ComponentName name) {
            Logger.get().error(TAG, "Unable to bind to service");
            mFuture.setException(
                    new RuntimeException("Cannot bind to service " + name));
        }
    }

    /**
     * A {@link Runnable} that enforces a TTL for a {@link RemoteWorkManagerClient} session.
     */
    public static class SessionTracker implements Runnable {
        private static final String TAG = Logger.tagWithPrefix("SessionHandler");
        private final RemoteWorkManagerClient mClient;

        public SessionTracker(@NonNull RemoteWorkManagerClient client) {
            mClient = client;
        }

        @Override
        public void run() {
            final long preLockIndex = mClient.getSessionIndex();
            synchronized (mClient.getSessionLock()) {
                final long sessionIndex = mClient.getSessionIndex();
                final Session currentSession = mClient.getCurrentSession();
                // We check for a session index here. This is because if the index changes
                // while we acquire a lock, that would mean that a new session request came through.
                if (currentSession != null) {
                    if (preLockIndex == sessionIndex) {
                        Logger.get().debug(TAG, "Unbinding service");
                        mClient.getContext().unbindService(currentSession);
                        // Cleanup as well.
                        currentSession.onBindingDied();
                    } else {
                        Logger.get().debug(TAG, "Ignoring request to unbind.");
                    }
                }
            }
        }
    }
}