Add tests for AvatarPhotoController.

To facilitate adding tests, the class is modified to allow the
Activity to be mocked.

A small amount of clean-up is also performed, to remove AsyncTask
usages and remove some unnecessary code.

Bug: 221223842
Test: atest AvatarPhotoControllerTest
Change-Id: I70f3e8dcd371f48c780b29fada002dbd2f174c2d
This commit is contained in:
Oli Lan
2022-02-24 15:52:13 +00:00
parent d62c6e6c7d
commit e8e79f8900
5 changed files with 464 additions and 86 deletions

View File

@@ -21,7 +21,6 @@ import android.content.ClipData;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
@@ -30,15 +29,15 @@ import android.graphics.Paint;
import android.graphics.RectF;
import android.media.ExifInterface;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.StrictMode;
import android.provider.ContactsContract;
import android.provider.MediaStore;
import android.util.EventLog;
import android.util.Log;
import androidx.core.content.FileProvider;
import com.android.settingslib.utils.ThreadUtils;
import libcore.io.Streams;
import java.io.File;
@@ -47,16 +46,35 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.ExecutionException;
class AvatarPhotoController {
interface AvatarUi {
boolean isFinishing();
void returnUriResult(Uri uri);
void startActivityForResult(Intent intent, int resultCode);
int getPhotoSize();
boolean canCropPhoto();
}
interface ContextInjector {
File getCacheDir();
Uri createTempImageUri(File parentDir, String fileName, boolean purge);
ContentResolver getContentResolver();
}
private static final String TAG = "AvatarPhotoController";
private static final int REQUEST_CODE_CHOOSE_PHOTO = 1001;
private static final int REQUEST_CODE_TAKE_PHOTO = 1002;
private static final int REQUEST_CODE_CROP_PHOTO = 1003;
// in rare cases we get a null Cursor when querying for DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI
// so we need a default photo size
private static final int DEFAULT_PHOTO_SIZE = 500;
static final int REQUEST_CODE_CHOOSE_PHOTO = 1001;
static final int REQUEST_CODE_TAKE_PHOTO = 1002;
static final int REQUEST_CODE_CROP_PHOTO = 1003;
private static final String IMAGES_DIR = "multi_user";
private static final String CROP_PICTURE_FILE_NAME = "CropEditUserPhoto.jpg";
@@ -64,22 +82,24 @@ class AvatarPhotoController {
private final int mPhotoSize;
private final AvatarPickerActivity mActivity;
private final String mFileAuthority;
private final AvatarUi mAvatarUi;
private final ContextInjector mContextInjector;
private final File mImagesDir;
private final Uri mCropPictureUri;
private final Uri mTakePictureUri;
AvatarPhotoController(AvatarPickerActivity activity, boolean waiting, String fileAuthority) {
mActivity = activity;
mFileAuthority = fileAuthority;
AvatarPhotoController(AvatarUi avatarUi, ContextInjector contextInjector, boolean waiting) {
mAvatarUi = avatarUi;
mContextInjector = contextInjector;
mImagesDir = new File(activity.getCacheDir(), IMAGES_DIR);
mImagesDir = new File(mContextInjector.getCacheDir(), IMAGES_DIR);
mImagesDir.mkdir();
mCropPictureUri = createTempImageUri(activity, CROP_PICTURE_FILE_NAME, !waiting);
mTakePictureUri = createTempImageUri(activity, TAKE_PICTURE_FILE_NAME, !waiting);
mPhotoSize = getPhotoSize(activity);
mCropPictureUri =
mContextInjector.createTempImageUri(mImagesDir, CROP_PICTURE_FILE_NAME, !waiting);
mTakePictureUri =
mContextInjector.createTempImageUri(mImagesDir, TAKE_PICTURE_FILE_NAME, !waiting);
mPhotoSize = mAvatarUi.getPhotoSize();
}
/**
@@ -102,16 +122,12 @@ class AvatarPhotoController {
switch (requestCode) {
case REQUEST_CODE_CROP_PHOTO:
mActivity.returnUriResult(pictureUri);
mAvatarUi.returnUriResult(pictureUri);
return true;
case REQUEST_CODE_TAKE_PHOTO:
case REQUEST_CODE_CHOOSE_PHOTO:
if (mTakePictureUri.equals(pictureUri)) {
if (PhotoCapabilityUtils.canCropPhoto(mActivity)) {
cropPhoto();
} else {
onPhotoNotCropped(pictureUri);
}
cropPhoto();
} else {
copyAndCropPhoto(pictureUri);
}
@@ -123,49 +139,47 @@ class AvatarPhotoController {
void takePhoto() {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE_SECURE);
appendOutputExtra(intent, mTakePictureUri);
mActivity.startActivityForResult(intent, REQUEST_CODE_TAKE_PHOTO);
mAvatarUi.startActivityForResult(intent, REQUEST_CODE_TAKE_PHOTO);
}
void choosePhoto() {
Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES, null);
intent.setType("image/*");
mActivity.startActivityForResult(intent, REQUEST_CODE_CHOOSE_PHOTO);
mAvatarUi.startActivityForResult(intent, REQUEST_CODE_CHOOSE_PHOTO);
}
private void copyAndCropPhoto(final Uri pictureUri) {
// TODO: Replace AsyncTask
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
final ContentResolver cr = mActivity.getContentResolver();
try {
ThreadUtils.postOnBackgroundThread(() -> {
final ContentResolver cr = mContextInjector.getContentResolver();
try (InputStream in = cr.openInputStream(pictureUri);
OutputStream out = cr.openOutputStream(mTakePictureUri)) {
Streams.copy(in, out);
} catch (IOException e) {
Log.w(TAG, "Failed to copy photo", e);
return;
}
return null;
}
@Override
protected void onPostExecute(Void result) {
if (!mActivity.isFinishing() && !mActivity.isDestroyed()) {
cropPhoto();
}
}
}.execute();
ThreadUtils.postOnMainThread(() -> {
if (!mAvatarUi.isFinishing()) {
cropPhoto();
}
});
}).get();
} catch (InterruptedException | ExecutionException e) {
Log.e(TAG, "Error performing copy-and-crop", e);
}
}
private void cropPhoto() {
// TODO: Use a public intent, when there is one.
Intent intent = new Intent("com.android.camera.action.CROP");
intent.setDataAndType(mTakePictureUri, "image/*");
appendOutputExtra(intent, mCropPictureUri);
appendCropExtras(intent);
if (intent.resolveActivity(mActivity.getPackageManager()) != null) {
if (mAvatarUi.canCropPhoto()) {
// TODO: Use a public intent, when there is one.
Intent intent = new Intent("com.android.camera.action.CROP");
intent.setDataAndType(mTakePictureUri, "image/*");
appendOutputExtra(intent, mCropPictureUri);
appendCropExtras(intent);
try {
StrictMode.disableDeathOnFileUriExposure();
mActivity.startActivityForResult(intent, REQUEST_CODE_CROP_PHOTO);
mAvatarUi.startActivityForResult(intent, REQUEST_CODE_CROP_PHOTO);
} finally {
StrictMode.enableDeathOnFileUriExposure();
}
@@ -192,24 +206,22 @@ class AvatarPhotoController {
}
private void onPhotoNotCropped(final Uri data) {
// TODO: Replace AsyncTask to avoid possible memory leaks and handle configuration change
new AsyncTask<Void, Void, Bitmap>() {
@Override
protected Bitmap doInBackground(Void... params) {
try {
ThreadUtils.postOnBackgroundThread(() -> {
// Scale and crop to a square aspect ratio
Bitmap croppedImage = Bitmap.createBitmap(mPhotoSize, mPhotoSize,
Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(croppedImage);
Bitmap fullImage;
try {
InputStream imageStream = mActivity.getContentResolver()
InputStream imageStream = mContextInjector.getContentResolver()
.openInputStream(data);
fullImage = BitmapFactory.decodeStream(imageStream);
} catch (FileNotFoundException fe) {
return null;
return;
}
if (fullImage != null) {
int rotation = getRotation(mActivity, data);
int rotation = getRotation(data);
final int squareSize = Math.min(fullImage.getWidth(),
fullImage.getHeight());
final int left = (fullImage.getWidth() - squareSize) / 2;
@@ -222,29 +234,27 @@ class AvatarPhotoController {
matrix.setRectToRect(rectSource, rectDest, Matrix.ScaleToFit.CENTER);
matrix.postRotate(rotation, mPhotoSize / 2f, mPhotoSize / 2f);
canvas.drawBitmap(fullImage, matrix, new Paint());
return croppedImage;
} else {
// Bah! Got nothin.
return null;
}
}
saveBitmapToFile(croppedImage, new File(mImagesDir, CROP_PICTURE_FILE_NAME));
@Override
protected void onPostExecute(Bitmap bitmap) {
saveBitmapToFile(bitmap, new File(mImagesDir, CROP_PICTURE_FILE_NAME));
mActivity.returnUriResult(mCropPictureUri);
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
ThreadUtils.postOnMainThread(() -> {
mAvatarUi.returnUriResult(mCropPictureUri);
});
}
}).get();
} catch (InterruptedException | ExecutionException e) {
Log.e(TAG, "Error performing internal crop", e);
}
}
/**
* Reads the image's exif data and determines the rotation degree needed to display the image
* in portrait mode.
*/
private int getRotation(Context context, Uri selectedImage) {
private int getRotation(Uri selectedImage) {
int rotation = -1;
try {
InputStream imageStream = context.getContentResolver().openInputStream(selectedImage);
InputStream imageStream =
mContextInjector.getContentResolver().openInputStream(selectedImage);
ExifInterface exif = new ExifInterface(imageStream);
rotation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1);
} catch (IOException exception) {
@@ -274,24 +284,66 @@ class AvatarPhotoController {
}
}
private static int getPhotoSize(Context context) {
try (Cursor cursor = context.getContentResolver().query(
ContactsContract.DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI,
new String[]{ContactsContract.DisplayPhoto.DISPLAY_MAX_DIM}, null, null, null)) {
if (cursor != null) {
cursor.moveToFirst();
return cursor.getInt(0);
} else {
return DEFAULT_PHOTO_SIZE;
}
static class AvatarUiImpl implements AvatarUi {
private final AvatarPickerActivity mActivity;
AvatarUiImpl(AvatarPickerActivity activity) {
mActivity = activity;
}
@Override
public boolean isFinishing() {
return mActivity.isFinishing() || mActivity.isDestroyed();
}
@Override
public void returnUriResult(Uri uri) {
mActivity.returnUriResult(uri);
}
@Override
public void startActivityForResult(Intent intent, int resultCode) {
mActivity.startActivityForResult(intent, resultCode);
}
@Override
public int getPhotoSize() {
return mActivity.getResources()
.getDimensionPixelSize(com.android.internal.R.dimen.user_icon_size);
}
@Override
public boolean canCropPhoto() {
return PhotoCapabilityUtils.canCropPhoto(mActivity);
}
}
private Uri createTempImageUri(Context context, String fileName, boolean purge) {
final File fullPath = new File(mImagesDir, fileName);
if (purge) {
fullPath.delete();
static class ContextInjectorImpl implements ContextInjector {
private final Context mContext;
private final String mFileAuthority;
ContextInjectorImpl(Context context, String fileAuthority) {
mContext = context;
mFileAuthority = fileAuthority;
}
@Override
public File getCacheDir() {
return mContext.getCacheDir();
}
@Override
public Uri createTempImageUri(File parentDir, String fileName, boolean purge) {
final File fullPath = new File(parentDir, fileName);
if (purge) {
fullPath.delete();
}
return FileProvider.getUriForFile(mContext, mFileAuthority, fullPath);
}
@Override
public ContentResolver getContentResolver() {
return mContext.getContentResolver();
}
return FileProvider.getUriForFile(context, mFileAuthority, fullPath);
}
}

View File

@@ -94,7 +94,9 @@ public class AvatarPickerActivity extends Activity {
restoreState(savedInstanceState);
mAvatarPhotoController = new AvatarPhotoController(
this, mWaitingForActivityResult, getFileAuthority());
new AvatarPhotoController.AvatarUiImpl(this),
new AvatarPhotoController.ContextInjectorImpl(this, getFileAuthority()),
mWaitingForActivityResult);
}
private void setUpButtons() {

View File

@@ -25,10 +25,19 @@
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<application>
<uses-library android:name="android.test.runner" />
<activity android:name=".drawer.SettingsDrawerActivityTest$TestActivity"/>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.android.settingslib.test"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"

View File

@@ -0,0 +1,20 @@
<!--
~ Copyright (C) 2022 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Offer access to files under Context.getCacheDir() -->
<cache-path name="my_cache" />
</paths>

View File

@@ -0,0 +1,295 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.users;
import static com.android.settingslib.users.AvatarPhotoController.REQUEST_CODE_CHOOSE_PHOTO;
import static com.android.settingslib.users.AvatarPhotoController.REQUEST_CODE_CROP_PHOTO;
import static com.android.settingslib.users.AvatarPhotoController.REQUEST_CODE_TAKE_PHOTO;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.provider.MediaStore;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@RunWith(AndroidJUnit4.class)
public class AvatarPhotoControllerTest {
private static final long TIMEOUT_MILLIS = 5000;
private static final int PHOTO_SIZE = 200;
@Mock AvatarPhotoController.AvatarUi mMockAvatarUi;
private File mImagesDir;
private AvatarPhotoController mController;
private Uri mTakePhotoUri = Uri.parse(
"content://com.android.settingslib.test/my_cache/multi_user/TakeEditUserPhoto.jpg");
private Uri mCropPhotoUri = Uri.parse(
"content://com.android.settingslib.test/my_cache/multi_user/CropEditUserPhoto.jpg");
private Context mContext = InstrumentationRegistry.getTargetContext();
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mMockAvatarUi.getPhotoSize()).thenReturn(PHOTO_SIZE);
mImagesDir = new File(
InstrumentationRegistry.getTargetContext().getCacheDir(), "multi_user");
mImagesDir.mkdir();
AvatarPhotoController.ContextInjector contextInjector =
new AvatarPhotoController.ContextInjectorImpl(
InstrumentationRegistry.getTargetContext(), "com.android.settingslib.test");
mController = new AvatarPhotoController(mMockAvatarUi, contextInjector, false);
}
@After
public void tearDown() {
mImagesDir.delete();
}
@Test
public void takePhotoHasCorrectIntentAndResultCode() {
mController.takePhoto();
verifyStartActivityForResult(
MediaStore.ACTION_IMAGE_CAPTURE_SECURE, REQUEST_CODE_TAKE_PHOTO);
}
@Test
public void choosePhotoHasCorrectIntentAndResultCode() {
mController.choosePhoto();
verifyStartActivityForResult(
MediaStore.ACTION_PICK_IMAGES, REQUEST_CODE_CHOOSE_PHOTO);
}
@Test
public void takePhotoIsFollowedByCropWhenSupported() throws IOException {
when(mMockAvatarUi.canCropPhoto()).thenReturn(true);
new File(mImagesDir, "file.txt").createNewFile();
Intent intent = new Intent();
intent.setData(Uri.parse(
"content://com.android.settingslib.test/my_cache/multi_user/file.txt"));
mController.onActivityResult(
REQUEST_CODE_TAKE_PHOTO, Activity.RESULT_OK, intent);
verifyStartActivityForResult(
"com.android.camera.action.CROP", REQUEST_CODE_CROP_PHOTO);
}
@Test
public void takePhotoIsNotFollowedByCropWhenResultCodeNotOk() throws IOException {
when(mMockAvatarUi.canCropPhoto()).thenReturn(true);
new File(mImagesDir, "file.txt").createNewFile();
Intent intent = new Intent();
intent.setData(Uri.parse(
"content://com.android.settingslib.test/my_cache/multi_user/file.txt"));
mController.onActivityResult(
REQUEST_CODE_TAKE_PHOTO, Activity.RESULT_CANCELED, intent);
verify(mMockAvatarUi, never()).startActivityForResult(any(), anyInt());
}
@Test
public void takePhotoIsFollowedByCropWhenTakePhotoUriReturned() throws IOException {
when(mMockAvatarUi.canCropPhoto()).thenReturn(true);
new File(mImagesDir, "TakeEditUserPhoto.jpg").createNewFile();
Intent intent = new Intent();
intent.setData(mTakePhotoUri);
mController.onActivityResult(
REQUEST_CODE_TAKE_PHOTO, Activity.RESULT_OK, intent);
verifyStartActivityForResult(
"com.android.camera.action.CROP", REQUEST_CODE_CROP_PHOTO);
}
@Test
public void takePhotoIsNotFollowedByCropIntentWhenCropNotSupported() throws IOException {
when(mMockAvatarUi.canCropPhoto()).thenReturn(false);
File file = new File(mImagesDir, "file.txt");
saveBitmapToFile(file);
Intent intent = new Intent();
intent.setData(Uri.parse(
"content://com.android.settingslib.test/my_cache/multi_user/file.txt"));
mController.onActivityResult(
REQUEST_CODE_TAKE_PHOTO, Activity.RESULT_OK, intent);
verify(mMockAvatarUi, never()).startActivityForResult(any(), anyInt());
}
@Test
public void takePhotoIsFollowedByInternalCropWhenCropNotSupported() throws IOException {
when(mMockAvatarUi.canCropPhoto()).thenReturn(false);
File file = new File(mImagesDir, "file.txt");
saveBitmapToFile(file);
Intent intent = new Intent();
intent.setData(Uri.parse(
"content://com.android.settingslib.test/my_cache/multi_user/file.txt"));
mController.onActivityResult(
REQUEST_CODE_TAKE_PHOTO, Activity.RESULT_OK, intent);
verify(mMockAvatarUi, timeout(TIMEOUT_MILLIS)).returnUriResult(mCropPhotoUri);
InputStream imageStream = mContext.getContentResolver().openInputStream(mCropPhotoUri);
Bitmap bitmap = BitmapFactory.decodeStream(imageStream);
assertThat(bitmap.getWidth()).isEqualTo(PHOTO_SIZE);
assertThat(bitmap.getHeight()).isEqualTo(PHOTO_SIZE);
}
@Test
public void choosePhotoIsFollowedByCropWhenSupported() throws IOException {
when(mMockAvatarUi.canCropPhoto()).thenReturn(true);
new File(mImagesDir, "file.txt").createNewFile();
Intent intent = new Intent();
intent.setData(Uri.parse(
"content://com.android.settingslib.test/my_cache/multi_user/file.txt"));
mController.onActivityResult(
REQUEST_CODE_CHOOSE_PHOTO, Activity.RESULT_OK, intent);
verifyStartActivityForResult(
"com.android.camera.action.CROP", REQUEST_CODE_CROP_PHOTO);
}
@Test
public void choosePhotoIsNotFollowedByCropWhenResultCodeNotOk() throws IOException {
when(mMockAvatarUi.canCropPhoto()).thenReturn(true);
new File(mImagesDir, "file.txt").createNewFile();
Intent intent = new Intent();
intent.setData(Uri.parse(
"content://com.android.settingslib.test/my_cache/multi_user/file.txt"));
mController.onActivityResult(
REQUEST_CODE_CHOOSE_PHOTO, Activity.RESULT_CANCELED, intent);
verify(mMockAvatarUi, never()).startActivityForResult(any(), anyInt());
}
@Test
public void choosePhotoIsFollowedByCropWhenTakePhotoUriReturned() throws IOException {
when(mMockAvatarUi.canCropPhoto()).thenReturn(true);
new File(mImagesDir, "TakeEditUserPhoto.jpg").createNewFile();
Intent intent = new Intent();
intent.setData(mTakePhotoUri);
mController.onActivityResult(
REQUEST_CODE_CHOOSE_PHOTO, Activity.RESULT_OK, intent);
verifyStartActivityForResult(
"com.android.camera.action.CROP", REQUEST_CODE_CROP_PHOTO);
}
@Test
public void choosePhotoIsNotFollowedByCropIntentWhenCropNotSupported() throws IOException {
when(mMockAvatarUi.canCropPhoto()).thenReturn(false);
File file = new File(mImagesDir, "file.txt");
saveBitmapToFile(file);
Intent intent = new Intent();
intent.setData(Uri.parse(
"content://com.android.settingslib.test/my_cache/multi_user/file.txt"));
mController.onActivityResult(
REQUEST_CODE_CHOOSE_PHOTO, Activity.RESULT_OK, intent);
verify(mMockAvatarUi, never()).startActivityForResult(any(), anyInt());
verify(mMockAvatarUi, timeout(TIMEOUT_MILLIS)).returnUriResult(mCropPhotoUri);
}
@Test
public void cropPhotoResultIsReturnedIfResultOkAndContent() {
Intent intent = new Intent();
intent.setData(mCropPhotoUri);
mController.onActivityResult(REQUEST_CODE_CROP_PHOTO, Activity.RESULT_OK, intent);
verify(mMockAvatarUi, timeout(TIMEOUT_MILLIS)).returnUriResult(mCropPhotoUri);
}
@Test
public void cropPhotoResultIsNotReturnedIfResultCancel() {
Intent intent = new Intent();
intent.setData(mCropPhotoUri);
mController.onActivityResult(REQUEST_CODE_CROP_PHOTO, Activity.RESULT_CANCELED, intent);
verify(mMockAvatarUi, timeout(TIMEOUT_MILLIS).times(0)).returnUriResult(mCropPhotoUri);
}
@Test
public void cropPhotoResultIsNotReturnedIfResultNotContent() {
Intent intent = new Intent();
intent.setData(Uri.parse("file://test"));
mController.onActivityResult(REQUEST_CODE_CROP_PHOTO, Activity.RESULT_OK, intent);
verify(mMockAvatarUi, timeout(TIMEOUT_MILLIS).times(0)).returnUriResult(mCropPhotoUri);
}
private void verifyStartActivityForResult(String action, int resultCode) {
ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
verify(mMockAvatarUi, timeout(TIMEOUT_MILLIS))
.startActivityForResult(captor.capture(), eq(resultCode));
assertThat(captor.getValue().getAction()).isEqualTo(action);
}
private void saveBitmapToFile(File file) throws IOException {
Bitmap bitmap = Bitmap.createBitmap(500, 500, Bitmap.Config.ARGB_8888);
OutputStream os = new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, os);
os.flush();
os.close();
}
}