Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2019 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | package androidx.camera.core; |
| 18 | |
| 19 | import static com.google.common.truth.Truth.assertThat; |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 20 | |
Jelle Fresen | fbb001b | 2020-01-16 15:46:29 +0000 | [diff] [blame] | 21 | import static org.mockito.ArgumentMatchers.any; |
| 22 | import static org.mockito.ArgumentMatchers.anyString; |
| 23 | import static org.mockito.ArgumentMatchers.eq; |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 24 | import static org.mockito.Mockito.mock; |
| 25 | import static org.mockito.Mockito.verify; |
| 26 | import static org.mockito.Mockito.when; |
| 27 | |
Xi Zhang | ec5384d | 2020-01-27 15:01:28 -0800 | [diff] [blame] | 28 | import android.Manifest; |
| 29 | import android.content.ContentResolver; |
| 30 | import android.content.ContentValues; |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 31 | import android.graphics.Bitmap; |
| 32 | import android.graphics.BitmapFactory; |
| 33 | import android.graphics.ImageFormat; |
TY Chang | 8d87936 | 2019-02-21 15:22:15 +0800 | [diff] [blame] | 34 | import android.graphics.Rect; |
Xi Zhang | ec5384d | 2020-01-27 15:01:28 -0800 | [diff] [blame] | 35 | import android.net.Uri; |
Xi Zhang | 6f28a57 | 2020-02-10 10:50:01 -0800 | [diff] [blame] | 36 | import android.os.Environment; |
Xi Zhang | ec5384d | 2020-01-27 15:01:28 -0800 | [diff] [blame] | 37 | import android.os.ParcelFileDescriptor; |
| 38 | import android.provider.MediaStore; |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 39 | import android.util.Base64; |
Xi Zhang | 6f28a57 | 2020-02-10 10:50:01 -0800 | [diff] [blame] | 40 | import android.util.Log; |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 41 | |
Xi Zhang | ec5384d | 2020-01-27 15:01:28 -0800 | [diff] [blame] | 42 | import androidx.annotation.NonNull; |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 43 | import androidx.annotation.Nullable; |
Franklin Wu | 3b4ed3e | 2019-09-13 13:43:29 -0700 | [diff] [blame] | 44 | import androidx.camera.core.ImageSaver.OnImageSavedCallback; |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 45 | import androidx.camera.core.ImageSaver.SaveError; |
Xi Zhang | ec5384d | 2020-01-27 15:01:28 -0800 | [diff] [blame] | 46 | import androidx.test.core.app.ApplicationProvider; |
Trevor McGuire | aa17f67 | 2019-02-14 15:14:46 -0800 | [diff] [blame] | 47 | import androidx.test.ext.junit.runners.AndroidJUnit4; |
Sam Gilbert | e212405 | 2019-05-03 18:36:32 -0400 | [diff] [blame] | 48 | import androidx.test.filters.MediumTest; |
Xi Zhang | ec5384d | 2020-01-27 15:01:28 -0800 | [diff] [blame] | 49 | import androidx.test.rule.GrantPermissionRule; |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 50 | |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 51 | import org.junit.After; |
| 52 | import org.junit.Before; |
Xi Zhang | ec5384d | 2020-01-27 15:01:28 -0800 | [diff] [blame] | 53 | import org.junit.Rule; |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 54 | import org.junit.Test; |
| 55 | import org.junit.runner.RunWith; |
Xi Zhang | ec5384d | 2020-01-27 15:01:28 -0800 | [diff] [blame] | 56 | import org.mockito.ArgumentCaptor; |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 57 | import org.mockito.Mock; |
| 58 | |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 59 | import java.io.File; |
Xi Zhang | ec5384d | 2020-01-27 15:01:28 -0800 | [diff] [blame] | 60 | import java.io.FileNotFoundException; |
| 61 | import java.io.FileOutputStream; |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 62 | import java.io.IOException; |
Xi Zhang | ec5384d | 2020-01-27 15:01:28 -0800 | [diff] [blame] | 63 | import java.io.OutputStream; |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 64 | import java.nio.ByteBuffer; |
WenHung_Teng | 5cc433b | 2019-08-26 11:40:45 +0800 | [diff] [blame] | 65 | import java.util.concurrent.ExecutorService; |
| 66 | import java.util.concurrent.Executors; |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 67 | import java.util.concurrent.Semaphore; |
| 68 | |
Xi Zhang | aa5b7ad | 2020-03-30 12:27:48 -0700 | [diff] [blame] | 69 | /** |
| 70 | * Instrument tests for {@link ImageSaver}. |
| 71 | */ |
Sam Gilbert | e212405 | 2019-05-03 18:36:32 -0400 | [diff] [blame] | 72 | @MediumTest |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 73 | @RunWith(AndroidJUnit4.class) |
Trevor McGuire | e85de57 | 2019-02-15 20:20:24 -0800 | [diff] [blame] | 74 | public class ImageSaverTest { |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 75 | |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 76 | private static final int WIDTH = 160; |
| 77 | private static final int HEIGHT = 120; |
TY Chang | 8d87936 | 2019-02-21 15:22:15 +0800 | [diff] [blame] | 78 | private static final int CROP_WIDTH = 100; |
| 79 | private static final int CROP_HEIGHT = 100; |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 80 | private static final int Y_PIXEL_STRIDE = 1; |
| 81 | private static final int Y_ROW_STRIDE = WIDTH; |
| 82 | private static final int UV_PIXEL_STRIDE = 1; |
| 83 | private static final int UV_ROW_STRIDE = WIDTH / 2; |
Trevor McGuire | 3ae3a1d | 2019-02-14 13:48:45 -0800 | [diff] [blame] | 84 | private static final String JPEG_IMAGE_DATA_BASE_64 = |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 85 | "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEB" |
| 86 | + "AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/2wBDAQEBAQEBAQEBAQEBAQEBAQEBAQEB" |
| 87 | + "AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/wAARCAB4AKADASIA" |
| 88 | + "AhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQA" |
| 89 | + "AAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3" |
| 90 | + "ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWm" |
| 91 | + "p6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEA" |
| 92 | + "AwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSEx" |
| 93 | + "BhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElK" |
| 94 | + "U1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3" |
| 95 | + "uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD/AD/6" |
| 96 | + "KKK/8/8AP/P/AAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiii" |
| 97 | + "gAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKA" |
| 98 | + "CiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAK" |
| 99 | + "KKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAoo" |
| 100 | + "ooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiii" |
| 101 | + "gAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA//9k="; |
Trevor McGuire | 6bf813c | 2019-02-26 10:26:12 -0800 | [diff] [blame] | 102 | // The image used here has a YUV_420_888 format. |
Xi Zhang | ec5384d | 2020-01-27 15:01:28 -0800 | [diff] [blame] | 103 | |
Xi Zhang | 6f28a57 | 2020-02-10 10:50:01 -0800 | [diff] [blame] | 104 | private static final String TAG = "ImageSaverTest"; |
Xi Zhang | aa5b7ad | 2020-03-30 12:27:48 -0700 | [diff] [blame] | 105 | private static final String INVALID_DATA_PATH = "/invalid_path"; |
Xi Zhang | 6f28a57 | 2020-02-10 10:50:01 -0800 | [diff] [blame] | 106 | |
Xi Zhang | ec5384d | 2020-01-27 15:01:28 -0800 | [diff] [blame] | 107 | @Rule |
| 108 | public GrantPermissionRule mStoragePermissionRule = |
| 109 | GrantPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE, |
| 110 | Manifest.permission.READ_EXTERNAL_STORAGE); |
| 111 | |
Trevor McGuire | 6bf813c | 2019-02-26 10:26:12 -0800 | [diff] [blame] | 112 | @Mock |
| 113 | private final ImageProxy mMockYuvImage = mock(ImageProxy.class); |
| 114 | @Mock |
| 115 | private final ImageProxy.PlaneProxy mYPlane = mock(ImageProxy.PlaneProxy.class); |
| 116 | @Mock |
| 117 | private final ImageProxy.PlaneProxy mUPlane = mock(ImageProxy.PlaneProxy.class); |
| 118 | @Mock |
| 119 | private final ImageProxy.PlaneProxy mVPlane = mock(ImageProxy.PlaneProxy.class); |
| 120 | private final ByteBuffer mYBuffer = ByteBuffer.allocateDirect(WIDTH * HEIGHT); |
| 121 | private final ByteBuffer mUBuffer = ByteBuffer.allocateDirect(WIDTH * HEIGHT / 4); |
| 122 | private final ByteBuffer mVBuffer = ByteBuffer.allocateDirect(WIDTH * HEIGHT / 4); |
| 123 | @Mock |
| 124 | private final ImageProxy mMockJpegImage = mock(ImageProxy.class); |
| 125 | @Mock |
| 126 | private final ImageProxy.PlaneProxy mJpegDataPlane = mock(ImageProxy.PlaneProxy.class); |
Trevor McGuire | 3ae3a1d | 2019-02-14 13:48:45 -0800 | [diff] [blame] | 127 | private final ByteBuffer mJpegDataBuffer = |
| 128 | ByteBuffer.wrap(Base64.decode(JPEG_IMAGE_DATA_BASE_64, Base64.DEFAULT)); |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 129 | |
Trevor McGuire | 3ae3a1d | 2019-02-14 13:48:45 -0800 | [diff] [blame] | 130 | private final Semaphore mSemaphore = new Semaphore(0); |
Franklin Wu | 3b4ed3e | 2019-09-13 13:43:29 -0700 | [diff] [blame] | 131 | private final ImageSaver.OnImageSavedCallback mMockCallback = |
| 132 | mock(ImageSaver.OnImageSavedCallback.class); |
| 133 | private final ImageSaver.OnImageSavedCallback mSyncCallback = |
| 134 | new OnImageSavedCallback() { |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 135 | @Override |
Xi Zhang | ec5384d | 2020-01-27 15:01:28 -0800 | [diff] [blame] | 136 | public void onImageSaved( |
| 137 | @NonNull ImageCapture.OutputFileResults outputFileResults) { |
| 138 | mMockCallback.onImageSaved(outputFileResults); |
Trevor McGuire | 3ae3a1d | 2019-02-14 13:48:45 -0800 | [diff] [blame] | 139 | mSemaphore.release(); |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 140 | } |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 141 | |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 142 | @Override |
husaynhakeem | 661b7e9 | 2019-11-22 16:39:36 -0800 | [diff] [blame] | 143 | public void onError(SaveError saveError, String message, |
husaynhakeem | d31b4c5 | 2019-11-11 10:23:35 -0800 | [diff] [blame] | 144 | @Nullable Throwable cause) { |
Xi Zhang | 6f28a57 | 2020-02-10 10:50:01 -0800 | [diff] [blame] | 145 | Log.d(TAG, message, cause); |
Franklin Wu | 3b4ed3e | 2019-09-13 13:43:29 -0700 | [diff] [blame] | 146 | mMockCallback.onError(saveError, message, cause); |
Trevor McGuire | 3ae3a1d | 2019-02-14 13:48:45 -0800 | [diff] [blame] | 147 | mSemaphore.release(); |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 148 | } |
| 149 | }; |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 150 | |
WenHung_Teng | 5cc433b | 2019-08-26 11:40:45 +0800 | [diff] [blame] | 151 | private ExecutorService mBackgroundExecutor; |
Xi Zhang | ec5384d | 2020-01-27 15:01:28 -0800 | [diff] [blame] | 152 | private ContentResolver mContentResolver; |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 153 | |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 154 | @Before |
| 155 | public void setup() { |
Xi Zhang | 6f28a57 | 2020-02-10 10:50:01 -0800 | [diff] [blame] | 156 | createDefaultPictureFolderIfNotExist(); |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 157 | // The YUV image's behavior. |
Trevor McGuire | 3ae3a1d | 2019-02-14 13:48:45 -0800 | [diff] [blame] | 158 | when(mMockYuvImage.getFormat()).thenReturn(ImageFormat.YUV_420_888); |
| 159 | when(mMockYuvImage.getWidth()).thenReturn(WIDTH); |
| 160 | when(mMockYuvImage.getHeight()).thenReturn(HEIGHT); |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 161 | |
Trevor McGuire | 3ae3a1d | 2019-02-14 13:48:45 -0800 | [diff] [blame] | 162 | when(mYPlane.getBuffer()).thenReturn(mYBuffer); |
| 163 | when(mYPlane.getPixelStride()).thenReturn(Y_PIXEL_STRIDE); |
| 164 | when(mYPlane.getRowStride()).thenReturn(Y_ROW_STRIDE); |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 165 | |
Trevor McGuire | 3ae3a1d | 2019-02-14 13:48:45 -0800 | [diff] [blame] | 166 | when(mUPlane.getBuffer()).thenReturn(mUBuffer); |
| 167 | when(mUPlane.getPixelStride()).thenReturn(UV_PIXEL_STRIDE); |
| 168 | when(mUPlane.getRowStride()).thenReturn(UV_ROW_STRIDE); |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 169 | |
Trevor McGuire | 3ae3a1d | 2019-02-14 13:48:45 -0800 | [diff] [blame] | 170 | when(mVPlane.getBuffer()).thenReturn(mVBuffer); |
| 171 | when(mVPlane.getPixelStride()).thenReturn(UV_PIXEL_STRIDE); |
| 172 | when(mVPlane.getRowStride()).thenReturn(UV_ROW_STRIDE); |
| 173 | when(mMockYuvImage.getPlanes()) |
| 174 | .thenReturn(new ImageProxy.PlaneProxy[]{mYPlane, mUPlane, mVPlane}); |
TY Chang | 8d87936 | 2019-02-21 15:22:15 +0800 | [diff] [blame] | 175 | when(mMockYuvImage.getCropRect()).thenReturn(new Rect(0, 0, CROP_WIDTH, CROP_HEIGHT)); |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 176 | |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 177 | // The JPEG image's behavior |
Trevor McGuire | 3ae3a1d | 2019-02-14 13:48:45 -0800 | [diff] [blame] | 178 | when(mMockJpegImage.getFormat()).thenReturn(ImageFormat.JPEG); |
| 179 | when(mMockJpegImage.getWidth()).thenReturn(WIDTH); |
| 180 | when(mMockJpegImage.getHeight()).thenReturn(HEIGHT); |
TY Chang | 8d87936 | 2019-02-21 15:22:15 +0800 | [diff] [blame] | 181 | when(mMockJpegImage.getCropRect()).thenReturn(new Rect(0, 0, CROP_WIDTH, CROP_HEIGHT)); |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 182 | |
Trevor McGuire | 3ae3a1d | 2019-02-14 13:48:45 -0800 | [diff] [blame] | 183 | when(mJpegDataPlane.getBuffer()).thenReturn(mJpegDataBuffer); |
| 184 | when(mMockJpegImage.getPlanes()).thenReturn(new ImageProxy.PlaneProxy[]{mJpegDataPlane}); |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 185 | |
WenHung_Teng | 5cc433b | 2019-08-26 11:40:45 +0800 | [diff] [blame] | 186 | // Set up a background executor for callbacks |
| 187 | mBackgroundExecutor = Executors.newSingleThreadExecutor(); |
Xi Zhang | ec5384d | 2020-01-27 15:01:28 -0800 | [diff] [blame] | 188 | |
| 189 | mContentResolver = ApplicationProvider.getApplicationContext().getContentResolver(); |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 190 | } |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 191 | |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 192 | @After |
| 193 | public void tearDown() { |
WenHung_Teng | 5cc433b | 2019-08-26 11:40:45 +0800 | [diff] [blame] | 194 | mBackgroundExecutor.shutdown(); |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 195 | } |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 196 | |
Xi Zhang | 6f28a57 | 2020-02-10 10:50:01 -0800 | [diff] [blame] | 197 | private void createDefaultPictureFolderIfNotExist() { |
| 198 | File pictureFolder = Environment.getExternalStoragePublicDirectory( |
| 199 | Environment.DIRECTORY_PICTURES); |
| 200 | if (!pictureFolder.exists()) { |
| 201 | pictureFolder.mkdir(); |
| 202 | } |
| 203 | } |
| 204 | |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 205 | private ImageSaver getDefaultImageSaver(ImageProxy image, File file) { |
Xi Zhang | ec5384d | 2020-01-27 15:01:28 -0800 | [diff] [blame] | 206 | return getDefaultImageSaver(image, |
| 207 | new ImageCapture.OutputFileOptions.Builder(file).build()); |
| 208 | } |
| 209 | |
Xi Zhang | 6f28a57 | 2020-02-10 10:50:01 -0800 | [diff] [blame] | 210 | private ImageSaver getDefaultImageSaver(@NonNull ImageProxy image) { |
| 211 | ContentValues contentValues = new ContentValues(); |
leo huang | a78ac01 | 2020-05-18 19:28:44 +0800 | [diff] [blame^] | 212 | contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg"); |
Xi Zhang | ec5384d | 2020-01-27 15:01:28 -0800 | [diff] [blame] | 213 | return getDefaultImageSaver(image, |
| 214 | new ImageCapture.OutputFileOptions.Builder(mContentResolver, |
| 215 | MediaStore.Images.Media.EXTERNAL_CONTENT_URI, |
Xi Zhang | 6f28a57 | 2020-02-10 10:50:01 -0800 | [diff] [blame] | 216 | contentValues).build()); |
Xi Zhang | ec5384d | 2020-01-27 15:01:28 -0800 | [diff] [blame] | 217 | } |
| 218 | |
| 219 | private ImageSaver getDefaultImageSaver(ImageProxy image, OutputStream outputStream) { |
| 220 | return getDefaultImageSaver(image, |
| 221 | new ImageCapture.OutputFileOptions.Builder(outputStream).build()); |
| 222 | } |
| 223 | |
| 224 | private ImageSaver getDefaultImageSaver(ImageProxy image, |
| 225 | ImageCapture.OutputFileOptions outputFileOptions) { |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 226 | return new ImageSaver( |
| 227 | image, |
Xi Zhang | ec5384d | 2020-01-27 15:01:28 -0800 | [diff] [blame] | 228 | outputFileOptions, |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 229 | /*orientation=*/ 0, |
WenHung_Teng | 5cc433b | 2019-08-26 11:40:45 +0800 | [diff] [blame] | 230 | mBackgroundExecutor, |
Franklin Wu | 3b4ed3e | 2019-09-13 13:43:29 -0700 | [diff] [blame] | 231 | mSyncCallback); |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 232 | } |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 233 | |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 234 | @Test |
| 235 | public void canSaveYuvImage() throws InterruptedException, IOException { |
| 236 | File saveLocation = File.createTempFile("test", ".jpg"); |
| 237 | saveLocation.deleteOnExit(); |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 238 | |
Xi Zhang | ec5384d | 2020-01-27 15:01:28 -0800 | [diff] [blame] | 239 | getDefaultImageSaver(mMockYuvImage, saveLocation).run(); |
Trevor McGuire | 3ae3a1d | 2019-02-14 13:48:45 -0800 | [diff] [blame] | 240 | mSemaphore.acquire(); |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 241 | |
Xi Zhang | ec5384d | 2020-01-27 15:01:28 -0800 | [diff] [blame] | 242 | verify(mMockCallback).onImageSaved(any()); |
| 243 | } |
| 244 | |
| 245 | @Test |
| 246 | public void saveToUri() throws InterruptedException, FileNotFoundException { |
| 247 | // Act. |
| 248 | getDefaultImageSaver(mMockYuvImage).run(); |
| 249 | mSemaphore.acquire(); |
| 250 | |
| 251 | // Assert. |
| 252 | // Verify success callback is called. |
| 253 | ArgumentCaptor<ImageCapture.OutputFileResults> outputFileResultsArgumentCaptor = |
| 254 | ArgumentCaptor.forClass(ImageCapture.OutputFileResults.class); |
| 255 | verify(mMockCallback).onImageSaved(outputFileResultsArgumentCaptor.capture()); |
| 256 | |
| 257 | // Verify save location Uri is available. |
| 258 | Uri saveLocationUri = outputFileResultsArgumentCaptor.getValue().getSavedUri(); |
| 259 | assertThat(saveLocationUri).isNotNull(); |
| 260 | |
| 261 | // Loads image and verify width and height. |
| 262 | ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(saveLocationUri, "r"); |
| 263 | Bitmap bitmap = BitmapFactory.decodeFileDescriptor(pfd.getFileDescriptor()); |
| 264 | assertThat(bitmap.getWidth()).isEqualTo(CROP_WIDTH); |
| 265 | assertThat(bitmap.getHeight()).isEqualTo(CROP_HEIGHT); |
leo huang | 6c2ef90 | 2020-03-12 19:43:27 +0800 | [diff] [blame] | 266 | |
| 267 | // Clean up. |
| 268 | mContentResolver.delete(saveLocationUri, null, null); |
Xi Zhang | ec5384d | 2020-01-27 15:01:28 -0800 | [diff] [blame] | 269 | } |
| 270 | |
| 271 | @Test |
Xi Zhang | ef6f703 | 2020-03-31 09:06:29 -0700 | [diff] [blame] | 272 | public void saveToUriWithEmptyCollection_onErrorCalled() throws InterruptedException { |
Xi Zhang | 6f28a57 | 2020-02-10 10:50:01 -0800 | [diff] [blame] | 273 | // Arrange. |
| 274 | ContentValues contentValues = new ContentValues(); |
leo huang | a78ac01 | 2020-05-18 19:28:44 +0800 | [diff] [blame^] | 275 | contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg"); |
Xi Zhang | 6f28a57 | 2020-02-10 10:50:01 -0800 | [diff] [blame] | 276 | contentValues.put(MediaStore.MediaColumns.DATA, INVALID_DATA_PATH); |
| 277 | ImageSaver imageSaver = getDefaultImageSaver(mMockYuvImage, |
| 278 | new ImageCapture.OutputFileOptions.Builder(mContentResolver, |
Xi Zhang | ef6f703 | 2020-03-31 09:06:29 -0700 | [diff] [blame] | 279 | Uri.EMPTY, |
Xi Zhang | 6f28a57 | 2020-02-10 10:50:01 -0800 | [diff] [blame] | 280 | contentValues).build()); |
| 281 | |
| 282 | // Act. |
| 283 | imageSaver.run(); |
| 284 | mSemaphore.acquire(); |
| 285 | |
| 286 | // Assert. |
Xi Zhang | 127e6ba | 2020-02-13 14:36:25 -0800 | [diff] [blame] | 287 | verify(mMockCallback).onError(eq(SaveError.FILE_IO_FAILED), any(), any()); |
Xi Zhang | 6f28a57 | 2020-02-10 10:50:01 -0800 | [diff] [blame] | 288 | } |
| 289 | |
| 290 | @Test |
Xi Zhang | ec5384d | 2020-01-27 15:01:28 -0800 | [diff] [blame] | 291 | public void saveToOutputStream() throws InterruptedException, IOException { |
| 292 | // Arrange. |
| 293 | File file = File.createTempFile("test", ".jpg"); |
| 294 | file.deleteOnExit(); |
| 295 | |
| 296 | // Act. |
| 297 | try (OutputStream outputStream = new FileOutputStream(file)) { |
| 298 | getDefaultImageSaver(mMockYuvImage, outputStream).run(); |
| 299 | mSemaphore.acquire(); |
| 300 | } |
| 301 | |
| 302 | // Assert. |
| 303 | verify(mMockCallback).onImageSaved(any()); |
| 304 | // Loads image and verify width and height. |
| 305 | Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath()); |
| 306 | assertThat(bitmap.getWidth()).isEqualTo(CROP_WIDTH); |
| 307 | assertThat(bitmap.getHeight()).isEqualTo(CROP_HEIGHT); |
| 308 | } |
| 309 | |
| 310 | @Test |
| 311 | public void saveToClosedOutputStream_onErrorCalled() throws InterruptedException, |
| 312 | IOException { |
| 313 | // Arrange. |
| 314 | File file = File.createTempFile("test", ".jpg"); |
| 315 | file.deleteOnExit(); |
| 316 | OutputStream outputStream = new FileOutputStream(file); |
| 317 | outputStream.close(); |
| 318 | |
| 319 | // Act. |
| 320 | getDefaultImageSaver(mMockYuvImage, outputStream).run(); |
| 321 | mSemaphore.acquire(); |
| 322 | |
| 323 | // Assert. |
| 324 | verify(mMockCallback).onError(eq(SaveError.FILE_IO_FAILED), anyString(), |
| 325 | any(Throwable.class)); |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 326 | } |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 327 | |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 328 | @Test |
| 329 | public void canSaveJpegImage() throws InterruptedException, IOException { |
| 330 | File saveLocation = File.createTempFile("test", ".jpg"); |
| 331 | saveLocation.deleteOnExit(); |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 332 | |
Xi Zhang | ec5384d | 2020-01-27 15:01:28 -0800 | [diff] [blame] | 333 | getDefaultImageSaver(mMockJpegImage, saveLocation).run(); |
Trevor McGuire | 3ae3a1d | 2019-02-14 13:48:45 -0800 | [diff] [blame] | 334 | mSemaphore.acquire(); |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 335 | |
Xi Zhang | ec5384d | 2020-01-27 15:01:28 -0800 | [diff] [blame] | 336 | verify(mMockCallback).onImageSaved(any()); |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 337 | } |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 338 | |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 339 | @Test |
Trevor McGuire | 3ae3a1d | 2019-02-14 13:48:45 -0800 | [diff] [blame] | 340 | public void errorCallbackWillBeCalledOnInvalidPath() throws InterruptedException { |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 341 | // Invalid filename should cause error |
| 342 | File saveLocation = new File("/not/a/real/path.jpg"); |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 343 | |
Xi Zhang | ec5384d | 2020-01-27 15:01:28 -0800 | [diff] [blame] | 344 | getDefaultImageSaver(mMockJpegImage, saveLocation).run(); |
Trevor McGuire | 3ae3a1d | 2019-02-14 13:48:45 -0800 | [diff] [blame] | 345 | mSemaphore.acquire(); |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 346 | |
Franklin Wu | 3b4ed3e | 2019-09-13 13:43:29 -0700 | [diff] [blame] | 347 | verify(mMockCallback).onError(eq(SaveError.FILE_IO_FAILED), anyString(), |
Trevor McGuire | 6bf813c | 2019-02-26 10:26:12 -0800 | [diff] [blame] | 348 | any(Throwable.class)); |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 349 | } |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 350 | |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 351 | @Test |
| 352 | public void imageIsClosedOnSuccess() throws InterruptedException, IOException { |
| 353 | File saveLocation = File.createTempFile("test", ".jpg"); |
| 354 | saveLocation.deleteOnExit(); |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 355 | |
Xi Zhang | ec5384d | 2020-01-27 15:01:28 -0800 | [diff] [blame] | 356 | getDefaultImageSaver(mMockJpegImage, saveLocation).run(); |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 357 | |
Trevor McGuire | 3ae3a1d | 2019-02-14 13:48:45 -0800 | [diff] [blame] | 358 | mSemaphore.acquire(); |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 359 | |
Trevor McGuire | 3ae3a1d | 2019-02-14 13:48:45 -0800 | [diff] [blame] | 360 | verify(mMockJpegImage).close(); |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 361 | } |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 362 | |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 363 | @Test |
Xi Zhang | ec5384d | 2020-01-27 15:01:28 -0800 | [diff] [blame] | 364 | public void imageIsClosedOnError() throws InterruptedException { |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 365 | // Invalid filename should cause error |
| 366 | File saveLocation = new File("/not/a/real/path.jpg"); |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 367 | |
Xi Zhang | ec5384d | 2020-01-27 15:01:28 -0800 | [diff] [blame] | 368 | getDefaultImageSaver(mMockJpegImage, saveLocation).run(); |
Trevor McGuire | 3ae3a1d | 2019-02-14 13:48:45 -0800 | [diff] [blame] | 369 | mSemaphore.acquire(); |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 370 | |
Trevor McGuire | 3ae3a1d | 2019-02-14 13:48:45 -0800 | [diff] [blame] | 371 | verify(mMockJpegImage).close(); |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 372 | } |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 373 | |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 374 | private void imageCanBeCropped(ImageProxy image) throws InterruptedException, IOException { |
| 375 | File saveLocation = File.createTempFile("test", ".jpg"); |
| 376 | saveLocation.deleteOnExit(); |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 377 | |
Xi Zhang | ec5384d | 2020-01-27 15:01:28 -0800 | [diff] [blame] | 378 | getDefaultImageSaver(image, saveLocation).run(); |
Trevor McGuire | 3ae3a1d | 2019-02-14 13:48:45 -0800 | [diff] [blame] | 379 | mSemaphore.acquire(); |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 380 | |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 381 | Bitmap bitmap = BitmapFactory.decodeFile(saveLocation.getPath()); |
Xi Zhang | ec5384d | 2020-01-27 15:01:28 -0800 | [diff] [blame] | 382 | assertThat(bitmap.getWidth()).isEqualTo(CROP_WIDTH); |
| 383 | assertThat(bitmap.getHeight()).isEqualTo(CROP_HEIGHT); |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 384 | } |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 385 | |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 386 | @Test |
| 387 | public void jpegImageCanBeCropped() throws InterruptedException, IOException { |
Trevor McGuire | 3ae3a1d | 2019-02-14 13:48:45 -0800 | [diff] [blame] | 388 | imageCanBeCropped(mMockJpegImage); |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 389 | } |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 390 | |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 391 | @Test |
| 392 | public void yuvImageCanBeCropped() throws InterruptedException, IOException { |
Trevor McGuire | 3ae3a1d | 2019-02-14 13:48:45 -0800 | [diff] [blame] | 393 | imageCanBeCropped(mMockYuvImage); |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 394 | } |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 395 | } |