Fix ANR when showing media in notifications.
This commit is contained in:
parent
9ab275195f
commit
42b0842aab
1 changed files with 104 additions and 1 deletions
|
@ -21,17 +21,27 @@ import android.content.ContentValues;
|
||||||
import android.content.UriMatcher;
|
import android.content.UriMatcher;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.HandlerThread;
|
||||||
import android.os.MemoryFile;
|
import android.os.MemoryFile;
|
||||||
import android.os.ParcelFileDescriptor;
|
import android.os.ParcelFileDescriptor;
|
||||||
|
import android.os.ProxyFileDescriptorCallback;
|
||||||
|
import android.os.storage.StorageManager;
|
||||||
|
import android.system.ErrnoException;
|
||||||
|
import android.system.OsConstants;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.RequiresApi;
|
||||||
|
|
||||||
import org.signal.core.util.StreamUtil;
|
import org.signal.core.util.StreamUtil;
|
||||||
|
import org.signal.core.util.concurrent.SignalExecutors;
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.BuildConfig;
|
import org.thoughtcrime.securesms.BuildConfig;
|
||||||
import org.thoughtcrime.securesms.attachments.AttachmentId;
|
import org.thoughtcrime.securesms.attachments.AttachmentId;
|
||||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
|
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
|
||||||
|
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||||
import org.thoughtcrime.securesms.mms.PartUriParser;
|
import org.thoughtcrime.securesms.mms.PartUriParser;
|
||||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||||
|
@ -42,6 +52,7 @@ import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
public final class PartProvider extends BaseContentProvider {
|
public final class PartProvider extends BaseContentProvider {
|
||||||
|
|
||||||
|
@ -79,12 +90,20 @@ public final class PartProvider extends BaseContentProvider {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (SignalDatabase.getInstance() == null) {
|
||||||
|
Log.w(TAG, "SignalDatabase unavailable");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (uriMatcher.match(uri) == SINGLE_ROW) {
|
if (uriMatcher.match(uri) == SINGLE_ROW) {
|
||||||
Log.i(TAG, "Parting out a single row...");
|
Log.i(TAG, "Parting out a single row...");
|
||||||
try {
|
try {
|
||||||
final PartUriParser partUri = new PartUriParser(uri);
|
final PartUriParser partUri = new PartUriParser(uri);
|
||||||
|
if (Build.VERSION.SDK_INT >= 26) {
|
||||||
|
return getParcelStreamProxyForAttachment(partUri.getPartId());
|
||||||
|
} else {
|
||||||
return getParcelStreamForAttachment(partUri.getPartId());
|
return getParcelStreamForAttachment(partUri.getPartId());
|
||||||
|
}
|
||||||
} catch (IOException ioe) {
|
} catch (IOException ioe) {
|
||||||
Log.w(TAG, ioe);
|
Log.w(TAG, ioe);
|
||||||
throw new FileNotFoundException("Error opening file");
|
throw new FileNotFoundException("Error opening file");
|
||||||
|
@ -104,6 +123,11 @@ public final class PartProvider extends BaseContentProvider {
|
||||||
public String getType(@NonNull Uri uri) {
|
public String getType(@NonNull Uri uri) {
|
||||||
Log.i(TAG, "getType() called: " + uri);
|
Log.i(TAG, "getType() called: " + uri);
|
||||||
|
|
||||||
|
if (SignalDatabase.getInstance() == null) {
|
||||||
|
Log.w(TAG, "SignalDatabase unavailable");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (uriMatcher.match(uri) == SINGLE_ROW) {
|
if (uriMatcher.match(uri) == SINGLE_ROW) {
|
||||||
PartUriParser partUriParser = new PartUriParser(uri);
|
PartUriParser partUriParser = new PartUriParser(uri);
|
||||||
DatabaseAttachment attachment = SignalDatabase.attachments().getAttachment(partUriParser.getPartId());
|
DatabaseAttachment attachment = SignalDatabase.attachments().getAttachment(partUriParser.getPartId());
|
||||||
|
@ -127,6 +151,11 @@ public final class PartProvider extends BaseContentProvider {
|
||||||
public Cursor query(@NonNull Uri url, @Nullable String[] projection, String selection, String[] selectionArgs, String sortOrder) {
|
public Cursor query(@NonNull Uri url, @Nullable String[] projection, String selection, String[] selectionArgs, String sortOrder) {
|
||||||
Log.i(TAG, "query() called: " + url);
|
Log.i(TAG, "query() called: " + url);
|
||||||
|
|
||||||
|
if (SignalDatabase.getInstance() == null) {
|
||||||
|
Log.w(TAG, "SignalDatabase unavailable");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (uriMatcher.match(url) == SINGLE_ROW) {
|
if (uriMatcher.match(url) == SINGLE_ROW) {
|
||||||
PartUriParser partUri = new PartUriParser(url);
|
PartUriParser partUri = new PartUriParser(url);
|
||||||
DatabaseAttachment attachment = SignalDatabase.attachments().getAttachment(partUri.getPartId());
|
DatabaseAttachment attachment = SignalDatabase.attachments().getAttachment(partUri.getPartId());
|
||||||
|
@ -168,4 +197,78 @@ public final class PartProvider extends BaseContentProvider {
|
||||||
|
|
||||||
return MemoryFileUtil.getParcelFileDescriptor(memoryFile);
|
return MemoryFileUtil.getParcelFileDescriptor(memoryFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RequiresApi(26)
|
||||||
|
private ParcelFileDescriptor getParcelStreamProxyForAttachment(AttachmentId attachmentId) throws IOException {
|
||||||
|
StorageManager storageManager = Objects.requireNonNull(getContext().getSystemService(StorageManager.class));
|
||||||
|
HandlerThread thread = SignalExecutors.getAndStartHandlerThread("storageservice-proxy");
|
||||||
|
Handler handler = new Handler(thread.getLooper());
|
||||||
|
|
||||||
|
ParcelFileDescriptor parcelFileDescriptor = storageManager.openProxyFileDescriptor(ParcelFileDescriptor.MODE_READ_ONLY,
|
||||||
|
new ProxyCallback(SignalDatabase.attachments(), attachmentId),
|
||||||
|
handler);
|
||||||
|
|
||||||
|
Log.i(TAG, attachmentId + ":createdProxy");
|
||||||
|
return parcelFileDescriptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(26)
|
||||||
|
private static final class ProxyCallback extends ProxyFileDescriptorCallback {
|
||||||
|
|
||||||
|
private AttachmentDatabase attachments;
|
||||||
|
private AttachmentId attachmentId;
|
||||||
|
|
||||||
|
public ProxyCallback(@NonNull AttachmentDatabase attachments, @NonNull AttachmentId attachmentId) {
|
||||||
|
this.attachments = attachments;
|
||||||
|
this.attachmentId = attachmentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long onGetSize() throws ErrnoException {
|
||||||
|
DatabaseAttachment attachment = attachments.getAttachment(attachmentId);
|
||||||
|
if (attachment != null && attachment.getSize() > 0) {
|
||||||
|
Log.i(TAG, attachmentId + ":getSize");
|
||||||
|
return attachment.getSize();
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, attachmentId + ":getSize:attachment is null or size is 0");
|
||||||
|
throw new ErrnoException("Attachment is invalid", OsConstants.ENOENT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int onRead(long offset, int size, byte[] data) throws ErrnoException {
|
||||||
|
try {
|
||||||
|
DatabaseAttachment attachment = attachments.getAttachment(attachmentId);
|
||||||
|
if (attachment == null || attachment.getSize() <= 0) {
|
||||||
|
Log.w(TAG, attachmentId + ":onRead:attachment is null or size is 0");
|
||||||
|
throw new ErrnoException("Attachment is invalid", OsConstants.ENOENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.i(TAG, attachmentId + ":onRead");
|
||||||
|
int totalRead = 0;
|
||||||
|
try (InputStream inputStream = attachments.getAttachmentStream(attachmentId, offset)) {
|
||||||
|
while (totalRead < size) {
|
||||||
|
int read = inputStream.read(data, totalRead, Math.max(0, size - totalRead));
|
||||||
|
if (read >= 0) {
|
||||||
|
totalRead += read;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return totalRead;
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.w(TAG, attachmentId + ":onRead:attachment read failed", e);
|
||||||
|
throw new ErrnoException("Error reading", OsConstants.EIO, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRelease() {
|
||||||
|
Log.i(TAG, attachmentId + ":onRelease");
|
||||||
|
|
||||||
|
attachments = null;
|
||||||
|
attachmentId = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue