diff options
author | Mike Dodd <mdodd@google.com> | 2015-08-11 11:16:59 -0700 |
---|---|---|
committer | Mike Dodd <mdodd@google.com> | 2015-08-12 08:58:28 -0700 |
commit | 461a34b466cb4b13dbbc2ec6330b31e217b2ac4e (patch) | |
tree | bc4b489af52d0e2521e21167d2ad76a47256f348 /src/com/android/messaging/util/SafeAsyncTask.java | |
parent | 8b3e2b9c1b0a09423a7ba5d1091b9192106502f8 (diff) | |
download | android_packages_apps_Messaging-461a34b466cb4b13dbbc2ec6330b31e217b2ac4e.tar.gz android_packages_apps_Messaging-461a34b466cb4b13dbbc2ec6330b31e217b2ac4e.tar.bz2 android_packages_apps_Messaging-461a34b466cb4b13dbbc2ec6330b31e217b2ac4e.zip |
Initial checkin of AOSP Messaging app.
b/23110861
Change-Id: I9aa980d7569247d6b2ca78f5dcb4502e1eaadb8a
Diffstat (limited to 'src/com/android/messaging/util/SafeAsyncTask.java')
-rw-r--r-- | src/com/android/messaging/util/SafeAsyncTask.java | 176 |
1 files changed, 176 insertions, 0 deletions
diff --git a/src/com/android/messaging/util/SafeAsyncTask.java b/src/com/android/messaging/util/SafeAsyncTask.java new file mode 100644 index 0000000..1cce6e9 --- /dev/null +++ b/src/com/android/messaging/util/SafeAsyncTask.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.messaging.util; + +import android.content.Intent; +import android.os.AsyncTask; +import android.os.Debug; +import android.os.SystemClock; + +import com.android.messaging.Factory; +import com.android.messaging.util.Assert.RunsOnAnyThread; + +/** + * Wrapper class which provides explicit API for: + * <ol> + * <li>Threading policy choice - Users of this class should use the explicit API instead of + * {@link #execute} which uses different threading policy on different OS versions. + * <li>Enforce creation on main thread as required by AsyncTask + * <li>Enforce that the background task does not take longer than expected. + * </ol> + */ +public abstract class SafeAsyncTask<Params, Progress, Result> + extends AsyncTask<Params, Progress, Result> { + private static final long DEFAULT_MAX_EXECUTION_TIME_MILLIS = 10 * 1000; // 10 seconds + + /** This is strongly discouraged as it can block other AsyncTasks indefinitely. */ + public static final long UNBOUNDED_TIME = Long.MAX_VALUE; + + private static final String WAKELOCK_ID = "bugle_safe_async_task_wakelock"; + protected static final int WAKELOCK_OP = 1000; + private static WakeLockHelper sWakeLock = new WakeLockHelper(WAKELOCK_ID); + + private final long mMaxExecutionTimeMillis; + private final boolean mCancelExecutionOnTimeout; + private boolean mThreadPoolRequested; + + public SafeAsyncTask() { + this(DEFAULT_MAX_EXECUTION_TIME_MILLIS, false); + } + + public SafeAsyncTask(final long maxTimeMillis) { + this(maxTimeMillis, false); + } + + /** + * @param maxTimeMillis maximum expected time for the background operation. This is just + * a diagnostic tool to catch unexpectedly long operations. If an operation does take + * longer than expected, it is fine to increase this argument. If the value is larger + * than a minute, you should consider using a dedicated thread so as not to interfere + * with other AsyncTasks. + * + * <p>Use {@link #UNBOUNDED_TIME} if you do not know the maximum expected time. This + * is strongly discouraged as it can block other AsyncTasks indefinitely. + * + * @param cancelExecutionOnTimeout whether to attempt to cancel the task execution on timeout. + * If this is set, at execution timeout we will call cancel(), so doInBackgroundTimed() + * should periodically check if the task is to be cancelled and finish promptly if + * possible, and handle the cancel event in onCancelled(). Also, at the end of execution + * we will not crash the execution if it went over limit since we explicitly canceled it. + */ + public SafeAsyncTask(final long maxTimeMillis, final boolean cancelExecutionOnTimeout) { + Assert.isMainThread(); // AsyncTask has to be created on the main thread + mMaxExecutionTimeMillis = maxTimeMillis; + mCancelExecutionOnTimeout = cancelExecutionOnTimeout; + } + + public final SafeAsyncTask<Params, Progress, Result> executeOnThreadPool( + final Params... params) { + Assert.isMainThread(); // AsyncTask requires this + mThreadPoolRequested = true; + executeOnExecutor(THREAD_POOL_EXECUTOR, params); + return this; + } + + protected abstract Result doInBackgroundTimed(final Params... params); + + @Override + protected final Result doInBackground(final Params... params) { + // This enforces that executeOnThreadPool was called, not execute. Ideally, we would + // make execute throw an exception, but since it is final, we cannot override it. + Assert.isTrue(mThreadPoolRequested); + + if (mCancelExecutionOnTimeout) { + ThreadUtil.getMainThreadHandler().postDelayed(new Runnable() { + @Override + public void run() { + if (getStatus() == Status.RUNNING) { + // Cancel the task if it's still running. + LogUtil.w(LogUtil.BUGLE_TAG, String.format("%s timed out and is canceled", + this)); + cancel(true /* mayInterruptIfRunning */); + } + } + }, mMaxExecutionTimeMillis); + } + + final long startTime = SystemClock.elapsedRealtime(); + try { + return doInBackgroundTimed(params); + } finally { + final long executionTime = SystemClock.elapsedRealtime() - startTime; + if (executionTime > mMaxExecutionTimeMillis) { + LogUtil.w(LogUtil.BUGLE_TAG, String.format("%s took %dms", this, executionTime)); + // Don't crash if debugger is attached or if we are asked to cancel on timeout. + if (!Debug.isDebuggerConnected() && !mCancelExecutionOnTimeout) { + Assert.fail(this + " took too long"); + } + } + } + + } + + @Override + protected void onPostExecute(final Result result) { + // No need to use AsyncTask at all if there is no onPostExecute + Assert.fail("Use SafeAsyncTask.executeOnThreadPool"); + } + + /** + * This provides a way for people to run async tasks but without onPostExecute. + * This can be called on any thread. + * + * Run code in a thread using AsyncTask's thread pool. + * + * To enable wakelock during the execution, see {@link #executeOnThreadPool(Runnable, boolean)} + * + * @param runnable The Runnable to execute asynchronously + */ + @RunsOnAnyThread + public static void executeOnThreadPool(final Runnable runnable) { + executeOnThreadPool(runnable, false); + } + + /** + * This provides a way for people to run async tasks but without onPostExecute. + * This can be called on any thread. + * + * Run code in a thread using AsyncTask's thread pool. + * + * @param runnable The Runnable to execute asynchronously + * @param withWakeLock when set, a wake lock will be held for the duration of the runnable + * execution + */ + public static void executeOnThreadPool(final Runnable runnable, final boolean withWakeLock) { + if (withWakeLock) { + final Intent intent = new Intent(); + sWakeLock.acquire(Factory.get().getApplicationContext(), intent, WAKELOCK_OP); + THREAD_POOL_EXECUTOR.execute(new Runnable() { + @Override + public void run() { + try { + runnable.run(); + } finally { + sWakeLock.release(intent, WAKELOCK_OP); + } + } + }); + } else { + THREAD_POOL_EXECUTOR.execute(runnable); + } + } +} |