parent
							
								
									3e6e28e688
								
							
						
					
					
						commit
						12845da91a
					
				@ -0,0 +1,64 @@
 | 
			
		||||
package org.thoughtcrime.securesms.database;
 | 
			
		||||
 | 
			
		||||
import android.net.Uri;
 | 
			
		||||
import android.test.InstrumentationTestCase;
 | 
			
		||||
 | 
			
		||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
 | 
			
		||||
 | 
			
		||||
import java.io.FileNotFoundException;
 | 
			
		||||
import java.io.InputStream;
 | 
			
		||||
 | 
			
		||||
import ws.com.google.android.mms.pdu.PduPart;
 | 
			
		||||
 | 
			
		||||
import static org.mockito.Matchers.any;
 | 
			
		||||
import static org.mockito.Matchers.anyFloat;
 | 
			
		||||
import static org.mockito.Matchers.anyLong;
 | 
			
		||||
import static org.mockito.Matchers.eq;
 | 
			
		||||
import static org.mockito.Mockito.doNothing;
 | 
			
		||||
import static org.mockito.Mockito.doReturn;
 | 
			
		||||
import static org.mockito.Mockito.mock;
 | 
			
		||||
import static org.mockito.Mockito.never;
 | 
			
		||||
import static org.mockito.Mockito.spy;
 | 
			
		||||
import static org.mockito.Mockito.times;
 | 
			
		||||
import static org.mockito.Mockito.verify;
 | 
			
		||||
import static org.mockito.Mockito.when;
 | 
			
		||||
 | 
			
		||||
public class PartDatabaseTest extends InstrumentationTestCase {
 | 
			
		||||
  private static final long PART_ID = 1L;
 | 
			
		||||
 | 
			
		||||
  private PartDatabase database;
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public void setUp() {
 | 
			
		||||
    database = spy(DatabaseFactory.getPartDatabase(getInstrumentation().getTargetContext()));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void testTaskNotRunWhenThumbnailExists() throws Exception {
 | 
			
		||||
    when(database.getPart(eq(PART_ID))).thenReturn(getPduPartSkeleton("x/x"));
 | 
			
		||||
    doReturn(mock(InputStream.class)).when(database).getDataStream(any(MasterSecret.class), anyLong(), eq("thumbnail"));
 | 
			
		||||
 | 
			
		||||
    database.getThumbnailStream(null, PART_ID);
 | 
			
		||||
 | 
			
		||||
    verify(database, never()).updatePartThumbnail(any(MasterSecret.class), anyLong(), any(PduPart.class), any(InputStream.class), anyFloat());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void testTaskRunWhenThumbnailMissing() throws Exception {
 | 
			
		||||
    when(database.getPart(eq(PART_ID))).thenReturn(getPduPartSkeleton("image/png"));
 | 
			
		||||
    doReturn(null).when(database).getDataStream(any(MasterSecret.class), anyLong(), eq("thumbnail"));
 | 
			
		||||
    doNothing().when(database).updatePartThumbnail(any(MasterSecret.class), anyLong(), any(PduPart.class), any(InputStream.class), anyFloat());
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      database.new ThumbnailFetchCallable(mock(MasterSecret.class), PART_ID).call();
 | 
			
		||||
      throw new AssertionError("didn't try to generate thumbnail");
 | 
			
		||||
    } catch (FileNotFoundException fnfe) {
 | 
			
		||||
      // success
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private PduPart getPduPartSkeleton(String contentType) {
 | 
			
		||||
    PduPart part = new PduPart();
 | 
			
		||||
    part.setContentType(contentType.getBytes());
 | 
			
		||||
    part.setDataUri(Uri.EMPTY);
 | 
			
		||||
    return part;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -1,126 +0,0 @@
 | 
			
		||||
package org.thoughtcrime.securesms.jobs;
 | 
			
		||||
 | 
			
		||||
import android.content.Context;
 | 
			
		||||
import android.graphics.Bitmap;
 | 
			
		||||
import android.graphics.Bitmap.CompressFormat;
 | 
			
		||||
import android.util.Log;
 | 
			
		||||
import android.util.Pair;
 | 
			
		||||
 | 
			
		||||
import org.thoughtcrime.securesms.R;
 | 
			
		||||
import org.thoughtcrime.securesms.crypto.AsymmetricMasterCipher;
 | 
			
		||||
import org.thoughtcrime.securesms.crypto.AsymmetricMasterSecret;
 | 
			
		||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
 | 
			
		||||
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
 | 
			
		||||
import org.thoughtcrime.securesms.crypto.SecurityEvent;
 | 
			
		||||
import org.thoughtcrime.securesms.crypto.SmsCipher;
 | 
			
		||||
import org.thoughtcrime.securesms.crypto.storage.TextSecureAxolotlStore;
 | 
			
		||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
 | 
			
		||||
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
 | 
			
		||||
import org.thoughtcrime.securesms.database.NoSuchMessageException;
 | 
			
		||||
import org.thoughtcrime.securesms.database.PartDatabase;
 | 
			
		||||
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
 | 
			
		||||
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
 | 
			
		||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
 | 
			
		||||
import org.thoughtcrime.securesms.service.KeyCachingService;
 | 
			
		||||
import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage;
 | 
			
		||||
import org.thoughtcrime.securesms.sms.IncomingEndSessionMessage;
 | 
			
		||||
import org.thoughtcrime.securesms.sms.IncomingKeyExchangeMessage;
 | 
			
		||||
import org.thoughtcrime.securesms.sms.IncomingPreKeyBundleMessage;
 | 
			
		||||
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
 | 
			
		||||
import org.thoughtcrime.securesms.sms.MessageSender;
 | 
			
		||||
import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage;
 | 
			
		||||
import org.thoughtcrime.securesms.util.BitmapDecodingException;
 | 
			
		||||
import org.thoughtcrime.securesms.util.BitmapUtil;
 | 
			
		||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
 | 
			
		||||
import org.thoughtcrime.securesms.util.Util;
 | 
			
		||||
import org.whispersystems.jobqueue.JobParameters;
 | 
			
		||||
import org.whispersystems.libaxolotl.DuplicateMessageException;
 | 
			
		||||
import org.whispersystems.libaxolotl.InvalidMessageException;
 | 
			
		||||
import org.whispersystems.libaxolotl.InvalidVersionException;
 | 
			
		||||
import org.whispersystems.libaxolotl.LegacyMessageException;
 | 
			
		||||
import org.whispersystems.libaxolotl.NoSessionException;
 | 
			
		||||
import org.whispersystems.libaxolotl.StaleKeyExchangeException;
 | 
			
		||||
import org.whispersystems.libaxolotl.UntrustedIdentityException;
 | 
			
		||||
import org.whispersystems.libaxolotl.util.guava.Optional;
 | 
			
		||||
import org.whispersystems.textsecure.api.messages.TextSecureGroup;
 | 
			
		||||
 | 
			
		||||
import java.io.ByteArrayInputStream;
 | 
			
		||||
import java.io.ByteArrayOutputStream;
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.io.FileNotFoundException;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
 | 
			
		||||
import ws.com.google.android.mms.ContentType;
 | 
			
		||||
import ws.com.google.android.mms.MmsException;
 | 
			
		||||
import ws.com.google.android.mms.pdu.PduPart;
 | 
			
		||||
 | 
			
		||||
public class ThumbnailGenerateJob extends MasterSecretJob {
 | 
			
		||||
 | 
			
		||||
  private static final String TAG = ThumbnailGenerateJob.class.getSimpleName();
 | 
			
		||||
 | 
			
		||||
  private final long partId;
 | 
			
		||||
 | 
			
		||||
  public ThumbnailGenerateJob(Context context, long partId) {
 | 
			
		||||
    super(context, JobParameters.newBuilder()
 | 
			
		||||
                                .withRequirement(new MasterSecretRequirement(context))
 | 
			
		||||
                                .create());
 | 
			
		||||
 | 
			
		||||
    this.partId = partId;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public void onAdded() { }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public void onRun(MasterSecret masterSecret) throws MmsException {
 | 
			
		||||
    PartDatabase database = DatabaseFactory.getPartDatabase(context);
 | 
			
		||||
    PduPart part = database.getPart(partId);
 | 
			
		||||
 | 
			
		||||
    if (part.getThumbnailUri() != null) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    long startMillis = System.currentTimeMillis();
 | 
			
		||||
    Bitmap thumbnail = generateThumbnailForPart(masterSecret, part);
 | 
			
		||||
 | 
			
		||||
    if (thumbnail != null) {
 | 
			
		||||
      ByteArrayOutputStream thumbnailBytes = new ByteArrayOutputStream();
 | 
			
		||||
      thumbnail.compress(CompressFormat.JPEG, 85, thumbnailBytes);
 | 
			
		||||
 | 
			
		||||
      float aspectRatio = (float)thumbnail.getWidth() / (float)thumbnail.getHeight();
 | 
			
		||||
      Log.w(TAG, String.format("generated thumbnail for part #%d, %dx%d (%.3f:1) in %dms",
 | 
			
		||||
                               partId,
 | 
			
		||||
                               thumbnail.getWidth(),
 | 
			
		||||
                               thumbnail.getHeight(),
 | 
			
		||||
                               aspectRatio, System.currentTimeMillis() - startMillis));
 | 
			
		||||
      database.updatePartThumbnail(masterSecret, partId, part, new ByteArrayInputStream(thumbnailBytes.toByteArray()), aspectRatio);
 | 
			
		||||
    } else {
 | 
			
		||||
      Log.w(TAG, "thumbnail not generated");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private Bitmap generateThumbnailForPart(MasterSecret masterSecret, PduPart part) {
 | 
			
		||||
    String contentType = Util.toIsoString(part.getContentType());
 | 
			
		||||
 | 
			
		||||
    if      (ContentType.isImageType(contentType)) return generateImageThumbnail(masterSecret, part);
 | 
			
		||||
    else                                           return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private Bitmap generateImageThumbnail(MasterSecret masterSecret, PduPart part) {
 | 
			
		||||
    try {
 | 
			
		||||
      int maxSize = context.getResources().getDimensionPixelSize(R.dimen.thumbnail_max_size);
 | 
			
		||||
      return BitmapUtil.createScaledBitmap(context, masterSecret, part.getDataUri(), maxSize, maxSize);
 | 
			
		||||
    } catch (FileNotFoundException | BitmapDecodingException | OutOfMemoryError e) {
 | 
			
		||||
      Log.w(TAG, e);
 | 
			
		||||
      return null;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public boolean onShouldRetryThrowable(Exception exception) {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public void onCanceled() { }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,72 @@
 | 
			
		||||
package org.thoughtcrime.securesms.util;
 | 
			
		||||
 | 
			
		||||
import android.content.Context;
 | 
			
		||||
import android.graphics.Bitmap;
 | 
			
		||||
import android.net.Uri;
 | 
			
		||||
import android.util.Log;
 | 
			
		||||
 | 
			
		||||
import org.thoughtcrime.securesms.R;
 | 
			
		||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
 | 
			
		||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
 | 
			
		||||
import org.thoughtcrime.securesms.database.PartDatabase;
 | 
			
		||||
 | 
			
		||||
import java.io.FileNotFoundException;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.io.InputStream;
 | 
			
		||||
import java.util.concurrent.Callable;
 | 
			
		||||
 | 
			
		||||
import ws.com.google.android.mms.ContentType;
 | 
			
		||||
import ws.com.google.android.mms.MmsException;
 | 
			
		||||
import ws.com.google.android.mms.pdu.PduPart;
 | 
			
		||||
 | 
			
		||||
public class MediaUtil {
 | 
			
		||||
  private static final String TAG = MediaUtil.class.getSimpleName();
 | 
			
		||||
 | 
			
		||||
  public static ThumbnailData generateThumbnail(Context context, MasterSecret masterSecret, Uri uri, String type)
 | 
			
		||||
      throws IOException, BitmapDecodingException, OutOfMemoryError
 | 
			
		||||
  {
 | 
			
		||||
    long   startMillis = System.currentTimeMillis();
 | 
			
		||||
    ThumbnailData data;
 | 
			
		||||
    if      (ContentType.isImageType(type)) data = new ThumbnailData(generateImageThumbnail(context, masterSecret, uri));
 | 
			
		||||
    else                                    data = null;
 | 
			
		||||
 | 
			
		||||
    if (data != null) {
 | 
			
		||||
      Log.w(TAG, String.format("generated thumbnail for part, %dx%d (%.3f:1) in %dms",
 | 
			
		||||
                               data.getBitmap().getWidth(), data.getBitmap().getHeight(),
 | 
			
		||||
                               data.getAspectRatio(), System.currentTimeMillis() - startMillis));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return data;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static Bitmap generateImageThumbnail(Context context, MasterSecret masterSecret, Uri uri)
 | 
			
		||||
      throws IOException, BitmapDecodingException, OutOfMemoryError
 | 
			
		||||
  {
 | 
			
		||||
    int maxSize = context.getResources().getDimensionPixelSize(R.dimen.thumbnail_max_size);
 | 
			
		||||
    return BitmapUtil.createScaledBitmap(context, masterSecret, uri, maxSize, maxSize);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static class ThumbnailData {
 | 
			
		||||
    Bitmap bitmap;
 | 
			
		||||
    float aspectRatio;
 | 
			
		||||
 | 
			
		||||
    public ThumbnailData(Bitmap bitmap) {
 | 
			
		||||
      this.bitmap      = bitmap;
 | 
			
		||||
      this.aspectRatio = (float) bitmap.getWidth() / (float) bitmap.getHeight();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Bitmap getBitmap() {
 | 
			
		||||
      return bitmap;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public float getAspectRatio() {
 | 
			
		||||
      return aspectRatio;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public InputStream toDataStream() {
 | 
			
		||||
      InputStream jpegStream  = BitmapUtil.toCompressedJpeg(bitmap);
 | 
			
		||||
      bitmap.recycle();
 | 
			
		||||
      return jpegStream;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
					Loading…
					
					
				
		Reference in New Issue