Messages: Replace (Safe)AsyncTask
* AsyncTask is deprecated * Executors and Handlers can achieve the same thing and are not deprecated Change-Id: I5271bb73b848ce885eeaf5632c1da27853c56c4a
This commit is contained in:
@@ -39,7 +39,6 @@ import com.android.messaging.util.ContactUtil;
|
||||
import com.android.messaging.util.LogUtil;
|
||||
import com.android.messaging.util.OsUtil;
|
||||
import com.android.messaging.util.PhoneUtils;
|
||||
import com.android.messaging.util.SafeAsyncTask;
|
||||
import com.google.common.base.Joiner;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -47,6 +46,7 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
@@ -146,7 +146,7 @@ public class ParticipantRefresh {
|
||||
if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
|
||||
LogUtil.v(TAG, "Started full participant refresh");
|
||||
}
|
||||
SafeAsyncTask.executeOnThreadPool(sFullRefreshRunnable);
|
||||
Executors.newSingleThreadExecutor().execute(sFullRefreshRunnable);
|
||||
} else if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
|
||||
LogUtil.v(TAG, "Skipped full participant refresh");
|
||||
}
|
||||
@@ -156,7 +156,7 @@ public class ParticipantRefresh {
|
||||
* Refresh self participants on subscription or settings change.
|
||||
*/
|
||||
public static void refreshSelfParticipants() {
|
||||
SafeAsyncTask.executeOnThreadPool(sSelfOnlyRefreshRunnable);
|
||||
Executors.newSingleThreadExecutor().execute(sSelfOnlyRefreshRunnable);
|
||||
}
|
||||
|
||||
private static boolean getNeedFullRefresh() {
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
package com.android.messaging.datamodel.data;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.android.messaging.datamodel.MessageTextStats;
|
||||
@@ -37,7 +39,6 @@ import com.android.messaging.util.Assert.RunsOnMainThread;
|
||||
import com.android.messaging.util.BugleGservicesKeys;
|
||||
import com.android.messaging.util.LogUtil;
|
||||
import com.android.messaging.util.PhoneUtils;
|
||||
import com.android.messaging.util.SafeAsyncTask;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
@@ -45,6 +46,11 @@ import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class DraftMessageData extends BindableData implements ReadDraftDataActionListener {
|
||||
|
||||
@@ -678,7 +684,7 @@ public class DraftMessageData extends BindableData implements ReadDraftDataActio
|
||||
// Any change in the draft will cancel any pending draft checking task, since the
|
||||
// size/status of the draft may have changed.
|
||||
if (mCheckDraftForSendTask != null) {
|
||||
mCheckDraftForSendTask.cancel(true /* mayInterruptIfRunning */);
|
||||
mCheckDraftForSendTask.cancel();
|
||||
mCheckDraftForSendTask = null;
|
||||
}
|
||||
mListeners.onDraftChanged(this, changeFlags);
|
||||
@@ -708,7 +714,7 @@ public class DraftMessageData extends BindableData implements ReadDraftDataActio
|
||||
public void checkDraftForAction(final boolean checkMessageSize, final int selfSubId,
|
||||
final CheckDraftTaskCallback callback, final Binding<DraftMessageData> binding) {
|
||||
new CheckDraftForSendTask(checkMessageSize, selfSubId, callback, binding)
|
||||
.executeOnThreadPool((Void) null);
|
||||
.execute();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -750,7 +756,7 @@ public class DraftMessageData extends BindableData implements ReadDraftDataActio
|
||||
void onDraftChecked(DraftMessageData data, int result);
|
||||
}
|
||||
|
||||
public class CheckDraftForSendTask extends SafeAsyncTask<Void, Void, Integer> {
|
||||
public class CheckDraftForSendTask {
|
||||
public static final int RESULT_PASSED = 0;
|
||||
public static final int RESULT_HAS_PENDING_ATTACHMENTS = 1;
|
||||
public static final int RESULT_NO_SELF_PHONE_NUMBER_IN_GROUP_MMS = 2;
|
||||
@@ -762,6 +768,10 @@ public class DraftMessageData extends BindableData implements ReadDraftDataActio
|
||||
private final CheckDraftTaskCallback mCallback;
|
||||
private final String mBindingId;
|
||||
private final List<MessagePartData> mAttachmentsCopy;
|
||||
ScheduledExecutorService mExecutor = Executors.newScheduledThreadPool(2);
|
||||
private final Handler mHandler = new Handler(Looper.getMainLooper());
|
||||
private Future<?> mFuture;
|
||||
private boolean mCancelled;
|
||||
private int mPreExecuteResult = RESULT_PASSED;
|
||||
|
||||
public CheckDraftForSendTask(final boolean checkMessageSize, final int selfSubId,
|
||||
@@ -777,7 +787,51 @@ public class DraftMessageData extends BindableData implements ReadDraftDataActio
|
||||
mCheckDraftForSendTask = this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
onPreExecute();
|
||||
|
||||
mFuture = mExecutor.submit(() -> {
|
||||
final int result;
|
||||
if (mPreExecuteResult != RESULT_PASSED) {
|
||||
result = mPreExecuteResult;
|
||||
} else if (mCheckMessageSize && getIsMessageOverLimit()) {
|
||||
result = RESULT_MESSAGE_OVER_LIMIT;
|
||||
} else {
|
||||
result = RESULT_PASSED;
|
||||
}
|
||||
|
||||
mHandler.post(() -> {
|
||||
mCheckDraftForSendTask = null;
|
||||
// Only call back if we are bound to the original binding.
|
||||
if (isBound(mBindingId) && !mCancelled) {
|
||||
mCallback.onDraftChecked(DraftMessageData.this, result);
|
||||
} else {
|
||||
if (!isBound(mBindingId)) {
|
||||
LogUtil.w(LogUtil.BUGLE_TAG, "Message can't be sent: draft not bound");
|
||||
}
|
||||
if (mCancelled) {
|
||||
LogUtil.w(LogUtil.BUGLE_TAG,
|
||||
"Message can't be sent: draft is cancelled");
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
mExecutor.schedule(this::cancel, 10, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
if (!mFuture.isDone()) {
|
||||
mFuture.cancel(true);
|
||||
mCancelled = true;
|
||||
mCheckDraftForSendTask = null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isCancelled() {
|
||||
return mCancelled;
|
||||
}
|
||||
|
||||
protected void onPreExecute() {
|
||||
// Perform checking work that can happen on the main thread.
|
||||
if (hasPendingAttachments()) {
|
||||
@@ -803,39 +857,6 @@ public class DraftMessageData extends BindableData implements ReadDraftDataActio
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Integer doInBackgroundTimed(Void... params) {
|
||||
if (mPreExecuteResult != RESULT_PASSED) {
|
||||
return mPreExecuteResult;
|
||||
}
|
||||
|
||||
if (mCheckMessageSize && getIsMessageOverLimit()) {
|
||||
return RESULT_MESSAGE_OVER_LIMIT;
|
||||
}
|
||||
return RESULT_PASSED;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Integer result) {
|
||||
mCheckDraftForSendTask = null;
|
||||
// Only call back if we are bound to the original binding.
|
||||
if (isBound(mBindingId) && !isCancelled()) {
|
||||
mCallback.onDraftChecked(DraftMessageData.this, result);
|
||||
} else {
|
||||
if (!isBound(mBindingId)) {
|
||||
LogUtil.w(LogUtil.BUGLE_TAG, "Message can't be sent: draft not bound");
|
||||
}
|
||||
if (isCancelled()) {
|
||||
LogUtil.w(LogUtil.BUGLE_TAG, "Message can't be sent: draft is cancelled");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancelled() {
|
||||
mCheckDraftForSendTask = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Check if the draft message contains too many attachments to send
|
||||
* 2. Computes the minimum size that this message could be compressed/downsampled/encoded
|
||||
|
||||
@@ -43,10 +43,10 @@ import com.android.messaging.util.ContentType;
|
||||
import com.android.messaging.util.GifTranscoder;
|
||||
import com.android.messaging.util.ImageUtils;
|
||||
import com.android.messaging.util.LogUtil;
|
||||
import com.android.messaging.util.SafeAsyncTask;
|
||||
import com.android.messaging.util.UriUtil;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* Represents a single message part. Messages consist of one or more parts which may contain
|
||||
@@ -444,7 +444,7 @@ public class MessagePartData implements Parcelable {
|
||||
public void destroyAsync() {
|
||||
final Uri contentUri = shouldDestroy();
|
||||
if (contentUri != null) {
|
||||
SafeAsyncTask.executeOnThreadPool(() ->
|
||||
Executors.newSingleThreadExecutor().execute(() ->
|
||||
Factory.get().getApplicationContext().getContentResolver().delete(
|
||||
contentUri, null, null));
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
package com.android.messaging.datamodel.data;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -24,9 +26,13 @@ import androidx.annotation.NonNull;
|
||||
import com.android.messaging.util.Assert;
|
||||
import com.android.messaging.util.ContentType;
|
||||
import com.android.messaging.util.LogUtil;
|
||||
import com.android.messaging.util.SafeAsyncTask;
|
||||
import com.android.messaging.util.UriUtil;
|
||||
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Represents a "pending" message part that acts as a placeholder for the actual attachment being
|
||||
* loaded. It handles the task to load and persist the attachment from a Uri to local scratch
|
||||
@@ -98,37 +104,26 @@ public class PendingAttachmentData extends MessagePartData {
|
||||
}
|
||||
mCurrentState = STATE_LOADING;
|
||||
|
||||
// Kick off a SafeAsyncTask to load the content of the media and persist it locally.
|
||||
// Kick off loading the content of the media and persist it locally.
|
||||
// Note: we need to persist the media locally even if it's not remote, because we
|
||||
// want to be able to resend the media in case the message failed to send.
|
||||
new SafeAsyncTask<Void, Void, MessagePartData>(LOAD_MEDIA_TIME_LIMIT_MILLIS,
|
||||
true /* cancelExecutionOnTimeout */) {
|
||||
@Override
|
||||
protected MessagePartData doInBackgroundTimed(final Void... params) {
|
||||
final Uri contentUri = getContentUri();
|
||||
final Uri persistedUri = UriUtil.persistContentToScratchSpace(contentUri);
|
||||
if (persistedUri != null) {
|
||||
return MessagePartData.createMediaMessagePart(
|
||||
getText(),
|
||||
getContentType(),
|
||||
persistedUri,
|
||||
getWidth(),
|
||||
getHeight());
|
||||
}
|
||||
return null;
|
||||
ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
|
||||
Handler handler = new Handler(Looper.getMainLooper());
|
||||
final Future<?> future = executor.submit(() -> {
|
||||
MessagePartData data = null;
|
||||
final Uri contentUri = getContentUri();
|
||||
final Uri persistedUri = UriUtil.persistContentToScratchSpace(contentUri);
|
||||
if (persistedUri != null) {
|
||||
data = MessagePartData.createMediaMessagePart(
|
||||
getText(),
|
||||
getContentType(),
|
||||
persistedUri,
|
||||
getWidth(),
|
||||
getHeight());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancelled() {
|
||||
LogUtil.w(LogUtil.BUGLE_TAG, "Timeout while retrieving media");
|
||||
mCurrentState = STATE_FAILED;
|
||||
if (draftMessageData.isBound(bindingId)) {
|
||||
draftMessageData.removePendingAttachment(PendingAttachmentData.this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(final MessagePartData attachment) {
|
||||
final MessagePartData attachment = data;
|
||||
handler.post(() -> {
|
||||
if (attachment != null) {
|
||||
mCurrentState = STATE_LOADED;
|
||||
if (draftMessageData.isBound(bindingId)) {
|
||||
@@ -147,8 +142,18 @@ public class PendingAttachmentData extends MessagePartData {
|
||||
draftMessageData.removePendingAttachment(PendingAttachmentData.this);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
executor.schedule(() -> {
|
||||
if (!future.isDone()) {
|
||||
future.cancel(true);
|
||||
LogUtil.w(LogUtil.BUGLE_TAG, "Timeout while retrieving media");
|
||||
mCurrentState = STATE_FAILED;
|
||||
if (draftMessageData.isBound(bindingId)) {
|
||||
draftMessageData.removePendingAttachment(PendingAttachmentData.this);
|
||||
}
|
||||
}
|
||||
}.executeOnThreadPool();
|
||||
}, LOAD_MEDIA_TIME_LIMIT_MILLIS, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
protected PendingAttachmentData(final Parcel in) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
* Copyright (C) 2024 The LineageOS Project
|
||||
* Copyright (C) 2024-2025 The LineageOS Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,7 +16,8 @@
|
||||
*/
|
||||
package com.android.messaging.datamodel.media;
|
||||
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
import com.android.messaging.Factory;
|
||||
import com.android.messaging.util.Assert;
|
||||
@@ -227,35 +228,30 @@ public class MediaResourceManager {
|
||||
if (bindableRequest != null && !bindableRequest.isBound()) {
|
||||
return; // Request is obsolete
|
||||
}
|
||||
// We don't use SafeAsyncTask here since it enforces the shared thread pool executor
|
||||
// whereas we want a dedicated thread pool executor.
|
||||
AsyncTask<Void, Void, MediaLoadingResult<T>> mediaLoadingTask = new AsyncTask<>() {
|
||||
private Exception mException;
|
||||
|
||||
@Override
|
||||
protected MediaLoadingResult<T> doInBackground(Void... params) {
|
||||
// Double check the request is still valid by the time we start processing it
|
||||
if (bindableRequest != null && !bindableRequest.isBound()) {
|
||||
return null; // Request is obsolete
|
||||
}
|
||||
Handler handler = new Handler(Looper.getMainLooper());
|
||||
executor.execute(() -> {
|
||||
Exception exception = null;
|
||||
|
||||
MediaLoadingResult<T> tmpResult = null;
|
||||
// Double check the request is still valid by the time we start processing it
|
||||
if (bindableRequest != null && bindableRequest.isBound()) {
|
||||
try {
|
||||
return processMediaRequestInternal(mediaRequest);
|
||||
tmpResult = processMediaRequestInternal(mediaRequest);
|
||||
} catch (Exception e) {
|
||||
mException = e;
|
||||
return null;
|
||||
exception = e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(final MediaLoadingResult<T> result) {
|
||||
final Exception mException = exception;
|
||||
final MediaLoadingResult<T> result = tmpResult;
|
||||
handler.post(() -> {
|
||||
if (result != null) {
|
||||
Assert.isNull(mException);
|
||||
Assert.isTrue(result.loadedResource.getRefCount() > 0);
|
||||
try {
|
||||
if (bindableRequest != null) {
|
||||
bindableRequest.onMediaResourceLoaded(
|
||||
bindableRequest, result.loadedResource, result.fromCache);
|
||||
}
|
||||
bindableRequest.onMediaResourceLoaded(
|
||||
bindableRequest, result.loadedResource, result.fromCache);
|
||||
} finally {
|
||||
result.loadedResource.release();
|
||||
result.scheduleChainedRequests();
|
||||
@@ -263,9 +259,7 @@ public class MediaResourceManager {
|
||||
} else if (mException != null) {
|
||||
LogUtil.e(LogUtil.BUGLE_TAG, "Asynchronous media loading failed, key=" +
|
||||
mediaRequest.getKey(), mException);
|
||||
if (bindableRequest != null) {
|
||||
bindableRequest.onMediaResourceLoadError(bindableRequest, mException);
|
||||
}
|
||||
bindableRequest.onMediaResourceLoadError(bindableRequest, mException);
|
||||
} else {
|
||||
Assert.isTrue(bindableRequest == null || !bindableRequest.isBound());
|
||||
if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
|
||||
@@ -273,9 +267,8 @@ public class MediaResourceManager {
|
||||
LogUtil.sanitizePII(mediaRequest.getKey()) /* key with phone# */);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
mediaLoadingTask.executeOnExecutor(executor, (Void) null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@RunsOnAnyThread
|
||||
|
||||
@@ -32,7 +32,6 @@ import com.android.messaging.datamodel.MediaScratchFileProvider;
|
||||
import com.android.messaging.datamodel.data.PersonItemData;
|
||||
import com.android.messaging.util.ContactUtil;
|
||||
import com.android.messaging.util.LogUtil;
|
||||
import com.android.messaging.util.SafeAsyncTask;
|
||||
import com.android.vcard.VCardEntry;
|
||||
import com.android.vcard.VCardEntry.EmailData;
|
||||
import com.android.vcard.VCardEntry.ImData;
|
||||
@@ -47,6 +46,7 @@ import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* Holds one entry item (i.e. a single contact) within a VCard resource. It is able to take
|
||||
@@ -72,7 +72,7 @@ public class VCardResourceEntry {
|
||||
void close() {
|
||||
// If the avatar image was temporarily saved in the scratch folder, remove that.
|
||||
if (MediaScratchFileProvider.isMediaScratchSpaceUri(mAvatarUri)) {
|
||||
SafeAsyncTask.executeOnThreadPool(() ->
|
||||
Executors.newSingleThreadExecutor().execute(() ->
|
||||
Factory.get().getApplicationContext().getContentResolver().delete(
|
||||
mAvatarUri, null, null));
|
||||
}
|
||||
|
||||
@@ -26,12 +26,12 @@ import com.android.messaging.datamodel.data.ParticipantData;
|
||||
import com.android.messaging.util.Assert;
|
||||
import com.android.messaging.util.LogUtil;
|
||||
import com.android.messaging.util.PhoneUtils;
|
||||
import com.android.messaging.util.SafeAsyncTask;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* MMS configuration.
|
||||
@@ -134,7 +134,7 @@ public class MmsConfig {
|
||||
* Same as load() but doing it using an async thread from SafeAsyncTask thread pool.
|
||||
*/
|
||||
public static void loadAsync() {
|
||||
SafeAsyncTask.executeOnThreadPool(MmsConfig::load);
|
||||
Executors.newSingleThreadExecutor().execute(MmsConfig::load);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,6 +20,8 @@ import android.content.ActivityNotFoundException;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
@@ -41,10 +43,12 @@ import com.android.messaging.datamodel.data.PersonItemData;
|
||||
import com.android.messaging.datamodel.data.VCardContactItemData;
|
||||
import com.android.messaging.datamodel.data.PersonItemData.PersonItemDataListener;
|
||||
import com.android.messaging.util.Assert;
|
||||
import com.android.messaging.util.SafeAsyncTask;
|
||||
import com.android.messaging.util.UiUtils;
|
||||
import com.android.messaging.util.UriUtil;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* A fragment that shows the content of a VCard that contains one or more contacts.
|
||||
*/
|
||||
@@ -130,19 +134,18 @@ public class VCardDetailFragment extends Fragment implements PersonItemDataListe
|
||||
final Uri vCardUri = mBinding.getData().getVCardUri();
|
||||
|
||||
// We have to do things in the background in case we need to copy the vcard data.
|
||||
new SafeAsyncTask<Void, Void, Uri>() {
|
||||
@Override
|
||||
protected Uri doInBackgroundTimed(final Void... params) {
|
||||
// We can't delete the persisted vCard file because we don't know when to
|
||||
// delete it, since the app that uses it (contacts, dialer) may start or
|
||||
// shut down at any point. Therefore, we rely on the system to clean up
|
||||
// the cache directory for us.
|
||||
return mScratchSpaceUri != null ? mScratchSpaceUri :
|
||||
UriUtil.persistContentToScratchSpace(vCardUri);
|
||||
}
|
||||
ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
Handler handler = new Handler(Looper.getMainLooper());
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(final Uri result) {
|
||||
executor.execute(() -> {
|
||||
// We can't delete the persisted vCard file because we don't know when to
|
||||
// delete it, since the app that uses it (contacts, dialer) may start or
|
||||
// shut down at any point. Therefore, we rely on the system to clean up
|
||||
// the cache directory for us.
|
||||
Uri result = mScratchSpaceUri != null ? mScratchSpaceUri :
|
||||
UriUtil.persistContentToScratchSpace(vCardUri);
|
||||
|
||||
handler.post(() -> {
|
||||
if (result != null) {
|
||||
mScratchSpaceUri = result;
|
||||
if (getActivity() != null) {
|
||||
@@ -152,8 +155,8 @@ public class VCardDetailFragment extends Fragment implements PersonItemDataListe
|
||||
result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}.executeOnThreadPool();
|
||||
});
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
* Copyright (C) 2024 The LineageOS Project
|
||||
* Copyright (C) 2024-2025 The LineageOS Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -21,7 +21,8 @@ import android.database.Cursor;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.text.Editable;
|
||||
import android.text.TextPaint;
|
||||
import android.text.TextWatcher;
|
||||
@@ -49,6 +50,7 @@ import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
@@ -147,66 +149,81 @@ public class ContactRecipientAutoCompleteView extends RecipientEditTextView {
|
||||
}
|
||||
|
||||
/**
|
||||
* An AsyncTask that cleans up contact chips on every chips commit (i.e. get or create a new
|
||||
* A class that cleans up contact chips on every chips commit (i.e. get or create a new
|
||||
* conversation with the given chips).
|
||||
*/
|
||||
private class AsyncContactChipSanitizeTask extends
|
||||
AsyncTask<Void, ChipReplacementTuple, Integer> {
|
||||
private class AsyncContactChipSanitizeTask {
|
||||
|
||||
@Override
|
||||
protected Integer doInBackground(final Void... params) {
|
||||
final DrawableRecipientChip[] recips = getText()
|
||||
.getSpans(0, getText().length(), DrawableRecipientChip.class);
|
||||
int invalidChipsRemoved = 0;
|
||||
for (final DrawableRecipientChip recipient : recips) {
|
||||
final RecipientEntry entry = recipient.getEntry();
|
||||
if (entry != null) {
|
||||
if (entry.isValid()) {
|
||||
if (RecipientEntry.isCreatedRecipient(entry.getContactId()) ||
|
||||
ContactRecipientEntryUtils.isSendToDestinationContact(entry)) {
|
||||
// This is a generated/send-to contact chip, try to look it up and
|
||||
// display a chip for the corresponding local contact.
|
||||
try (final Cursor lookupResult =
|
||||
ContactUtil.lookupDestination(
|
||||
getContext(), entry.getDestination())
|
||||
.performSynchronousQuery()) {
|
||||
if (lookupResult != null && lookupResult.moveToNext()) {
|
||||
// Found a match, remove the generated entry and replace with a
|
||||
// better local entry.
|
||||
publishProgress(
|
||||
new ChipReplacementTuple(
|
||||
recipient,
|
||||
ContactUtil.createRecipientEntryForPhoneQuery(
|
||||
lookupResult, true)));
|
||||
} else if (PhoneUtils.isValidSmsMmsDestination(
|
||||
entry.getDestination())) {
|
||||
// No match was found, but we have a valid destination so let's
|
||||
// at least create an entry that shows an avatar.
|
||||
publishProgress(
|
||||
new ChipReplacementTuple(
|
||||
recipient,
|
||||
ContactRecipientEntryUtils
|
||||
.constructNumberWithAvatarEntry(
|
||||
entry.getDestination())));
|
||||
} else {
|
||||
// Not a valid contact. Remove and show an error.
|
||||
publishProgress(new ChipReplacementTuple(recipient, null));
|
||||
invalidChipsRemoved++;
|
||||
private ExecutorService mExecutor = Executors.newSingleThreadExecutor();
|
||||
private Handler mHandler = new Handler(Looper.getMainLooper());
|
||||
private boolean mIsCancelled;
|
||||
|
||||
protected void execute() {
|
||||
mExecutor.execute(() -> {
|
||||
final DrawableRecipientChip[] recips = getText()
|
||||
.getSpans(0, getText().length(), DrawableRecipientChip.class);
|
||||
int invalidChipsRemoved = 0;
|
||||
for (final DrawableRecipientChip recipient : recips) {
|
||||
if (mIsCancelled) {
|
||||
break;
|
||||
}
|
||||
final RecipientEntry entry = recipient.getEntry();
|
||||
if (entry != null) {
|
||||
if (entry.isValid()) {
|
||||
if (RecipientEntry.isCreatedRecipient(entry.getContactId()) ||
|
||||
ContactRecipientEntryUtils.isSendToDestinationContact(entry)) {
|
||||
// This is a generated/send-to contact chip, try to look it up and
|
||||
// display a chip for the corresponding local contact.
|
||||
try (final Cursor lookupResult = ContactUtil.lookupDestination(
|
||||
getContext(), entry.getDestination())
|
||||
.performSynchronousQuery()) {
|
||||
if (mIsCancelled) {
|
||||
break;
|
||||
}
|
||||
if (lookupResult != null && lookupResult.moveToNext()) {
|
||||
// Found a match, remove the generated entry and replace
|
||||
// with abetter local entry.
|
||||
publishProgress(
|
||||
new ChipReplacementTuple(recipient,
|
||||
ContactUtil.
|
||||
createRecipientEntryForPhoneQuery(
|
||||
lookupResult, true)));
|
||||
} else if (PhoneUtils.isValidSmsMmsDestination(
|
||||
entry.getDestination())) {
|
||||
// No match was found, but we have a valid destination so
|
||||
// let's at least create an entry that shows an avatar.
|
||||
publishProgress(
|
||||
new ChipReplacementTuple(
|
||||
recipient,
|
||||
ContactRecipientEntryUtils
|
||||
.constructNumberWithAvatarEntry(
|
||||
entry.getDestination())));
|
||||
} else {
|
||||
// Not a valid contact. Remove and show an error.
|
||||
publishProgress(new ChipReplacementTuple(recipient, null));
|
||||
invalidChipsRemoved++;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
publishProgress(new ChipReplacementTuple(recipient, null));
|
||||
invalidChipsRemoved++;
|
||||
}
|
||||
} else {
|
||||
publishProgress(new ChipReplacementTuple(recipient, null));
|
||||
invalidChipsRemoved++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return invalidChipsRemoved;
|
||||
|
||||
final int finalInvalidChipsRemoved = invalidChipsRemoved;
|
||||
mHandler.post(() -> {
|
||||
mCurrentSanitizeTask = null;
|
||||
if (finalInvalidChipsRemoved > 0) {
|
||||
mChipsChangeListener.onInvalidContactChipsPruned(finalInvalidChipsRemoved);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProgressUpdate(final ChipReplacementTuple... values) {
|
||||
for (final ChipReplacementTuple tuple : values) {
|
||||
private void publishProgress(final ChipReplacementTuple tuple) {
|
||||
mHandler.post(() -> {
|
||||
if (tuple.removedChip != null) {
|
||||
final Editable text = getText();
|
||||
final int chipStart = text.getSpanStart(tuple.removedChip);
|
||||
@@ -219,24 +236,18 @@ public class ContactRecipientAutoCompleteView extends RecipientEditTextView {
|
||||
appendRecipientEntry(tuple.replacedChipEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(final Integer invalidChipsRemoved) {
|
||||
mCurrentSanitizeTask = null;
|
||||
if (invalidChipsRemoved > 0) {
|
||||
mChipsChangeListener.onInvalidContactChipsPruned(invalidChipsRemoved);
|
||||
}
|
||||
public void cancel() {
|
||||
mIsCancelled = true;
|
||||
}
|
||||
|
||||
public boolean isCancelled() {
|
||||
return mIsCancelled;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We don't use SafeAsyncTask but instead use a single threaded executor to ensure that
|
||||
* all sanitization tasks are serially executed so as not to interfere with each other.
|
||||
*/
|
||||
private static final Executor SANITIZE_EXECUTOR = Executors.newSingleThreadExecutor();
|
||||
|
||||
private AsyncContactChipSanitizeTask mCurrentSanitizeTask;
|
||||
|
||||
/**
|
||||
@@ -251,11 +262,11 @@ public class ContactRecipientAutoCompleteView extends RecipientEditTextView {
|
||||
*/
|
||||
private void sanitizeContactChips() {
|
||||
if (mCurrentSanitizeTask != null && !mCurrentSanitizeTask.isCancelled()) {
|
||||
mCurrentSanitizeTask.cancel(false);
|
||||
mCurrentSanitizeTask.cancel();
|
||||
mCurrentSanitizeTask = null;
|
||||
}
|
||||
mCurrentSanitizeTask = new AsyncContactChipSanitizeTask();
|
||||
mCurrentSanitizeTask.executeOnExecutor(SANITIZE_EXECUTOR);
|
||||
mCurrentSanitizeTask.execute();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -21,6 +21,8 @@ import android.content.res.Resources;
|
||||
import android.graphics.Rect;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.text.Editable;
|
||||
import android.text.Html;
|
||||
import android.text.InputFilter;
|
||||
@@ -70,13 +72,14 @@ import com.android.messaging.util.ContentType;
|
||||
import com.android.messaging.util.LogUtil;
|
||||
import com.android.messaging.util.MediaUtil;
|
||||
import com.android.messaging.util.PhoneUtils;
|
||||
import com.android.messaging.util.SafeAsyncTask;
|
||||
import com.android.messaging.util.UiUtils;
|
||||
import com.android.messaging.util.UriUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* This view contains the UI required to generate and send messages.
|
||||
@@ -596,10 +599,11 @@ public class ComposeMessageView extends LinearLayout
|
||||
mConversationDataModel.getData().getParticipantsLoaded();
|
||||
}
|
||||
|
||||
private static class AsyncUpdateMessageBodySizeTask
|
||||
extends SafeAsyncTask<List<MessagePartData>, Void, Long> {
|
||||
private static class AsyncUpdateMessageBodySizeTask {
|
||||
|
||||
private final Context mContext;
|
||||
private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
|
||||
private final Handler mHandler = new Handler(Looper.getMainLooper());
|
||||
private final TextView mSizeTextView;
|
||||
|
||||
public AsyncUpdateMessageBodySizeTask(final Context context, final TextView tv) {
|
||||
@@ -607,20 +611,23 @@ public class ComposeMessageView extends LinearLayout
|
||||
mSizeTextView = tv;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Long doInBackgroundTimed(final List<MessagePartData>... params) {
|
||||
final List<MessagePartData> attachments = params[0];
|
||||
long totalSize = 0;
|
||||
for (final MessagePartData attachment : attachments) {
|
||||
final Uri contentUri = attachment.getContentUri();
|
||||
if (contentUri != null) {
|
||||
totalSize += UriUtil.getContentSize(attachment.getContentUri());
|
||||
protected void execute(final List<MessagePartData> attachments) {
|
||||
mExecutor.execute(() -> {
|
||||
long totalSize = 0;
|
||||
for (final MessagePartData attachment : attachments) {
|
||||
final Uri contentUri = attachment.getContentUri();
|
||||
if (contentUri != null) {
|
||||
totalSize += UriUtil.getContentSize(attachment.getContentUri());
|
||||
}
|
||||
}
|
||||
}
|
||||
return totalSize;
|
||||
|
||||
final long size = totalSize;
|
||||
mHandler.post(() -> {
|
||||
onPostExecute(size);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Long size) {
|
||||
if (mSizeTextView != null) {
|
||||
mSizeTextView.setText(Formatter.formatFileSize(mContext, size));
|
||||
@@ -656,7 +663,7 @@ public class ComposeMessageView extends LinearLayout
|
||||
if (hasAttachmentsChanged) {
|
||||
// Calculate message attachments size and show it.
|
||||
new AsyncUpdateMessageBodySizeTask(getContext(), mMessageBodySize)
|
||||
.executeOnThreadPool(attachments, null, null);
|
||||
.execute(attachments);
|
||||
} else {
|
||||
// No update. Just show previous size.
|
||||
mMessageBodySize.setVisibility(View.VISIBLE);
|
||||
|
||||
@@ -34,6 +34,7 @@ import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Parcelable;
|
||||
import android.telephony.PhoneNumberUtils;
|
||||
import android.text.TextUtils;
|
||||
@@ -105,7 +106,6 @@ import com.android.messaging.util.ImeUtil;
|
||||
import com.android.messaging.util.LogUtil;
|
||||
import com.android.messaging.util.OsUtil;
|
||||
import com.android.messaging.util.PhoneUtils;
|
||||
import com.android.messaging.util.SafeAsyncTask;
|
||||
import com.android.messaging.util.TextUtil;
|
||||
import com.android.messaging.util.UiUtils;
|
||||
import com.android.messaging.util.UriUtil;
|
||||
@@ -113,6 +113,8 @@ import com.android.messaging.util.UriUtil;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* Shows a list of messages/parts comprising a conversation.
|
||||
@@ -312,7 +314,7 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
part.getContentType());
|
||||
}
|
||||
if (saveAttachmentTask.getAttachmentCount() > 0) {
|
||||
saveAttachmentTask.executeOnThreadPool();
|
||||
saveAttachmentTask.execute();
|
||||
mHost.dismissActionMode();
|
||||
}
|
||||
return true;
|
||||
@@ -1266,8 +1268,10 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
}
|
||||
}
|
||||
|
||||
public static class SaveAttachmentTask extends SafeAsyncTask<Void, Void, Void> {
|
||||
public static class SaveAttachmentTask {
|
||||
private final Context mContext;
|
||||
private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
|
||||
private final Handler mHandler = new Handler(Looper.getMainLooper());
|
||||
private final List<AttachmentToSave> mAttachmentsToSave = new ArrayList<>();
|
||||
|
||||
public SaveAttachmentTask(final Context context, final Uri contentUri,
|
||||
@@ -1288,8 +1292,14 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
return mAttachmentsToSave.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackgroundTimed(final Void... arg) {
|
||||
public void execute() {
|
||||
mExecutor.execute(() -> {
|
||||
onExecute();
|
||||
mHandler.post(this::onPostExecute);
|
||||
});
|
||||
}
|
||||
|
||||
protected void onExecute() {
|
||||
final File appDir = new File(Environment.getExternalStoragePublicDirectory(
|
||||
Environment.DIRECTORY_PICTURES),
|
||||
mContext.getResources().getString(R.string.app_name));
|
||||
@@ -1301,11 +1311,9 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
attachment.persistedUri = UriUtil.persistContent(attachment.uri,
|
||||
isImageOrVideo ? appDir : downloadDir, attachment.contentType);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(final Void result) {
|
||||
protected void onPostExecute() {
|
||||
int failCount = 0;
|
||||
int imageCount = 0;
|
||||
int videoCount = 0;
|
||||
|
||||
@@ -46,10 +46,11 @@ import com.android.messaging.util.ContentType;
|
||||
import com.android.messaging.util.LogUtil;
|
||||
import com.android.messaging.util.MediaUtil;
|
||||
import com.android.messaging.util.MediaUtil.OnCompletionListener;
|
||||
import com.android.messaging.util.SafeAsyncTask;
|
||||
import com.android.messaging.util.ThreadUtil;
|
||||
import com.android.messaging.util.UiUtils;
|
||||
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* Hosts an audio recorder with tap and hold to record functionality.
|
||||
*/
|
||||
@@ -260,7 +261,7 @@ public class AudioRecordView extends FrameLayout implements
|
||||
// "tap+hold" to record audio.
|
||||
final Uri outputUri = stopRecording();
|
||||
if (outputUri != null) {
|
||||
SafeAsyncTask.executeOnThreadPool(() ->
|
||||
Executors.newSingleThreadExecutor().execute(() ->
|
||||
Factory.get().getApplicationContext().getContentResolver().delete(
|
||||
outputUri, null, null));
|
||||
}
|
||||
|
||||
@@ -34,9 +34,10 @@ import com.android.messaging.R;
|
||||
import com.android.messaging.datamodel.data.PendingAttachmentData;
|
||||
import com.android.messaging.util.ContentType;
|
||||
import com.android.messaging.util.LogUtil;
|
||||
import com.android.messaging.util.SafeAsyncTask;
|
||||
import com.android.messaging.util.UiUtils;
|
||||
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* Chooser which allows the user to select an existing contact from contacts apps on this device.
|
||||
* Note that this chooser requires the Manifest.permission.READ_CONTACTS which is one of the miminum
|
||||
@@ -71,7 +72,7 @@ class ContactMediaChooser extends MediaChooser {
|
||||
final Uri vCardUri = Uri.withAppendedPath(Contacts.CONTENT_VCARD_URI,
|
||||
lookupKey);
|
||||
if (vCardUri != null) {
|
||||
SafeAsyncTask.executeOnThreadPool(() -> {
|
||||
Executors.newSingleThreadExecutor().execute(() -> {
|
||||
final PendingAttachmentData pendingItem =
|
||||
PendingAttachmentData.createPendingAttachmentData(
|
||||
ContentType.TEXT_X_VCARD.toLowerCase(), vCardUri);
|
||||
|
||||
@@ -19,6 +19,8 @@ package com.android.messaging.ui.mediapicker;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.PickVisualMediaRequest;
|
||||
@@ -33,10 +35,11 @@ import com.android.messaging.util.BugleGservicesKeys;
|
||||
import com.android.messaging.util.LogUtil;
|
||||
import com.android.messaging.util.FileUtil;
|
||||
import com.android.messaging.util.ImageUtils;
|
||||
import com.android.messaging.util.SafeAsyncTask;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* Wraps around the functionalities to allow the user to pick an image/video/audio from the document
|
||||
@@ -108,24 +111,24 @@ public class DocumentImagePicker {
|
||||
// Notify our listener with a PendingAttachmentData containing the metadata.
|
||||
// Asynchronously get the content type for the picked image since
|
||||
// ImageUtils.getContentType() potentially involves I/O and can be expensive.
|
||||
new SafeAsyncTask<Void, Void, String>() {
|
||||
@Override
|
||||
protected String doInBackgroundTimed(final Void... params) {
|
||||
if (FileUtil.isInPrivateDir(documentUri) &&
|
||||
!MediaScratchFileProvider.isMediaScratchSpaceUri(documentUri)) {
|
||||
// hacker sending private app data. Bail out
|
||||
if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.ERROR)) {
|
||||
LogUtil.e(LogUtil.BUGLE_TAG, "Aborting attach of private app data ("
|
||||
+ documentUri + ")");
|
||||
}
|
||||
return null;
|
||||
ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
Handler handler = new Handler(Looper.getMainLooper());
|
||||
executor.execute(() -> {
|
||||
final String contentType;
|
||||
if (FileUtil.isInPrivateDir(documentUri) &&
|
||||
!MediaScratchFileProvider.isMediaScratchSpaceUri(documentUri)) {
|
||||
// hacker sending private app data. Bail out
|
||||
if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.ERROR)) {
|
||||
LogUtil.e(LogUtil.BUGLE_TAG, "Aborting attach of private app data ("
|
||||
+ documentUri + ")");
|
||||
}
|
||||
return ImageUtils.getContentType(
|
||||
contentType = null;
|
||||
} else {
|
||||
contentType = ImageUtils.getContentType(
|
||||
Factory.get().getApplicationContext().getContentResolver(), documentUri);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(final String contentType) {
|
||||
handler.post(() -> {
|
||||
if (contentType == null) {
|
||||
return; // bad uri on input
|
||||
}
|
||||
@@ -134,7 +137,7 @@ public class DocumentImagePicker {
|
||||
PendingAttachmentData.createPendingAttachmentData(contentType,
|
||||
documentUri);
|
||||
mListener.onDocumentSelected(pendingItem);
|
||||
}
|
||||
}.executeOnThreadPool();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,10 +26,10 @@ import com.android.messaging.datamodel.MediaScratchFileProvider;
|
||||
import com.android.messaging.util.Assert;
|
||||
import com.android.messaging.util.ContentType;
|
||||
import com.android.messaging.util.LogUtil;
|
||||
import com.android.messaging.util.SafeAsyncTask;
|
||||
import com.android.messaging.util.UiUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* Wraps around the functionalities of MediaRecorder, performs routine setup for audio recording
|
||||
@@ -147,7 +147,7 @@ public class LevelTrackingMediaRecorder {
|
||||
"media recorder. " + ex);
|
||||
if (mOutputUri != null) {
|
||||
final Uri outputUri = mOutputUri;
|
||||
SafeAsyncTask.executeOnThreadPool(() ->
|
||||
Executors.newSingleThreadExecutor().execute(() ->
|
||||
Factory.get().getApplicationContext().getContentResolver().delete(
|
||||
outputUri, null, null));
|
||||
mOutputUri = null;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
* Copyright (C) 2024 The LineageOS Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -157,7 +158,7 @@ public class BuglePhotoViewController extends PhotoViewController {
|
||||
}
|
||||
final String photoUri = adapter.getPhotoUri(cursor);
|
||||
new ConversationFragment.SaveAttachmentTask(((Activity) getActivity()),
|
||||
Uri.parse(photoUri), adapter.getContentType(cursor)).executeOnThreadPool();
|
||||
Uri.parse(photoUri), adapter.getContentType(cursor)).execute();
|
||||
return true;
|
||||
} else {
|
||||
return super.onOptionsItemSelected(item);
|
||||
|
||||
@@ -1,171 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
* Copyright (C) 2024 The LineageOS 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 final 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(() -> {
|
||||
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(() -> {
|
||||
try {
|
||||
runnable.run();
|
||||
} finally {
|
||||
sWakeLock.release(intent, WAKELOCK_OP);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
THREAD_POOL_EXECUTOR.execute(runnable);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,9 +28,10 @@ import com.android.messaging.R;
|
||||
import com.android.messaging.ui.UIIntents;
|
||||
import com.android.messaging.util.LogUtil;
|
||||
import com.android.messaging.util.OsUtil;
|
||||
import com.android.messaging.util.SafeAsyncTask;
|
||||
import com.android.messaging.util.UiUtils;
|
||||
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
public class BugleWidgetProvider extends BaseWidgetProvider {
|
||||
public static final String ACTION_NOTIFY_CONVERSATIONS_CHANGED =
|
||||
"com.android.Bugle.intent.action.ACTION_NOTIFY_CONVERSATIONS_CHANGED";
|
||||
@@ -43,7 +44,7 @@ public class BugleWidgetProvider extends BaseWidgetProvider {
|
||||
@Override
|
||||
protected void updateWidget(final Context context, final int appWidgetId) {
|
||||
if (OsUtil.hasRequiredPermissions()) {
|
||||
SafeAsyncTask.executeOnThreadPool(() -> rebuildWidget(context, appWidgetId));
|
||||
Executors.newSingleThreadExecutor().execute(() -> rebuildWidget(context, appWidgetId));
|
||||
} else {
|
||||
AppWidgetManager.getInstance(context).updateAppWidget(appWidgetId,
|
||||
UiUtils.getWidgetMissingPermissionView(context));
|
||||
|
||||
@@ -36,9 +36,10 @@ import com.android.messaging.ui.UIIntents;
|
||||
import com.android.messaging.ui.WidgetPickConversationActivity;
|
||||
import com.android.messaging.util.LogUtil;
|
||||
import com.android.messaging.util.OsUtil;
|
||||
import com.android.messaging.util.SafeAsyncTask;
|
||||
import com.android.messaging.util.UiUtils;
|
||||
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
public class WidgetConversationProvider extends BaseWidgetProvider {
|
||||
public static final String ACTION_NOTIFY_MESSAGES_CHANGED =
|
||||
"com.android.Bugle.intent.action.ACTION_NOTIFY_MESSAGES_CHANGED";
|
||||
@@ -149,7 +150,8 @@ public class WidgetConversationProvider extends BaseWidgetProvider {
|
||||
// widget dependent on ConversationListItemData. However, we have to update
|
||||
// the widget regardless, even with those missing pieces. Here we update the
|
||||
// widget again in the background.
|
||||
SafeAsyncTask.executeOnThreadPool(() -> rebuildWidget(context, appWidgetId));
|
||||
Executors.newSingleThreadExecutor().execute(() ->
|
||||
rebuildWidget(context, appWidgetId));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user