summaryrefslogtreecommitdiffstats
path: root/src/com/android/messaging/util/SafeAsyncTask.java
diff options
context:
space:
mode:
authorMike Dodd <mdodd@google.com>2015-08-11 11:16:59 -0700
committerMike Dodd <mdodd@google.com>2015-08-12 08:58:28 -0700
commit461a34b466cb4b13dbbc2ec6330b31e217b2ac4e (patch)
treebc4b489af52d0e2521e21167d2ad76a47256f348 /src/com/android/messaging/util/SafeAsyncTask.java
parent8b3e2b9c1b0a09423a7ba5d1091b9192106502f8 (diff)
downloadandroid_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.java176
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);
+ }
+ }
+}