From 1632cfe1e3f3f94295289a62c69ec5a0489d8f13 Mon Sep 17 00:00:00 2001 From: roldenburg Date: Thu, 30 Nov 2017 11:16:31 -0800 Subject: *** Reason for rollback *** Copybara is fixed for AOSP export Bug: 68665330 Test: rollback PiperOrigin-RevId: 177480870 Change-Id: I0ba38e213bb840436fa6dafc4af0a79019ee93f4 --- .../common/concurrent/DialerExecutorComponent.java | 11 ++ .../dialer/common/concurrent/UiListener.java | 143 +++++++++++++++++++++ .../dialer/common/concurrent/UiThreadExecutor.java | 4 + 3 files changed, 158 insertions(+) create mode 100644 java/com/android/dialer/common/concurrent/UiListener.java (limited to 'java/com/android/dialer/common') diff --git a/java/com/android/dialer/common/concurrent/DialerExecutorComponent.java b/java/com/android/dialer/common/concurrent/DialerExecutorComponent.java index 346fdda56..7ee30a083 100644 --- a/java/com/android/dialer/common/concurrent/DialerExecutorComponent.java +++ b/java/com/android/dialer/common/concurrent/DialerExecutorComponent.java @@ -16,9 +16,12 @@ package com.android.dialer.common.concurrent; +import android.app.FragmentManager; import android.content.Context; import com.android.dialer.common.concurrent.Annotations.NonUiParallel; +import com.android.dialer.common.concurrent.Annotations.Ui; import com.android.dialer.inject.HasRootComponent; +import com.google.common.util.concurrent.ListeningExecutorService; import dagger.Subcomponent; import java.util.concurrent.ExecutorService; @@ -28,6 +31,14 @@ public abstract class DialerExecutorComponent { public abstract DialerExecutorFactory dialerExecutorFactory(); + @Ui + public abstract ListeningExecutorService uiExecutorService(); + + public UiListener createUiListener( + FragmentManager fragmentManager, String taskId) { + return UiListener.create(uiExecutorService(), fragmentManager, taskId); + } + @NonUiParallel public abstract ExecutorService lowPriorityThreadPool(); diff --git a/java/com/android/dialer/common/concurrent/UiListener.java b/java/com/android/dialer/common/concurrent/UiListener.java new file mode 100644 index 000000000..11302d299 --- /dev/null +++ b/java/com/android/dialer/common/concurrent/UiListener.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 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 com.android.dialer.common.concurrent; + +import android.app.Fragment; +import android.app.FragmentManager; +import android.os.Bundle; +import android.support.annotation.MainThread; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import com.android.dialer.common.Assert; +import com.android.dialer.common.LogUtil; +import com.android.dialer.common.concurrent.DialerExecutor.FailureListener; +import com.android.dialer.common.concurrent.DialerExecutor.SuccessListener; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import java.util.concurrent.Executor; + +/** + * A headless fragment for use in UI components that interact with ListenableFutures. + * + *

Callbacks are only executed if the UI component is still alive. + * + *

Example usage:

+ * public class MyActivity extends Activity {
+ *
+ *   private UiListener<MyOutputType> uiListener;
+ *
+ *   public void onCreate(Bundle bundle) {
+ *     super.onCreate(bundle);
+ *
+ *     // Must be called in onCreate!
+ *     uiListener = DialerExecutorComponent.get(context).createUiListener(fragmentManager, taskId);
+ *   }
+ *
+ *   private void onSuccess(MyOutputType output) { ... }
+ *   private void onFailure(Throwable throwable) { ... }
+ *
+ *   private void userDidSomething() {
+ *     ListenableFuture<MyOutputType> future = callSomeMethodReturningListenableFuture(input);
+ *     uiListener.listen(future, this::onSuccess, this::onFailure);
+ *   }
+ * }
+ * 
+ */ +public class UiListener extends Fragment { + + private Executor uiThreadExecutor; + private CallbackWrapper callbackWrapper; + + @MainThread + static UiListener create( + Executor uiThreadExecutor, FragmentManager fragmentManager, String taskId) { + @SuppressWarnings("unchecked") + UiListener uiListener = + (UiListener) fragmentManager.findFragmentByTag(taskId); + + if (uiListener == null) { + LogUtil.i("UiListener.create", "creating new UiListener for " + taskId); + uiListener = new UiListener<>(); + uiListener.uiThreadExecutor = uiThreadExecutor; + fragmentManager.beginTransaction().add(uiListener, taskId).commit(); + } + return uiListener; + } + + /** + * Adds the specified listeners to the provided future. + * + *

The listeners are not called if the UI component this {@link UiListener} is declared in is + * dead. + */ + @MainThread + public void listen( + @NonNull ListenableFuture future, + @NonNull SuccessListener successListener, + @NonNull FailureListener failureListener) { + callbackWrapper = + new CallbackWrapper<>(Assert.isNotNull(successListener), Assert.isNotNull(failureListener)); + Futures.addCallback(Assert.isNotNull(future), callbackWrapper, uiThreadExecutor); + } + + private static class CallbackWrapper implements FutureCallback { + private SuccessListener successListener; + private FailureListener failureListener; + + private CallbackWrapper( + SuccessListener successListener, FailureListener failureListener) { + this.successListener = successListener; + this.failureListener = failureListener; + } + + @Override + public void onSuccess(@Nullable OutputT output) { + if (successListener == null) { + LogUtil.i("UiListener.runTask", "task succeeded but UI is dead"); + } else { + successListener.onSuccess(output); + } + } + + @Override + public void onFailure(Throwable throwable) { + LogUtil.e("UiListener.runTask", "task failed", throwable); + if (failureListener == null) { + LogUtil.i("UiListener.runTask", "task failed but UI is dead"); + } else { + failureListener.onFailure(throwable); + } + } + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setRetainInstance(true); + } + + @Override + public void onDetach() { + super.onDetach(); + LogUtil.enterBlock("UiListener.onDetach"); + if (callbackWrapper != null) { + callbackWrapper.successListener = null; + callbackWrapper.failureListener = null; + } + } +} diff --git a/java/com/android/dialer/common/concurrent/UiThreadExecutor.java b/java/com/android/dialer/common/concurrent/UiThreadExecutor.java index 7b95d70ea..ec51ed334 100644 --- a/java/com/android/dialer/common/concurrent/UiThreadExecutor.java +++ b/java/com/android/dialer/common/concurrent/UiThreadExecutor.java @@ -21,6 +21,7 @@ import com.google.common.util.concurrent.SettableFuture; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; +import javax.inject.Inject; /** * An ExecutorService that delegates to the UI thread. Rejects attempts to shut down, and all @@ -29,6 +30,9 @@ import java.util.concurrent.TimeUnit; */ public class UiThreadExecutor extends AbstractListeningExecutorService { + @Inject + UiThreadExecutor() {} + @Override public void shutdown() { throw new UnsupportedOperationException(); -- cgit v1.2.3