1 /* 2 * Copyright (C) 2018 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.content.Context; 20 import android.os.Bundle; 21 import android.support.annotation.MainThread; 22 import android.support.annotation.NonNull; 23 import android.support.annotation.Nullable; 24 import android.support.v4.app.Fragment; 25 import android.support.v4.app.FragmentManager; 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 /** 36 * A headless fragment for use in UI components that interact with ListenableFutures. 37 * 38 * <p>Callbacks are only executed if the UI component is still alive. 39 * 40 * <p>Example usage: <code><pre> 41 * public class MyActivity extends AppCompatActivity { 42 * 43 * private SupportUiListener<MyOutputType> uiListener; 44 * 45 * public void onCreate(Bundle bundle) { 46 * super.onCreate(bundle); 47 * 48 * // Must be called in onCreate! 49 * uiListener = DialerExecutorComponent.get(context).createUiListener(fragmentManager, taskId); 50 * } 51 * 52 * private void onSuccess(MyOutputType output) { ... } 53 * private void onFailure(Throwable throwable) { ... } 54 * 55 * private void userDidSomething() { 56 * ListenableFuture<MyOutputType> future = callSomeMethodReturningListenableFuture(input); 57 * uiListener.listen(this, future, this::onSuccess, this::onFailure); 58 * } 59 * } 60 * </pre></code> 61 */ 62 public class SupportUiListener<OutputT> extends Fragment { 63 64 private CallbackWrapper<OutputT> callbackWrapper; 65 66 @MainThread create( FragmentManager fragmentManager, String taskId)67 static <OutputT> SupportUiListener<OutputT> create( 68 FragmentManager fragmentManager, String taskId) { 69 @SuppressWarnings("unchecked") 70 SupportUiListener<OutputT> uiListener = 71 (SupportUiListener<OutputT>) fragmentManager.findFragmentByTag(taskId); 72 73 if (uiListener == null) { 74 LogUtil.i("SupportUiListener.create", "creating new SupportUiListener for " + taskId); 75 uiListener = new SupportUiListener<>(); 76 // When launching an activity with the screen off, its onSaveInstanceState() is called before 77 // its fragments are created, which means we can't use commit() and need to use 78 // commitAllowingStateLoss(). This is not a problem for SupportUiListener which saves no 79 // state. 80 fragmentManager.beginTransaction().add(uiListener, taskId).commitAllowingStateLoss(); 81 } 82 return uiListener; 83 } 84 85 /** 86 * Adds the specified listeners to the provided future. 87 * 88 * <p>The listeners are not called if the UI component this {@link SupportUiListener} is declared 89 * in is dead. 90 */ 91 @MainThread listen( Context context, @NonNull ListenableFuture<OutputT> future, @NonNull SuccessListener<OutputT> successListener, @NonNull FailureListener failureListener)92 public void listen( 93 Context context, 94 @NonNull ListenableFuture<OutputT> future, 95 @NonNull SuccessListener<OutputT> successListener, 96 @NonNull FailureListener failureListener) { 97 callbackWrapper = 98 new CallbackWrapper<>(Assert.isNotNull(successListener), Assert.isNotNull(failureListener)); 99 Futures.addCallback( 100 Assert.isNotNull(future), 101 callbackWrapper, 102 DialerExecutorComponent.get(context).uiExecutor()); 103 } 104 105 private static class CallbackWrapper<OutputT> implements FutureCallback<OutputT> { 106 private SuccessListener<OutputT> successListener; 107 private FailureListener failureListener; 108 CallbackWrapper( SuccessListener<OutputT> successListener, FailureListener failureListener)109 private CallbackWrapper( 110 SuccessListener<OutputT> successListener, FailureListener failureListener) { 111 this.successListener = successListener; 112 this.failureListener = failureListener; 113 } 114 115 @Override onSuccess(@ullable OutputT output)116 public void onSuccess(@Nullable OutputT output) { 117 if (successListener == null) { 118 LogUtil.i("SupportUiListener.runTask", "task succeeded but UI is dead"); 119 } else { 120 successListener.onSuccess(output); 121 } 122 } 123 124 @Override onFailure(Throwable throwable)125 public void onFailure(Throwable throwable) { 126 LogUtil.e("SupportUiListener.runTask", "task failed", throwable); 127 if (failureListener == null) { 128 LogUtil.i("SupportUiListener.runTask", "task failed but UI is dead"); 129 } else { 130 failureListener.onFailure(throwable); 131 } 132 } 133 } 134 135 @Override onCreate(Bundle savedInstanceState)136 public void onCreate(Bundle savedInstanceState) { 137 super.onCreate(savedInstanceState); 138 setRetainInstance(true); 139 // Note: We use commitAllowingStateLoss when attaching the fragment so it may not be safe to 140 // read savedInstanceState in all situations. (But it's not anticipated that this fragment 141 // should need to rely on saved state.) 142 } 143 144 @Override onDetach()145 public void onDetach() { 146 super.onDetach(); 147 LogUtil.enterBlock("SupportUiListener.onDetach"); 148 if (callbackWrapper != null) { 149 callbackWrapper.successListener = null; 150 callbackWrapper.failureListener = null; 151 } 152 } 153 } 154 155