Merge "Revert "Rewrite ServiceBinding to coroutines"" into androidx-main
diff --git a/work/work-multiprocess/src/main/java/androidx/work/multiprocess/ListenableWorkerImplClient.java b/work/work-multiprocess/src/main/java/androidx/work/multiprocess/ListenableWorkerImplClient.java
index 121fbfd..170c1b2 100644
--- a/work/work-multiprocess/src/main/java/androidx/work/multiprocess/ListenableWorkerImplClient.java
+++ b/work/work-multiprocess/src/main/java/androidx/work/multiprocess/ListenableWorkerImplClient.java
@@ -16,8 +16,6 @@
package androidx.work.multiprocess;
-import static androidx.work.multiprocess.ServiceBindingKt.bindToService;
-
import android.annotation.SuppressLint;
import android.content.ComponentName;
import android.content.Context;
@@ -29,6 +27,7 @@
import androidx.annotation.RestrictTo;
import androidx.annotation.VisibleForTesting;
import androidx.work.Logger;
+import androidx.work.multiprocess.ServiceBinding.Session;
import com.google.common.util.concurrent.ListenableFuture;
@@ -75,10 +74,10 @@
+ component.getClassName());
Intent intent = new Intent();
intent.setComponent(component);
- mConnection = bindToService(mContext, intent,
+ mConnection = ServiceBinding.bindToService(mContext, intent,
IListenableWorkerImpl.Stub::asInterface, TAG);
}
- return mConnection.getConnectedFuture();
+ return mConnection.mConnectedFuture;
}
}
diff --git a/work/work-multiprocess/src/main/java/androidx/work/multiprocess/RemoteWorkManagerClient.java b/work/work-multiprocess/src/main/java/androidx/work/multiprocess/RemoteWorkManagerClient.java
index 9d092ad..d6e4801 100644
--- a/work/work-multiprocess/src/main/java/androidx/work/multiprocess/RemoteWorkManagerClient.java
+++ b/work/work-multiprocess/src/main/java/androidx/work/multiprocess/RemoteWorkManagerClient.java
@@ -43,6 +43,7 @@
import androidx.work.WorkRequest;
import androidx.work.impl.WorkContinuationImpl;
import androidx.work.impl.WorkManagerImpl;
+import androidx.work.multiprocess.ServiceBinding.Session;
import androidx.work.multiprocess.parcelable.ParcelConverters;
import androidx.work.multiprocess.parcelable.ParcelableForegroundRequestInfo;
import androidx.work.multiprocess.parcelable.ParcelableUpdateRequest;
@@ -79,7 +80,7 @@
public static final Function<byte[], Void> sVoidMapper = input -> null;
// Synthetic access
- Session<IWorkManagerImpl> mSession;
+ ServiceBinding.Session<IWorkManagerImpl> mSession;
final Context mContext;
final WorkManagerImpl mWorkManager;
@@ -339,7 +340,7 @@
* @return The current {@link Session} in use by {@link RemoteWorkManagerClient}.
*/
@Nullable
- Session<IWorkManagerImpl> getCurrentSession() {
+ ServiceBinding.Session<IWorkManagerImpl> getCurrentSession() {
return mSession;
}
@@ -398,15 +399,14 @@
mSessionIndex += 1;
ListenableFuture<IWorkManagerImpl> resultFuture;
if (mSession == null) {
- mSession = ServiceBindingKt.bindToService(mContext, intent,
+ mSession = ServiceBinding.bindToService(mContext, intent,
IWorkManagerImpl.Stub::asInterface, TAG);
// reading future right away, because `this::cleanUp` will synchronously
// set mSession to null.
- resultFuture = mSession.getConnectedFuture();
- mSession.getDisconnectedFuture()
- .addListener(this::cleanUp, DirectExecutor.INSTANCE);
+ resultFuture = mSession.mConnectedFuture;
+ mSession.mDisconnectedFuture.addListener(this::cleanUp, DirectExecutor.INSTANCE);
} else {
- resultFuture = mSession.getConnectedFuture();
+ resultFuture = mSession.mConnectedFuture;
}
// Reset session tracker.
mRunnableScheduler.cancel(mSessionTracker);
diff --git a/work/work-multiprocess/src/main/java/androidx/work/multiprocess/ServiceBinding.java b/work/work-multiprocess/src/main/java/androidx/work/multiprocess/ServiceBinding.java
new file mode 100644
index 0000000..cad2faf
--- /dev/null
+++ b/work/work-multiprocess/src/main/java/androidx/work/multiprocess/ServiceBinding.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2024 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 android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.IInterface;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.core.util.Function;
+import androidx.work.Logger;
+import androidx.work.impl.utils.futures.SettableFuture;
+
+class ServiceBinding {
+
+ private ServiceBinding() {
+ }
+
+ static <T extends IInterface> Session<T> bindToService(@NonNull Context context,
+ @NonNull Intent intent,
+ @NonNull Function<IBinder, T> asInterface,
+ @NonNull String loggingTag) {
+ Logger.get().debug(loggingTag, "Binding via " + intent);
+ Session<T> session = new Session<>(loggingTag, asInterface);
+ try {
+ boolean bound = context.bindService(intent, session, BIND_AUTO_CREATE);
+ if (!bound) {
+ session.resolveClosedConnection(new RuntimeException("Unable to bind to service"));
+ }
+ } catch (Throwable throwable) {
+ session.resolveClosedConnection(throwable);
+ }
+ return session;
+ }
+
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ static class Session<T extends IInterface> implements ServiceConnection {
+ private final String mLogTag;
+ final SettableFuture<T> mConnectedFuture;
+ final SettableFuture<T> mDisconnectedFuture;
+ final Function<IBinder, T> mAsInterface;
+
+ public Session(String loggingTag, @NonNull Function<IBinder, T> asInterface) {
+ mAsInterface = asInterface;
+ mConnectedFuture = SettableFuture.create();
+ mDisconnectedFuture = SettableFuture.create();
+ mLogTag = loggingTag;
+ }
+
+ @Override
+ public void onServiceConnected(
+ @NonNull ComponentName componentName,
+ @NonNull IBinder iBinder) {
+ mConnectedFuture.set(mAsInterface.apply(iBinder));
+ }
+
+ @Override
+ public void onServiceDisconnected(@NonNull ComponentName componentName) {
+ Logger.get().debug(mLogTag, "Service disconnected");
+ resolveClosedConnection(new RuntimeException("Service disconnected"));
+ }
+
+ @Override
+ public void onBindingDied(@NonNull ComponentName name) {
+ onBindingDied();
+ }
+
+ /**
+ * Clean-up client when a binding dies.
+ */
+ public void onBindingDied() {
+ Logger.get().debug(mLogTag, "Binding died");
+ resolveClosedConnection(new RuntimeException("Binding died"));
+ }
+
+ @Override
+ public void onNullBinding(@NonNull ComponentName name) {
+ Logger.get().error(mLogTag, "Unable to bind to service");
+ resolveClosedConnection(new RuntimeException("Cannot bind to service " + name));
+ }
+
+ private void resolveClosedConnection(Throwable throwable) {
+ // finishing connected future, in case onServiceDisconnected hasn't been
+ // called.
+ mConnectedFuture.setException(throwable);
+ mDisconnectedFuture.set(null);
+ }
+ }
+}
diff --git a/work/work-multiprocess/src/main/java/androidx/work/multiprocess/ServiceBinding.kt b/work/work-multiprocess/src/main/java/androidx/work/multiprocess/ServiceBinding.kt
deleted file mode 100644
index 870d0d3..0000000
--- a/work/work-multiprocess/src/main/java/androidx/work/multiprocess/ServiceBinding.kt
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright 2024 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 android.content.ComponentName
-import android.content.Context
-import android.content.Intent
-import android.content.ServiceConnection
-import android.os.IBinder
-import android.os.IInterface
-import androidx.concurrent.futures.SuspendToFutureAdapter.launchFuture
-import androidx.core.util.Function
-import androidx.work.Logger
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.first
-
-internal fun <T : IInterface?> bindToService(
- context: Context,
- intent: Intent,
- asInterface: Function<IBinder?, T>,
- loggingTag: String
-): Session<T> {
- Logger.get().debug(loggingTag, "Binding via $intent")
-
- val session = Session(loggingTag, asInterface)
- try {
- val bound = context.bindService(intent, session, Context.BIND_AUTO_CREATE)
- if (!bound) {
- session.resolveClosedConnection(RuntimeException("Unable to bind to service"))
- }
- } catch (throwable: Throwable) {
- session.resolveClosedConnection(throwable)
- }
- return session
-}
-
-internal class Session<T : IInterface?>(
- private val logTag: String,
- private val asInterface: Function<IBinder?, T>
-) : ServiceConnection {
-
- sealed class State {
- object Created : State()
- class Connected(val iBinder: IBinder) : State()
- class Disconnected(val throwable: Throwable) : State()
- }
-
- private val stateFlow = MutableStateFlow<State>(State.Created)
-
- val connectedFuture = launchFuture<T>(Dispatchers.Unconfined) {
- val state = stateFlow.first { it != State.Created }
- if (state is State.Connected) {
- asInterface.apply(state.iBinder)
- } else {
- // we can go straight to disconnected state if we failed to bind
- throw (state as State.Disconnected).throwable
- }
- }
-
- val disconnectedFuture = launchFuture<Unit> {
- stateFlow.first { it is State.Disconnected }
- }
-
- override fun onServiceConnected(componentName: ComponentName, iBinder: IBinder) {
- stateFlow.value = State.Connected(iBinder)
- }
-
- override fun onServiceDisconnected(componentName: ComponentName) {
- Logger.get().debug(logTag, "Service disconnected")
- resolveClosedConnection(RuntimeException("Service disconnected"))
- }
-
- override fun onBindingDied(name: ComponentName) {
- onBindingDied()
- }
-
- /**
- * Clean-up client when a binding dies.
- */
- fun onBindingDied() {
- Logger.get().debug(logTag, "Binding died")
- resolveClosedConnection(RuntimeException("Binding died"))
- }
-
- override fun onNullBinding(name: ComponentName) {
- Logger.get().error(logTag, "Unable to bind to service")
- resolveClosedConnection(RuntimeException("Cannot bind to service $name"))
- }
-
- fun resolveClosedConnection(throwable: Throwable) {
- stateFlow.value = State.Disconnected(throwable)
- }
-}