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.os.Bundle; 22 import android.support.annotation.MainThread; 23 import android.support.annotation.NonNull; 24 import android.support.annotation.Nullable; 25 import android.support.annotation.WorkerThread; 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.android.dialer.common.concurrent.DialerExecutor.Worker; 31 import java.util.concurrent.Executor; 32 import java.util.concurrent.ExecutorService; 33 import java.util.concurrent.ScheduledExecutorService; 34 import java.util.concurrent.ScheduledFuture; 35 import java.util.concurrent.TimeUnit; 36 37 /** 38 * Do not use this class directly. Instead use {@link DialerExecutors}. 39 * 40 * @param <InputT> the type of the object sent to the task upon execution 41 * @param <OutputT> the type of the result of the background computation 42 */ 43 public final class DialerUiTaskFragment<InputT, OutputT> extends Fragment { 44 45 private Worker<InputT, OutputT> worker; 46 private SuccessListener<OutputT> successListener; 47 private FailureListener failureListener; 48 49 private ScheduledExecutorService serialExecutor; 50 private Executor parallelExecutor; 51 private ScheduledFuture<?> scheduledFuture; 52 53 /** 54 * Creates a new {@link DialerUiTaskFragment} or gets an existing one in the event that a 55 * configuration change occurred while the previous activity's task was still running. Must be 56 * called from onCreate of your activity or fragment. 57 * 58 * @param taskId used for the headless fragment ID and task ID 59 * @param worker a function executed on a worker thread which accepts an {@link InputT} and 60 * returns an {@link OutputT}. It should ideally not be an inner class of your 61 * activity/fragment (meaning it should not be a lambda, anonymous, or non-static) but it can 62 * be a static nested class. The static nested class should not contain any reference to UI, 63 * including any activity or fragment or activity context, though it may reference some 64 * threadsafe system objects such as the application context. 65 * @param successListener a function executed on the main thread upon task success. There are no 66 * restraints on this as it is executed on the main thread, so lambdas, anonymous, or inner 67 * classes of your activity or fragment are all fine. 68 * @param failureListener a function executed on the main thread upon task failure. The exception 69 * is already logged so this can often be a no-op. There are no restraints on this as it is 70 * executed on the main thread, so lambdas, anonymous, or inner classes of your activity or 71 * fragment are all fine. 72 * @param <InputT> the type of the object sent to the task upon execution 73 * @param <OutputT> the type of the result of the background computation 74 * @return a {@link DialerUiTaskFragment} which may be used to call the "execute*" methods 75 */ 76 @MainThread create( FragmentManager fragmentManager, String taskId, Worker<InputT, OutputT> worker, SuccessListener<OutputT> successListener, FailureListener failureListener, @NonNull ScheduledExecutorService serialExecutorService, @NonNull Executor parallelExecutor)77 static <InputT, OutputT> DialerUiTaskFragment<InputT, OutputT> create( 78 FragmentManager fragmentManager, 79 String taskId, 80 Worker<InputT, OutputT> worker, 81 SuccessListener<OutputT> successListener, 82 FailureListener failureListener, 83 @NonNull ScheduledExecutorService serialExecutorService, 84 @NonNull Executor parallelExecutor) { 85 Assert.isMainThread(); 86 87 DialerUiTaskFragment<InputT, OutputT> fragment = 88 (DialerUiTaskFragment<InputT, OutputT>) fragmentManager.findFragmentByTag(taskId); 89 90 if (fragment == null) { 91 LogUtil.i("DialerUiTaskFragment.create", "creating new DialerUiTaskFragment for " + taskId); 92 fragment = new DialerUiTaskFragment<>(); 93 fragmentManager.beginTransaction().add(fragment, taskId).commit(); 94 } 95 fragment.worker = worker; 96 fragment.successListener = successListener; 97 fragment.failureListener = failureListener; 98 fragment.serialExecutor = Assert.isNotNull(serialExecutorService); 99 fragment.parallelExecutor = Assert.isNotNull(parallelExecutor); 100 return fragment; 101 } 102 103 @Override onCreate(Bundle savedInstanceState)104 public void onCreate(Bundle savedInstanceState) { 105 super.onCreate(savedInstanceState); 106 setRetainInstance(true); 107 } 108 109 @Override onDetach()110 public void onDetach() { 111 super.onDetach(); 112 LogUtil.enterBlock("DialerUiTaskFragment.onDetach"); 113 successListener = null; 114 failureListener = null; 115 if (scheduledFuture != null) { 116 scheduledFuture.cancel(false /* mayInterrupt */); 117 scheduledFuture = null; 118 } 119 } 120 executeSerial(InputT input)121 void executeSerial(InputT input) { 122 serialExecutor.execute(() -> runTask(input)); 123 } 124 executeSerialWithWait(InputT input, long waitMillis)125 void executeSerialWithWait(InputT input, long waitMillis) { 126 if (scheduledFuture != null) { 127 LogUtil.i("DialerUiTaskFragment.executeSerialWithWait", "cancelling waiting task"); 128 scheduledFuture.cancel(false /* mayInterrupt */); 129 } 130 scheduledFuture = 131 serialExecutor.schedule(() -> runTask(input), waitMillis, TimeUnit.MILLISECONDS); 132 } 133 executeParallel(InputT input)134 void executeParallel(InputT input) { 135 parallelExecutor.execute(() -> runTask(input)); 136 } 137 executeOnCustomExecutor(ExecutorService executor, InputT input)138 void executeOnCustomExecutor(ExecutorService executor, InputT input) { 139 executor.execute(() -> runTask(input)); 140 } 141 142 @WorkerThread runTask(@ullable InputT input)143 private void runTask(@Nullable InputT input) { 144 try { 145 OutputT output = worker.doInBackground(input); 146 if (successListener == null) { 147 LogUtil.i("DialerUiTaskFragment.runTask", "task succeeded but UI is dead"); 148 } else { 149 ThreadUtil.postOnUiThread( 150 () -> { 151 // Even though there is a null check above, it is possible for the activity/fragment 152 // to be finished between the time the runnable is posted and the time it executes. Do 153 // an additional check here. 154 if (successListener == null) { 155 LogUtil.i( 156 "DialerUiTaskFragment.runTask", 157 "task succeeded but UI died after success runnable posted"); 158 } else { 159 successListener.onSuccess(output); 160 } 161 }); 162 } 163 } catch (Throwable throwable) { 164 LogUtil.e("DialerUiTaskFragment.runTask", "task failed", throwable); 165 if (failureListener == null) { 166 LogUtil.i("DialerUiTaskFragment.runTask", "task failed but UI is dead"); 167 } else { 168 ThreadUtil.postOnUiThread( 169 () -> { 170 // Even though there is a null check above, it is possible for the activity/fragment 171 // to be finished between the time the runnable is posted and the time it executes. Do 172 // an additional check here. 173 if (failureListener == null) { 174 LogUtil.i( 175 "DialerUiTaskFragment.runTask", 176 "task failed but UI died after failure runnable posted"); 177 } else { 178 failureListener.onFailure(throwable); 179 } 180 }); 181 } 182 } 183 } 184 } 185