1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package com.android.dialer.common.concurrent; 18 19 import android.app.Fragment; 20 import android.app.FragmentManager; 21 import android.content.Context; 22 import android.os.Bundle; 23 import android.support.annotation.MainThread; 24 import android.support.annotation.NonNull; 25 import android.support.annotation.Nullable; 26 import com.android.dialer.common.Assert; 27 import com.android.dialer.common.LogUtil; 28 import com.android.dialer.common.concurrent.DialerExecutor.FailureListener; 29 import com.android.dialer.common.concurrent.DialerExecutor.SuccessListener; 30 import com.google.common.util.concurrent.FutureCallback; 31 import com.google.common.util.concurrent.Futures; 32 import com.google.common.util.concurrent.ListenableFuture; 33 34 /** 35 * A headless fragment for use in UI components that interact with ListenableFutures. 36 * 37 * <p>Callbacks are only executed if the UI component is still alive. 38 * 39 * <p>Example usage: <code><pre> 40 * public class MyActivity extends Activity { 41 * 42 * private UiListener<MyOutputType> uiListener; 43 * 44 * public void onCreate(Bundle bundle) { 45 * super.onCreate(bundle); 46 * 47 * // Must be called in onCreate! 48 * uiListener = DialerExecutorComponent.get(context).createUiListener(fragmentManager, taskId); 49 * } 50 * 51 * private void onSuccess(MyOutputType output) { ... } 52 * private void onFailure(Throwable throwable) { ... } 53 * 54 * private void userDidSomething() { 55 * ListenableFuture<MyOutputType> future = callSomeMethodReturningListenableFuture(input); 56 * uiListener.listen(this, future, this::onSuccess, this::onFailure); 57 * } 58 * } 59 * </pre></code> 60 */ 61 public class UiListener<OutputT> extends Fragment { 62 63 private CallbackWrapper<OutputT> callbackWrapper; 64 65 @MainThread 66 static <OutputT> UiListener<OutputT> create(FragmentManager fragmentManager, String taskId) { 67 @SuppressWarnings("unchecked") 68 UiListener<OutputT> uiListener = 69 (UiListener<OutputT>) fragmentManager.findFragmentByTag(taskId); 70 71 if (uiListener == null) { 72 LogUtil.i("UiListener.create", "creating new UiListener for " + taskId); 73 uiListener = new UiListener<>(); 74 // When launching an activity with the screen off, its onSaveInstanceState() is called before 75 // its fragments are created, which means we can't use commit() and need to use 76 // commitAllowingStateLoss(). This is not a problem for UiListener which saves no state. 77 fragmentManager.beginTransaction().add(uiListener, taskId).commitAllowingStateLoss(); 78 } 79 return uiListener; 80 } 81 82 /** 83 * Adds the specified listeners to the provided future. 84 * 85 * <p>The listeners are not called if the UI component this {@link UiListener} is declared in is 86 * dead. 87 */ 88 @MainThread 89 public void listen( 90 Context context, 91 @NonNull ListenableFuture<OutputT> future, 92 @NonNull SuccessListener<OutputT> successListener, 93 @NonNull FailureListener failureListener) { 94 callbackWrapper = 95 new CallbackWrapper<>(Assert.isNotNull(successListener), Assert.isNotNull(failureListener)); 96 Futures.addCallback( 97 Assert.isNotNull(future), 98 callbackWrapper, 99 DialerExecutorComponent.get(context).uiExecutor()); 100 } 101 102 private static class CallbackWrapper<OutputT> implements FutureCallback<OutputT> { 103 private SuccessListener<OutputT> successListener; 104 private FailureListener failureListener; 105 106 private CallbackWrapper( 107 SuccessListener<OutputT> successListener, FailureListener failureListener) { 108 this.successListener = successListener; 109 this.failureListener = failureListener; 110 } 111 112 @Override 113 public void onSuccess(@Nullable OutputT output) { 114 if (successListener == null) { 115 LogUtil.i("UiListener.runTask", "task succeeded but UI is dead"); 116 } else { 117 successListener.onSuccess(output); 118 } 119 } 120 121 @Override 122 public void onFailure(Throwable throwable) { 123 LogUtil.e("UiListener.runTask", "task failed", throwable); 124 if (failureListener == null) { 125 LogUtil.i("UiListener.runTask", "task failed but UI is dead"); 126 } else { 127 failureListener.onFailure(throwable); 128 } 129 } 130 } 131 132 @Override 133 public void onCreate(Bundle savedInstanceState) { 134 super.onCreate(savedInstanceState); 135 setRetainInstance(true); 136 // Note: We use commitAllowingStateLoss when attaching the fragment so it may not be safe to 137 // read savedInstanceState in all situations. (But it's not anticipated that this fragment 138 // should need to rely on saved state.) 139 } 140 141 @Override 142 public void onDetach() { 143 super.onDetach(); 144 LogUtil.enterBlock("UiListener.onDetach"); 145 if (callbackWrapper != null) { 146 callbackWrapper.successListener = null; 147 callbackWrapper.failureListener = null; 148 } 149 } 150 } 151