Eliminate MediaNetworkRequirement style attachment job handling

// FREEBIE
This commit is contained in:
Moxie Marlinspike 2017-07-21 15:59:27 -07:00
parent b5259f6847
commit 82b5b35d3b
14 changed files with 117 additions and 173 deletions

View File

@ -32,7 +32,6 @@ import org.thoughtcrime.securesms.jobs.CreateSignedPreKeyJob;
import org.thoughtcrime.securesms.jobs.GcmRefreshJob;
import org.thoughtcrime.securesms.jobs.persistence.EncryptingJobSerializer;
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirementProvider;
import org.thoughtcrime.securesms.jobs.requirements.MediaNetworkRequirementProvider;
import org.thoughtcrime.securesms.jobs.requirements.ServiceRequirementProvider;
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
import org.thoughtcrime.securesms.service.DirectoryRefreshListener;
@ -71,8 +70,6 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
private JobManager jobManager;
private ObjectGraph objectGraph;
private MediaNetworkRequirementProvider mediaNetworkRequirementProvider = new MediaNetworkRequirementProvider();
public static ApplicationContext getInstance(Context context) {
return (ApplicationContext)context.getApplicationContext();
}
@ -122,16 +119,11 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
.withJobSerializer(new EncryptingJobSerializer())
.withRequirementProviders(new MasterSecretRequirementProvider(this),
new ServiceRequirementProvider(this),
new NetworkRequirementProvider(this),
mediaNetworkRequirementProvider)
new NetworkRequirementProvider(this))
.withConsumerThreads(5)
.build();
}
public void notifyMediaControlEvent() {
mediaNetworkRequirementProvider.notifyMediaControlEvent();
}
private void initializeDependencyInjection() {
this.objectGraph = ObjectGraph.create(new SignalCommunicationModule(this, new SignalServiceNetworkAccess(this)),
new AxolotlStorageModule(this));

View File

@ -44,6 +44,7 @@ import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
import org.thoughtcrime.securesms.components.AlertView;
import org.thoughtcrime.securesms.components.AudioView;
import org.thoughtcrime.securesms.components.AvatarImageView;
@ -61,6 +62,7 @@ import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob;
import org.thoughtcrime.securesms.jobs.MmsDownloadJob;
import org.thoughtcrime.securesms.jobs.MmsSendJob;
import org.thoughtcrime.securesms.jobs.SmsSendJob;
@ -559,6 +561,11 @@ public class ConversationItem extends LinearLayout
DatabaseFactory.getAttachmentDatabase(context).setTransferState(messageRecord.getId(),
slide.asAttachment(),
AttachmentDatabase.TRANSFER_PROGRESS_STARTED);
ApplicationContext.getInstance(context)
.getJobManager()
.add(new AttachmentDownloadJob(context, messageRecord.getId(),
((DatabaseAttachment)slide.asAttachment()).getAttachmentId(), true));
}
}
}

View File

@ -257,7 +257,7 @@ public class DatabaseUpgradeActivity extends BaseActivity {
Log.w(TAG, "queuing new attachment download job for incoming push part " + attachment.getAttachmentId() + ".");
ApplicationContext.getInstance(context)
.getJobManager()
.add(new AttachmentDownloadJob(context, attachment.getMmsId(), attachment.getAttachmentId()));
.add(new AttachmentDownloadJob(context, attachment.getMmsId(), attachment.getAttachmentId(), false));
}
reader.close();
}

View File

@ -29,7 +29,7 @@ public class MmsNotificationAttachment extends Attachment {
if (status == MmsDatabase.Status.DOWNLOAD_INITIALIZED ||
status == MmsDatabase.Status.DOWNLOAD_NO_CONNECTIVITY)
{
return AttachmentDatabase.TRANSFER_PROGRESS_AUTO_PENDING;
return AttachmentDatabase.TRANSFER_PROGRESS_PENDING;
} else if (status == MmsDatabase.Status.DOWNLOAD_CONNECTING) {
return AttachmentDatabase.TRANSFER_PROGRESS_STARTED;
} else {

View File

@ -44,7 +44,7 @@ public class PointerAttachment extends Attachment {
if (pointer.isPointer()) {
String encryptedKey = MediaKey.getEncrypted(masterSecret, pointer.asPointer().getKey());
results.add(new PointerAttachment(pointer.getContentType(),
AttachmentDatabase.TRANSFER_PROGRESS_AUTO_PENDING,
AttachmentDatabase.TRANSFER_PROGRESS_PENDING,
pointer.asPointer().getSize().or(0),
pointer.asPointer().getFileName().orNull(),
String.valueOf(pointer.asPointer().getId()),

View File

@ -84,10 +84,10 @@ public class AttachmentDatabase extends Database {
static final String VOICE_NOTE = "voice_note";
public static final String FAST_PREFLIGHT_ID = "fast_preflight_id";
public static final int TRANSFER_PROGRESS_DONE = 0;
public static final int TRANSFER_PROGRESS_STARTED = 1;
public static final int TRANSFER_PROGRESS_AUTO_PENDING = 2;
public static final int TRANSFER_PROGRESS_FAILED = 3;
public static final int TRANSFER_PROGRESS_DONE = 0;
public static final int TRANSFER_PROGRESS_STARTED = 1;
public static final int TRANSFER_PROGRESS_PENDING = 2;
public static final int TRANSFER_PROGRESS_FAILED = 3;
private static final String PART_ID_WHERE = ROW_ID + " = ? AND " + UNIQUE_ID + " = ?";
@ -380,7 +380,6 @@ public class AttachmentDatabase extends Database {
values.put(TRANSFER_STATE, transferState);
database.update(TABLE_NAME, values, PART_ID_WHERE, attachmentId.toStrings());
notifyConversationListeners(DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(messageId));
ApplicationContext.getInstance(context).notifyMediaControlEvent();
}
@VisibleForTesting

View File

@ -79,7 +79,8 @@ public class DatabaseFactory {
private static final int INTRODUCED_FAST_PREFLIGHT = 33;
private static final int INTRODUCED_VOICE_NOTES = 34;
private static final int INTRODUCED_IDENTITY_TIMESTAMP = 35;
private static final int DATABASE_VERSION = 35;
private static final int SANIFY_ATTACHMENT_DOWNLOAD = 36;
private static final int DATABASE_VERSION = 36;
private static final String DATABASE_NAME = "messages.db";
private static final Object lock = new Object();
@ -878,6 +879,10 @@ public class DatabaseFactory {
db.execSQL("CREATE INDEX IF NOT EXISTS archived_count_index ON thread (archived, message_count)");
}
if (oldVersion < SANIFY_ATTACHMENT_DOWNLOAD) {
db.execSQL("UPDATE part SET pending_push = '2' WHERE pending_push = '1'");
}
db.setTransactionSuccessful();
db.endTransaction();
}

View File

@ -12,13 +12,14 @@ import org.thoughtcrime.securesms.crypto.AsymmetricMasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
import org.thoughtcrime.securesms.crypto.MediaKey;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.events.PartProgressEvent;
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
import org.thoughtcrime.securesms.jobs.requirements.MediaNetworkRequirement;
import org.thoughtcrime.securesms.mms.MmsException;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.util.AttachmentUtil;
import org.thoughtcrime.securesms.util.Hex;
import org.whispersystems.jobqueue.JobParameters;
import org.whispersystems.jobqueue.requirements.NetworkRequirement;
@ -36,31 +37,30 @@ import java.io.InputStream;
import javax.inject.Inject;
import org.thoughtcrime.securesms.mms.MmsException;
public class AttachmentDownloadJob extends MasterSecretJob implements InjectableType {
private static final long serialVersionUID = 1L;
private static final long serialVersionUID = 2L;
private static final int MAX_ATTACHMENT_SIZE = 150 * 1024 * 1024;
private static final String TAG = AttachmentDownloadJob.class.getSimpleName();
@Inject transient SignalServiceMessageReceiver messageReceiver;
private final long messageId;
private final long partRowId;
private final long partUniqueId;
private final long messageId;
private final long partRowId;
private final long partUniqueId;
private final boolean manual;
public AttachmentDownloadJob(Context context, long messageId, AttachmentId attachmentId) {
public AttachmentDownloadJob(Context context, long messageId, AttachmentId attachmentId, boolean manual) {
super(context, JobParameters.newBuilder()
.withGroupId(AttachmentDownloadJob.class.getCanonicalName())
.withRequirement(new MasterSecretRequirement(context))
.withRequirement(new NetworkRequirement(context))
.withRequirement(new MediaNetworkRequirement(context, messageId, attachmentId))
.withPersistence()
.create());
this.messageId = messageId;
this.partRowId = attachmentId.getRowId();
this.partUniqueId = attachmentId.getUniqueId();
this.manual = manual;
}
@Override
@ -69,8 +69,9 @@ public class AttachmentDownloadJob extends MasterSecretJob implements Injectable
@Override
public void onRun(MasterSecret masterSecret) throws IOException {
final AttachmentId attachmentId = new AttachmentId(partRowId, partUniqueId);
final Attachment attachment = DatabaseFactory.getAttachmentDatabase(context).getAttachment(masterSecret, attachmentId);
final AttachmentDatabase database = DatabaseFactory.getAttachmentDatabase(context);
final AttachmentId attachmentId = new AttachmentId(partRowId, partUniqueId);
final Attachment attachment = database.getAttachment(masterSecret, attachmentId);
if (attachment == null) {
Log.w(TAG, "attachment no longer exists.");
@ -82,7 +83,13 @@ public class AttachmentDownloadJob extends MasterSecretJob implements Injectable
return;
}
if (!manual && !AttachmentUtil.isAutoDownloadPermitted(context, attachment)) {
Log.w(TAG, "Attachment can't be auto downloaded...");
return;
}
Log.w(TAG, "Downloading push part " + attachmentId);
database.setTransferState(messageId, attachmentId, AttachmentDatabase.TRANSFER_PROGRESS_STARTED);
retrieveAttachment(masterSecret, messageId, attachmentId, attachment);
MessageNotifier.updateNotification(context, masterSecret);

View File

@ -508,8 +508,7 @@ public class PushDecryptJob extends ContextJob {
for (DatabaseAttachment attachment : attachments) {
ApplicationContext.getInstance(context)
.getJobManager()
.add(new AttachmentDownloadJob(context, insertResult.get().getMessageId(),
attachment.getAttachmentId()));
.add(new AttachmentDownloadJob(context, insertResult.get().getMessageId(), attachment.getAttachmentId(), false));
if (!masterSecret.getMasterSecret().isPresent()) {
ApplicationContext.getInstance(context)
@ -579,7 +578,7 @@ public class PushDecryptJob extends ContextJob {
for (DatabaseAttachment attachment : DatabaseFactory.getAttachmentDatabase(context).getAttachmentsForMessage(null, messageId)) {
ApplicationContext.getInstance(context)
.getJobManager()
.add(new AttachmentDownloadJob(context, messageId, attachment.getAttachmentId()));
.add(new AttachmentDownloadJob(context, messageId, attachment.getAttachmentId(), false));
}
if (smsMessageId.isPresent()) {

View File

@ -1,120 +0,0 @@
package org.thoughtcrime.securesms.jobs.requirements;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.Log;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.attachments.AttachmentId;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.jobqueue.dependencies.ContextDependent;
import org.whispersystems.jobqueue.requirements.Requirement;
import java.util.Collections;
import java.util.Set;
public class MediaNetworkRequirement implements Requirement, ContextDependent {
private static final long serialVersionUID = 0L;
private static final String TAG = MediaNetworkRequirement.class.getSimpleName();
private transient Context context;
private final long messageId;
private final long partRowId;
private final long partUniqueId;
public MediaNetworkRequirement(Context context, long messageId, AttachmentId attachmentId) {
this.context = context;
this.messageId = messageId;
this.partRowId = attachmentId.getRowId();
this.partUniqueId = attachmentId.getUniqueId();
}
@Override
public void setContext(Context context) {
this.context = context;
}
private NetworkInfo getNetworkInfo() {
return ServiceUtil.getConnectivityManager(context).getActiveNetworkInfo();
}
public boolean isConnectedWifi() {
final NetworkInfo info = getNetworkInfo();
return info != null && info.isConnected() && info.getType() == ConnectivityManager.TYPE_WIFI;
}
public boolean isConnectedMobile() {
final NetworkInfo info = getNetworkInfo();
return info != null && info.isConnected() && info.getType() == ConnectivityManager.TYPE_MOBILE;
}
public boolean isConnectedRoaming() {
final NetworkInfo info = getNetworkInfo();
return info != null && info.isConnected() && info.isRoaming() && info.getType() == ConnectivityManager.TYPE_MOBILE;
}
private @NonNull Set<String> getAllowedAutoDownloadTypes() {
if (isConnectedWifi()) {
return TextSecurePreferences.getWifiMediaDownloadAllowed(context);
} else if (isConnectedRoaming()) {
return TextSecurePreferences.getRoamingMediaDownloadAllowed(context);
} else if (isConnectedMobile()) {
return TextSecurePreferences.getMobileMediaDownloadAllowed(context);
} else {
return Collections.emptySet();
}
}
@Override
public boolean isPresent() {
final AttachmentId attachmentId = new AttachmentId(partRowId, partUniqueId);
final AttachmentDatabase db = DatabaseFactory.getAttachmentDatabase(context);
final Attachment attachment = db.getAttachment(null, attachmentId);
if (attachment == null) {
Log.w(TAG, "attachment was null, returning vacuous true");
return true;
}
Log.w(TAG, "part transfer progress is " + attachment.getTransferState());
switch (attachment.getTransferState()) {
case AttachmentDatabase.TRANSFER_PROGRESS_STARTED:
return true;
case AttachmentDatabase.TRANSFER_PROGRESS_AUTO_PENDING:
final Set<String> allowedTypes = getAllowedAutoDownloadTypes();
final String contentType = attachment.getContentType();
boolean isAllowed;
if (attachment.isVoiceNote() || (MediaUtil.isAudio(attachment) && TextUtils.isEmpty(attachment.getFileName()))) {
isAllowed = isConnectedWifi() || isConnectedMobile();
} else if (isNonDocumentType(contentType)) {
isAllowed = allowedTypes.contains(MediaUtil.getDiscreteMimeType(contentType));
} else {
isAllowed = allowedTypes.contains("documents");
}
/// XXX WTF -- This is *hella* gross. A requirement shouldn't have the side effect of
// *modifying the database* just by calling isPresent().
if (isAllowed) db.setTransferState(messageId, attachmentId, AttachmentDatabase.TRANSFER_PROGRESS_STARTED);
return isAllowed;
default:
return false;
}
}
private boolean isNonDocumentType(String contentType) {
return
MediaUtil.isImageType(contentType) ||
MediaUtil.isVideoType(contentType) ||
MediaUtil.isAudioType(contentType);
}
}

View File

@ -1,18 +0,0 @@
package org.thoughtcrime.securesms.jobs.requirements;
import org.whispersystems.jobqueue.requirements.RequirementListener;
import org.whispersystems.jobqueue.requirements.RequirementProvider;
public class MediaNetworkRequirementProvider implements RequirementProvider {
private RequirementListener listener;
public void notifyMediaControlEvent() {
if (listener != null) listener.onRequirementStatusChanged();
}
@Override
public void setListener(RequirementListener listener) {
this.listener = listener;
}
}

View File

@ -109,7 +109,7 @@ public abstract class Slide {
public boolean isPendingDownload() {
return getTransferState() == AttachmentDatabase.TRANSFER_PROGRESS_FAILED ||
getTransferState() == AttachmentDatabase.TRANSFER_PROGRESS_AUTO_PENDING;
getTransferState() == AttachmentDatabase.TRANSFER_PROGRESS_PENDING;
}
public long getTransferState() {

View File

@ -0,0 +1,73 @@
package org.thoughtcrime.securesms.util;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
import org.thoughtcrime.securesms.attachments.Attachment;
import java.util.Collections;
import java.util.Set;
public class AttachmentUtil {
private static final String TAG = AttachmentUtil.class.getSimpleName();
public static boolean isAutoDownloadPermitted(@NonNull Context context, @Nullable Attachment attachment) {
if (attachment == null) {
Log.w(TAG, "attachment was null, returning vacuous true");
return true;
}
Set<String> allowedTypes = getAllowedAutoDownloadTypes(context);
String contentType = attachment.getContentType();
if (attachment.isVoiceNote() || (MediaUtil.isAudio(attachment) && TextUtils.isEmpty(attachment.getFileName()))) {
return true;
} else if (isNonDocumentType(contentType)) {
return allowedTypes.contains(MediaUtil.getDiscreteMimeType(contentType));
} else {
return allowedTypes.contains("documents");
}
}
private static boolean isNonDocumentType(String contentType) {
return
MediaUtil.isImageType(contentType) ||
MediaUtil.isVideoType(contentType) ||
MediaUtil.isAudioType(contentType);
}
private static @NonNull Set<String> getAllowedAutoDownloadTypes(@NonNull Context context) {
if (isConnectedWifi(context)) return TextSecurePreferences.getWifiMediaDownloadAllowed(context);
else if (isConnectedRoaming(context)) return TextSecurePreferences.getRoamingMediaDownloadAllowed(context);
else if (isConnectedMobile(context)) return TextSecurePreferences.getMobileMediaDownloadAllowed(context);
else return Collections.emptySet();
}
private static NetworkInfo getNetworkInfo(@NonNull Context context) {
return ServiceUtil.getConnectivityManager(context).getActiveNetworkInfo();
}
private static boolean isConnectedWifi(@NonNull Context context) {
final NetworkInfo info = getNetworkInfo(context);
return info != null && info.isConnected() && info.getType() == ConnectivityManager.TYPE_WIFI;
}
private static boolean isConnectedMobile(@NonNull Context context) {
final NetworkInfo info = getNetworkInfo(context);
return info != null && info.isConnected() && info.getType() == ConnectivityManager.TYPE_MOBILE;
}
private static boolean isConnectedRoaming(@NonNull Context context) {
final NetworkInfo info = getNetworkInfo(context);
return info != null && info.isConnected() && info.isRoaming() && info.getType() == ConnectivityManager.TYPE_MOBILE;
}
}

View File

@ -17,7 +17,7 @@ public class AttachmentDownloadJobTest extends BaseUnitTest {
@Override
public void setUp() throws Exception {
super.setUp();
job = new AttachmentDownloadJob(context, 1L, new AttachmentId(1L, 1L));
job = new AttachmentDownloadJob(context, 1L, new AttachmentId(1L, 1L), false);
}
@Test(expected = InvalidPartException.class)