• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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