Merge "DO NOT MERGE: Fix ANR: Run getPreviewIcon on bg thread" into honeycomb-mr1

This commit is contained in:
Makoto Onuki 2011-03-03 15:31:13 -08:00 committed by Android (Google) Code Review
commit ab8caebec6
3 changed files with 319 additions and 22 deletions

View File

@ -0,0 +1,154 @@
/*
* Copyright (C) 2011 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.emailcommon.utility;
import android.os.AsyncTask;
import java.util.LinkedList;
import java.util.concurrent.ExecutionException;
/**
* {@link AsyncTask} substitution for the email app.
*
* Modeled after {@link AsyncTask}; the basic usage is the same, with extra features:
* - Bulk cancellation of multiple tasks. This is mainly used by UI to cancell pending tasks
* in onDestroy() or similar places.
* - More features to come...
*
* Note this class isn't 100% compatible to the regular {@link AsyncTask}, e.g. it lacks
* {@link AsyncTask#onProgressUpdate}. Add these when necessary.
*/
public abstract class EmailAsyncTask<Params, Progress, Result> {
/**
* Tracks {@link EmailAsyncTask}.
*
* Call {@link #cancellAllInterrupt()} to cancel all tasks registered.
*/
public static class Tracker {
private final LinkedList<EmailAsyncTask<?, ?, ?>> mTasks =
new LinkedList<EmailAsyncTask<?, ?, ?>>();
private void add(EmailAsyncTask<?, ?, ?> task) {
synchronized (mTasks) {
mTasks.add(task);
}
}
private void remove(EmailAsyncTask<?, ?, ?> task) {
synchronized (mTasks) {
mTasks.remove(task);
}
}
/**
* Cancel all registered tasks.
*/
public void cancellAllInterrupt() {
synchronized (mTasks) {
for (EmailAsyncTask<?, ?, ?> task : mTasks) {
task.cancel(true);
}
mTasks.clear();
}
}
/* package */ int getTaskCountForTest() {
return mTasks.size();
}
}
private final Tracker mTracker;
private static class InnerTask<Params2, Progress2, Result2>
extends AsyncTask<Params2, Progress2, Result2> {
private final EmailAsyncTask<Params2, Progress2, Result2> mOwner;
public InnerTask(EmailAsyncTask<Params2, Progress2, Result2> owner) {
mOwner = owner;
}
@Override
protected Result2 doInBackground(Params2... params) {
return mOwner.doInBackground(params);
}
@Override
public void onCancelled(Result2 result) {
mOwner.unregisterSelf();
mOwner.onCancelled(result);
}
@Override
public void onPostExecute(Result2 result) {
mOwner.unregisterSelf();
mOwner.onPostExecute(result);
}
}
private final InnerTask<Params, Progress, Result> mInnerTask;
public EmailAsyncTask(Tracker tracker) {
mTracker = tracker;
if (mTracker != null) {
mTracker.add(this);
}
mInnerTask = new InnerTask<Params, Progress, Result>(this);
}
/* package */ void unregisterSelf() {
if (mTracker != null) {
mTracker.remove(this);
}
}
protected abstract Result doInBackground(Params... params);
public final boolean cancel(boolean mayInterruptIfRunning) {
return mInnerTask.cancel(mayInterruptIfRunning);
}
protected void onCancelled(Result result) {
}
protected void onPostExecute(Result result) {
}
public final EmailAsyncTask<Params, Progress, Result> execute(Params... params) {
mInnerTask.execute(params);
return this;
}
public final Result get() throws InterruptedException, ExecutionException {
return mInnerTask.get();
}
public final boolean isCancelled() {
return mInnerTask.isCancelled();
}
/* package */ Result callDoInBackgroundForTest(Params... params) {
return mInnerTask.doInBackground(params);
}
/* package */ void callOnCancelledForTest(Result result) {
mInnerTask.onCancelled(result);
}
/* package */ void callOnPostExecuteForTest(Result result) {
mInnerTask.onPostExecute(result);
}
}

View File

@ -34,6 +34,7 @@ import com.android.emailcommon.provider.EmailContent.Body;
import com.android.emailcommon.provider.EmailContent.Mailbox;
import com.android.emailcommon.provider.EmailContent.Message;
import com.android.emailcommon.utility.AttachmentUtilities;
import com.android.emailcommon.utility.EmailAsyncTask;
import com.android.emailcommon.utility.Utility;
import org.apache.commons.io.IOUtils;
@ -205,6 +206,9 @@ public abstract class MessageViewFragmentBase extends Fragment implements View.O
private boolean mRestoredPictureLoaded;
private final EmailAsyncTask.Tracker mUpdatePreviewIconTaskTracker
= new EmailAsyncTask.Tracker();
/**
* Zoom scales for webview. Values correspond to {@link Preferences#TEXT_ZOOM_TINY}..
* {@link Preferences#TEXT_ZOOM_HUGE}.
@ -423,6 +427,7 @@ public abstract class MessageViewFragmentBase extends Fragment implements View.O
private void cancelAllTasks() {
mMessageObserver.unregister();
mUpdatePreviewIconTaskTracker.cancellAllInterrupt();
Utility.cancelTaskInterrupt(mLoadMessageTask);
mLoadMessageTask = null;
Utility.cancelTaskInterrupt(mReloadMessageTask);
@ -1154,12 +1159,12 @@ public abstract class MessageViewFragmentBase extends Fragment implements View.O
}
}
private Bitmap getPreviewIcon(AttachmentInfo attachment) {
private static Bitmap getPreviewIcon(Context context, AttachmentInfo attachment) {
try {
return BitmapFactory.decodeStream(
mContext.getContentResolver().openInputStream(
context.getContentResolver().openInputStream(
AttachmentUtilities.getAttachmentThumbnailUri(
mAccountId, attachment.mId,
attachment.mAccountKey, attachment.mId,
PREVIEW_ICON_WIDTH,
PREVIEW_ICON_HEIGHT)));
} catch (Exception e) {
@ -1168,20 +1173,6 @@ public abstract class MessageViewFragmentBase extends Fragment implements View.O
}
}
private void updateAttachmentThumbnail(long attachmentId) {
for (int i = 0, count = mAttachments.getChildCount(); i < count; i++) {
MessageViewAttachmentInfo attachment =
(MessageViewAttachmentInfo) mAttachments.getChildAt(i).getTag();
if (attachment.mId == attachmentId) {
Bitmap previewIcon = getPreviewIcon(attachment);
if (previewIcon != null) {
attachment.iconView.setImageBitmap(previewIcon);
}
return;
}
}
}
/**
* Subclass of AttachmentInfo which includes our views and buttons related to attachment
* handling, as well as our determination of suitability for viewing (based on availability of
@ -1318,10 +1309,7 @@ public abstract class MessageViewFragmentBase extends Fragment implements View.O
loadButton.setVisibility(View.GONE);
cancelButton.setVisibility(View.GONE);
Bitmap previewIcon = getPreviewIcon(attachmentInfo);
if (previewIcon != null) {
attachmentIcon.setImageBitmap(previewIcon);
}
updatePreviewIcon(attachmentInfo);
} else {
// The attachment is not loaded, so present UI to start downloading it
@ -1398,6 +1386,17 @@ public abstract class MessageViewFragmentBase extends Fragment implements View.O
mAttachments.setVisibility(View.VISIBLE);
}
private MessageViewAttachmentInfo findAttachmentInfoFromView(long attachmentId) {
for (int i = 0, count = mAttachments.getChildCount(); i < count; i++) {
MessageViewAttachmentInfo attachmentInfo =
(MessageViewAttachmentInfo) mAttachments.getChildAt(i).getTag();
if (attachmentInfo.mId == attachmentId) {
return attachmentInfo;
}
}
return null;
}
/**
* Reload the UI from a provider cursor. {@link LoadMessageTask#onPostExecute} calls it.
*
@ -1656,7 +1655,11 @@ public abstract class MessageViewFragmentBase extends Fragment implements View.O
showAttachmentProgress(attachmentId, progress);
switch (progress) {
case 100:
updateAttachmentThumbnail(attachmentId);
final MessageViewAttachmentInfo attachmentInfo =
findAttachmentInfoFromView(attachmentId);
if (attachmentInfo != null) {
updatePreviewIcon(attachmentInfo);
}
doFinishLoadAttachment(attachmentId);
break;
default:
@ -1757,6 +1760,35 @@ public abstract class MessageViewFragmentBase extends Fragment implements View.O
}
}
private void updatePreviewIcon(MessageViewAttachmentInfo attachmentInfo) {
new UpdatePreviewIconTask(attachmentInfo).execute();
}
private class UpdatePreviewIconTask extends EmailAsyncTask<Void, Void, Bitmap> {
@SuppressWarnings("hiding")
private final Context mContext;
private final MessageViewAttachmentInfo mAttachmentInfo;
public UpdatePreviewIconTask(MessageViewAttachmentInfo attachmentInfo) {
super(mUpdatePreviewIconTaskTracker);
mContext = getActivity();
mAttachmentInfo = attachmentInfo;
}
@Override
protected Bitmap doInBackground(Void... params) {
return getPreviewIcon(mContext, mAttachmentInfo);
}
@Override
protected void onPostExecute(Bitmap result) {
if (result == null) {
return;
}
mAttachmentInfo.iconView.setImageBitmap(result);
}
}
public boolean isMessageLoadedForTest() {
return mIsMessageLoadedForTest;
}

View File

@ -0,0 +1,111 @@
/*
* Copyright (C) 2011 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.emailcommon.utility;
import android.test.AndroidTestCase;
import android.test.MoreAsserts;
public class EmailAsyncTaskTests extends AndroidTestCase {
public void testAll() throws Exception {
// Because AsyncTask relies on the UI thread and how we use threads in test, we can't
// execute() these tasks.
// Instead, we directly call onPostExecute/onCancel.
final EmailAsyncTask.Tracker tracker = new EmailAsyncTask.Tracker();
// Initially empty
assertEquals(0, tracker.getTaskCountForTest());
// Start 4 tasks
final MyTask task1 = new MyTask(tracker);
assertEquals(1, tracker.getTaskCountForTest());
final MyTask task2 = new MyTask(tracker);
assertEquals(2, tracker.getTaskCountForTest());
final MyTask task3 = new MyTask(tracker);
assertEquals(3, tracker.getTaskCountForTest());
final MyTask task4 = new MyTask(tracker);
assertEquals(4, tracker.getTaskCountForTest());
// Check the piping for doInBackground
task1.mDoInBackgroundResult = "R";
assertEquals("R", task1.callDoInBackgroundForTest("1", "2"));
MoreAsserts.assertEquals(new String[] {"1", "2"}, task1.mDoInBackgroundArg);
// Finish task1
task1.callOnPostExecuteForTest("a");
// onPostExecute should unregister the instance
assertEquals(3, tracker.getTaskCountForTest());
// and call onPostExecuteInternal
assertEquals("a", task1.mOnPostExecuteArg);
assertNull(task1.mOnCancelledArg);
// Cancel task 3
task3.callOnCancelledForTest("b");
// onCancelled should unregister the instance too
assertEquals(2, tracker.getTaskCountForTest());
// and call onCancelledInternal
assertNull(task3.mOnPostExecuteArg);
assertEquals("b", task3.mOnCancelledArg);
// Task 2 and 4 are still registered.
// Cancel all left
tracker.cancellAllInterrupt();
// Check if they're canceled
assertTrue(task2.isCancelled());
assertTrue(task4.isCancelled());
assertEquals(0, tracker.getTaskCountForTest());
}
// Make sure null tracker will be accepted
public void testNullTracker() {
final MyTask task1 = new MyTask(null);
task1.unregisterSelf();
}
private static class MyTask extends EmailAsyncTask<String, String, String> {
public String[] mDoInBackgroundArg;
public String mDoInBackgroundResult;
public String mOnCancelledArg;
public String mOnPostExecuteArg;
public MyTask(Tracker tracker) {
super(tracker);
}
@Override
protected String doInBackground(String... params) {
mDoInBackgroundArg = params;
return mDoInBackgroundResult;
}
@Override
protected void onCancelled(String result) {
mOnCancelledArg = result;
}
@Override
protected void onPostExecute(String result) {
mOnPostExecuteArg = result;
}
}
}