OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 package org.chromium.chrome.browser.bookmark; |
| 6 |
| 7 import android.app.Activity; |
| 8 import android.app.Fragment; |
| 9 import android.app.ProgressDialog; |
| 10 import android.os.AsyncTask; |
| 11 import android.os.Bundle; |
| 12 import android.os.Handler; |
| 13 |
| 14 /** |
| 15 * Fragment that allows running asynchronous tasks showing a progress dialog if
it takes too long. |
| 16 * Since each task blocks the UI in an informative way, only one task is allowed
at once. |
| 17 * The fragment is retained in order to correctly support screen rotation change
s and to prevent |
| 18 * tasks to run multiple times. Tasks are automatically canceled if the fragment
is destroyed. |
| 19 * This class assumes that all its public methods will be called from the UI thr
ead. |
| 20 * |
| 21 * The main purpose of this class is to allow fragments to correcly use our prov
ider considering |
| 22 * that it should never been called from the UI thread. |
| 23 */ |
| 24 public class AsyncTaskFragment extends Fragment { |
| 25 /** |
| 26 * Delay in milliseconds introduced before showing the progress dialog. |
| 27 * Introduced in order to avoid flickering for short operations. |
| 28 */ |
| 29 private static final int DELAY_BEFORE_PROGRESS_DIALOG_MS = 300; |
| 30 |
| 31 /** |
| 32 * Minimum time in miliseconds the dialog stays if shown. |
| 33 * This will artificially delay the result of the asynchronous tasks. |
| 34 * Will be ignored if the task is cancelled. |
| 35 */ |
| 36 private static final int MINIMUM_DIALOG_STAY_MS = 500; |
| 37 |
| 38 private final Handler mHandler = new Handler(); |
| 39 private ProgressDialog mProgressDialog; |
| 40 private String mDialogMessage; |
| 41 private boolean mShouldShowDialog; |
| 42 private boolean mHasDialogStayedEnough; |
| 43 protected FragmentAsyncTask mCurrentTask; |
| 44 |
| 45 private final Runnable mShowProgressDialog = new Runnable() { |
| 46 @Override |
| 47 public void run() { |
| 48 mShouldShowDialog = true; |
| 49 showDialog(); |
| 50 } |
| 51 }; |
| 52 |
| 53 private final Runnable mDialogStaysEnough = new Runnable() { |
| 54 @Override |
| 55 public void run() { |
| 56 mHasDialogStayedEnough = true; |
| 57 if (mCurrentTask != null) mCurrentTask.onDialogStayedEnough(); |
| 58 } |
| 59 }; |
| 60 |
| 61 /** |
| 62 * @return true if an asynchronous fragment task is currently running in the
fragment. |
| 63 */ |
| 64 public boolean isFragmentAsyncTaskRunning() { |
| 65 return mCurrentTask != null; |
| 66 } |
| 67 |
| 68 /** |
| 69 * Starts a new asynchronous fragment task. Will fail if another task is alr
eady running. |
| 70 * |
| 71 * @param task New asynchronous fragment task to run. |
| 72 * @param dialogMessage Message shown in the progress dialog while the task
is run. |
| 73 */ |
| 74 public boolean runFragmentAsyncTask(FragmentAsyncTask task, String dialogMes
sage) { |
| 75 if (isFragmentAsyncTaskRunning() || task == null) return false; |
| 76 |
| 77 mCurrentTask = task; |
| 78 mDialogMessage = dialogMessage; |
| 79 mShouldShowDialog = false; |
| 80 mHasDialogStayedEnough = false; |
| 81 mHandler.postDelayed(mShowProgressDialog, DELAY_BEFORE_PROGRESS_DIALOG_M
S); |
| 82 task.execute(); |
| 83 return true; |
| 84 } |
| 85 |
| 86 /** |
| 87 * Cancels the execution of any ongoing fragment asynchronous task. |
| 88 * Note that this doesn't ensure the immediate interruption of the task. |
| 89 */ |
| 90 public void cancelFragmentAsyncTask() { |
| 91 if (mCurrentTask != null) { |
| 92 mCurrentTask.cancel(false); |
| 93 taskFinished(); |
| 94 } |
| 95 } |
| 96 |
| 97 /** |
| 98 * Base class for asynchronous tasks to be run within the fragment. |
| 99 */ |
| 100 public abstract class FragmentAsyncTask extends AsyncTask<Void, Void, Void>
{ |
| 101 /** |
| 102 * Method called to run the asynchronous code. |
| 103 */ |
| 104 protected abstract void runBackgroundTask(); |
| 105 |
| 106 /** |
| 107 * Method called when the asynchronous task has finished. |
| 108 */ |
| 109 protected abstract void onTaskFinished(); |
| 110 |
| 111 /** |
| 112 * Method called to disable the UI elements that depend on the task when
it starts, |
| 113 * and again to re-enable them when finished or cancelled. |
| 114 */ |
| 115 protected abstract void setDependentUIEnabled(boolean enabled); |
| 116 |
| 117 /** |
| 118 * Updates the enabled status of the UI elements depending on the task a
ccording to |
| 119 * the current task status. |
| 120 */ |
| 121 public void updateDependentUI() { |
| 122 setDependentUIEnabled(getStatus() != Status.RUNNING); |
| 123 } |
| 124 |
| 125 @Override |
| 126 protected void onPreExecute() { |
| 127 setDependentUIEnabled(false); |
| 128 } |
| 129 |
| 130 @Override |
| 131 protected Void doInBackground(Void... params) { |
| 132 runBackgroundTask(); |
| 133 return null; |
| 134 } |
| 135 |
| 136 @Override |
| 137 protected void onPostExecute(Void result) { |
| 138 // Don't dispatch the task result yet if the dialog is present and h
asn't stayed enough. |
| 139 if (mProgressDialog == null || mHasDialogStayedEnough) finishTask(); |
| 140 } |
| 141 |
| 142 @Override |
| 143 protected void onCancelled(Void result) { |
| 144 cleanUp(); |
| 145 } |
| 146 |
| 147 private void finishTask() { |
| 148 cleanUp(); |
| 149 onTaskFinished(); |
| 150 } |
| 151 |
| 152 private void cleanUp() { |
| 153 setDependentUIEnabled(true); |
| 154 taskFinished(); |
| 155 } |
| 156 |
| 157 void onDialogStayedEnough() { |
| 158 // Dispatch the results of any finished tasks that are waiting for t
he dialog. |
| 159 if (getStatus() == Status.FINISHED) finishTask(); |
| 160 } |
| 161 } |
| 162 |
| 163 @Override |
| 164 public void onAttach(Activity activity) { |
| 165 super.onAttach(activity); |
| 166 showDialog(); |
| 167 } |
| 168 |
| 169 @Override |
| 170 public void onDetach() { |
| 171 super.onDetach(); |
| 172 hideDialog(); |
| 173 } |
| 174 |
| 175 @Override |
| 176 public void onCreate(Bundle savedInstanceState) { |
| 177 super.onCreate(savedInstanceState); |
| 178 super.setRetainInstance(true); |
| 179 } |
| 180 |
| 181 @Override |
| 182 public void onDestroy() { |
| 183 super.onDestroy(); |
| 184 cancelFragmentAsyncTask(); |
| 185 } |
| 186 |
| 187 @Override |
| 188 public void onActivityCreated(Bundle savedInstanceState) { |
| 189 super.onActivityCreated(savedInstanceState); |
| 190 if (isFragmentAsyncTaskRunning()) updateTaskDependentUI(); |
| 191 } |
| 192 |
| 193 @Override |
| 194 public void onHiddenChanged(boolean hidden) { |
| 195 super.onHiddenChanged(hidden); |
| 196 if (hidden) { |
| 197 hideDialog(); |
| 198 } else { |
| 199 showDialog(); |
| 200 } |
| 201 } |
| 202 |
| 203 @Override |
| 204 public void setRetainInstance(boolean retain) { |
| 205 // The fragment is always retained for task and dialog consistence when
rotating the screen. |
| 206 assert retain; |
| 207 } |
| 208 |
| 209 private void updateTaskDependentUI() { |
| 210 if (mCurrentTask != null) mCurrentTask.updateDependentUI(); |
| 211 } |
| 212 |
| 213 private void showDialog() { |
| 214 if (isDetached() || isHidden() || !mShouldShowDialog) return; |
| 215 mProgressDialog = ProgressDialog.show(getActivity(), null, mDialogMessag
e, true, false); |
| 216 mHandler.postDelayed(mDialogStaysEnough, MINIMUM_DIALOG_STAY_MS); |
| 217 } |
| 218 |
| 219 private void hideDialog() { |
| 220 if (mProgressDialog != null) { |
| 221 mProgressDialog.dismiss(); |
| 222 mProgressDialog = null; |
| 223 } |
| 224 } |
| 225 |
| 226 private void taskFinished() { |
| 227 mHandler.removeCallbacks(mShowProgressDialog); |
| 228 mHandler.removeCallbacks(mDialogStaysEnough); |
| 229 hideDialog(); |
| 230 mShouldShowDialog = false; |
| 231 mHasDialogStayedEnough = false; |
| 232 mCurrentTask = null; |
| 233 } |
| 234 } |
OLD | NEW |