diff options
author | android-build-prod (mdb) <android-build-team-robot@google.com> | 2021-02-05 21:27:02 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2021-02-05 21:27:02 +0000 |
commit | 92ffb254b24e2a33c10252f6e9aba41f71b3cc1d (patch) | |
tree | 44e63e52661f5c98d8f484b54e51c380bde5f71f | |
parent | 4fc196a66681f1a48ccd8f2c40186f0e155e1f3a (diff) | |
parent | e0f2005549ca5bbf6408d3793c84350691103936 (diff) | |
download | support-snap-temp-L16000000807332640.tar.gz |
Merge "Remove the validation of image saved location to improve the performance of taking picture" into snap-temp-L16000000807332640snap-temp-L16000000807332640
5 files changed, 5 insertions, 436 deletions
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java index a6c3b876d5f..442ea68b1b6 100644 --- a/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java +++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java @@ -767,22 +767,6 @@ public final class ImageCapture extends UseCase { * set, or {@link #setCropAspectRatio} is used, the image may be cropped before saving to * disk which causes an additional latency. * - * <p> Before triggering the image capture pipeline, if the save location is a {@link File} or - * {@link MediaStore}, it is first verified to ensure it's valid and writable. A {@link File} - * is verified by attempting to open a {@link java.io.FileOutputStream} to it, whereas a - * location in {@link MediaStore} is validated by - * {@linkplain ContentResolver#insert(Uri, ContentValues) creating a new row} in the user - * defined table, retrieving a {@link Uri} pointing to it, then attempting to open an - * {@link OutputStream} to it. The newly created row is - * {@linkplain ContentResolver#delete(Uri, String, String[]) deleted} - * at the end of the verification. On Huawei devices, this deletion results in the system - * displaying a notification informing the user that a photo has been deleted. In order to - * avoid this, validating the image capture save location in - * {@link android.provider.MediaStore} is skipped on Huawei devices. - * - * <p> If the validation of the save location fails, {@link OnImageSavedCallback}'s error - * callback is invoked with an {@link ImageCaptureException}. - * * @param outputFileOptions Options to store the newly captured image. * @param executor The executor in which the callback methods will be run. * @param imageSavedCallback Callback to be called for the newly captured image. @@ -792,30 +776,11 @@ public final class ImageCapture extends UseCase { final @NonNull OutputFileOptions outputFileOptions, final @NonNull Executor executor, final @NonNull OnImageSavedCallback imageSavedCallback) { - mSequentialIoExecutor.execute(() -> { - if (!ImageSaveLocationValidator.isValid(outputFileOptions)) { - // Check whether the captured image can be saved. If it cannot, fail fast and - // notify user. - executor.execute(() -> imageSavedCallback.onError( - new ImageCaptureException(ERROR_FILE_IO, - "Cannot save capture result to specified location", null))); - } else { - CameraXExecutors.mainThreadExecutor().execute( - () -> takePictureAfterValidation(outputFileOptions, executor, - imageSavedCallback)); - } - }); - } - - /** - * Takes picture after the output file options is validated. - */ - @UiThread - private void takePictureAfterValidation( - final @NonNull OutputFileOptions outputFileOptions, - final @NonNull Executor executor, - final @NonNull OnImageSavedCallback imageSavedCallback) { - Threads.checkMainThread(); + if (Looper.getMainLooper() != Looper.myLooper()) { + CameraXExecutors.mainThreadExecutor().execute( + () -> takePicture(outputFileOptions, executor, imageSavedCallback)); + return; + } /* * We need to chain the following callbacks to save the image to disk: * diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageSaveLocationValidator.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageSaveLocationValidator.java deleted file mode 100644 index d99aea708f2..00000000000 --- a/camera/camera-core/src/main/java/androidx/camera/core/ImageSaveLocationValidator.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright 2020 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 androidx.camera.core; - -import android.content.ContentResolver; -import android.content.ContentValues; -import android.net.Uri; - -import androidx.annotation.NonNull; -import androidx.camera.core.internal.compat.quirk.DeviceQuirks; -import androidx.camera.core.internal.compat.quirk.HuaweiMediaStoreLocationValidationQuirk; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; - -class ImageSaveLocationValidator { - - private static final String TAG = "SaveLocationValidator"; - - private ImageSaveLocationValidator() { - } - - /** - * Verifies whether the result of an image capture operation can be saved to the specified - * location in {@link androidx.camera.core.ImageCapture.OutputFileOptions}. - * <p> - * This method checks whether an image capture to a {@link File} or to - * {@link android.provider.MediaStore} will work by checking whether the {@link File} exists - * and is writable, and if the {@link Uri} to {@link android.provider.MediaStore} is valid. - * - * @param outputFileOptions Options for saving the result of an image capture operation - * @return true if the image capture result can be saved to the specified storage option, - * false otherwise. - */ - @SuppressWarnings("ConstantConditions") - static boolean isValid(final @NonNull ImageCapture.OutputFileOptions outputFileOptions) { - if (isSaveToFile(outputFileOptions)) { - return canSaveToFile(outputFileOptions.getFile()); - } - - if (isSaveToMediaStore(outputFileOptions)) { - // Skip verification on Huawei devices - final HuaweiMediaStoreLocationValidationQuirk huaweiQuirk = - DeviceQuirks.get(HuaweiMediaStoreLocationValidationQuirk.class); - if (huaweiQuirk != null) { - return huaweiQuirk.canSaveToMediaStore(); - } - - return canSaveToMediaStore(outputFileOptions.getContentResolver(), - outputFileOptions.getSaveCollection(), outputFileOptions.getContentValues()); - } - return true; - } - - private static boolean isSaveToFile( - final @NonNull ImageCapture.OutputFileOptions outputFileOptions) { - return outputFileOptions.getFile() != null; - } - - private static boolean isSaveToMediaStore( - final @NonNull ImageCapture.OutputFileOptions outputFileOptions) { - return outputFileOptions.getSaveCollection() != null - && outputFileOptions.getContentResolver() != null - && outputFileOptions.getContentValues() != null; - } - - private static boolean canSaveToFile(@NonNull final File file) { - // Try opening a write stream to the output file. If this succeeds, the image save - // destination is valid. Otherwise, it's invalid. - try (FileOutputStream ignored = new FileOutputStream(file)) { - return true; - } catch (IOException exception) { - Logger.e(TAG, "Failed to open a write stream to " + file.toString(), exception); - return false; - } - } - - private static boolean canSaveToMediaStore(@NonNull final ContentResolver contentResolver, - @NonNull final Uri uri, @NonNull ContentValues values) { - final Uri outputUri; - try { - // Get the uri where the image will be saved - outputUri = contentResolver.insert(uri, values); - } catch (IllegalArgumentException exception) { - Logger.e(TAG, "Failed to insert into " + uri.toString(), exception); - return false; - } - - // If the uri is null, saving the capture result to the given uri isn't possible - if (outputUri == null) { - return false; - } - - // Try opening a write stream to the output uri. If this succeeds, the image save - // destination is valid. Otherwise, it's invalid. - try (OutputStream stream = contentResolver.openOutputStream(outputUri)) { - return stream != null; - } catch (IOException exception) { - Logger.e(TAG, "Failed to open a write stream to" + outputUri.toString(), exception); - return false; - } finally { - try { - // Delete inserted row - contentResolver.delete(outputUri, null, null); - } catch (IllegalArgumentException exception) { - Logger.e(TAG, "Failed to delete inserted row at " + outputUri.toString(), - exception); - } - } - } -} diff --git a/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/quirk/DeviceQuirksLoader.java b/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/quirk/DeviceQuirksLoader.java index 54b9270c710..0a15ab7d60b 100644 --- a/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/quirk/DeviceQuirksLoader.java +++ b/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/quirk/DeviceQuirksLoader.java @@ -39,10 +39,6 @@ public class DeviceQuirksLoader { final List<Quirk> quirks = new ArrayList<>(); // Load all device specific quirks - if (HuaweiMediaStoreLocationValidationQuirk.load()) { - quirks.add(new HuaweiMediaStoreLocationValidationQuirk()); - } - if (IncompleteCameraListQuirk.load()) { quirks.add(new IncompleteCameraListQuirk()); } diff --git a/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/quirk/HuaweiMediaStoreLocationValidationQuirk.java b/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/quirk/HuaweiMediaStoreLocationValidationQuirk.java deleted file mode 100644 index 1a5d3acd51c..00000000000 --- a/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/quirk/HuaweiMediaStoreLocationValidationQuirk.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2020 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 androidx.camera.core.internal.compat.quirk; - -import android.os.Build; - -import androidx.camera.core.impl.Quirk; - -/** - * Quirk that displays a notification when an image in {@link android.provider.MediaStore} has - * been deleted. - * - * <p> When triggering an image capture, the {@link androidx.camera.core.ImageCapture} - * use case validates the save location before starting the image capture pipeline. Validating a - * save location that is a {@link android.net.Uri} in {@link android.provider.MediaStore} involves - * creating a new row in the user defined table, retrieving an output {@link android.net.Uri} - * pointing to it, then attempting to open an {@link java.io.OutputStream} to it. The newly - * created row is deleted at the end of the verification. On Huawei devices, this last step - * results in the system displaying a notification informing the user that a photo has been - * deleted. In order to avoid this, validating the image capture save location to - * {@link android.provider.MediaStore} is skipped on Huawei devices. See b/169497925. - */ -public class HuaweiMediaStoreLocationValidationQuirk implements Quirk { - - static boolean load() { - return "HUAWEI".equals(Build.BRAND.toUpperCase()) - || "HONOR".equals(Build.BRAND.toUpperCase()); - } - - /** - * Always skip checking if the image capture save destination in - * {@link android.provider.MediaStore} is valid. - */ - public boolean canSaveToMediaStore() { - return true; - } -} diff --git a/camera/camera-core/src/test/java/androidx/camera/core/ImageSaveLocationValidatorTest.java b/camera/camera-core/src/test/java/androidx/camera/core/ImageSaveLocationValidatorTest.java deleted file mode 100644 index d2d095be40d..00000000000 --- a/camera/camera-core/src/test/java/androidx/camera/core/ImageSaveLocationValidatorTest.java +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright 2020 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 androidx.camera.core; - -import static com.google.common.truth.Truth.assertThat; - -import static org.junit.Assume.assumeFalse; - -import android.content.ContentProvider; -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; -import android.os.Build; -import android.provider.MediaStore; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.test.core.app.ApplicationProvider; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.Robolectric; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; -import org.robolectric.annotation.internal.DoNotInstrument; -import org.robolectric.util.ReflectionHelpers; - -import java.io.File; -import java.io.IOException; -import java.util.function.Function; - -@RunWith(RobolectricTestRunner.class) -@DoNotInstrument -@Config(minSdk = Build.VERSION_CODES.LOLLIPOP) -public class ImageSaveLocationValidatorTest { - - private static final Uri ANY_URI = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; - - @Test - public void canSaveToValidWritableFile() throws IOException { - final File saveLocation = File.createTempFile("test", ".jpg"); - saveLocation.deleteOnExit(); - final ImageCapture.OutputFileOptions outputOptions = - new ImageCapture.OutputFileOptions.Builder(saveLocation).build(); - - assertThat(ImageSaveLocationValidator.isValid(outputOptions)).isTrue(); - } - - @Test - public void canSaveToFileInCache() { - final File cacheDir = ApplicationProvider.getApplicationContext().getCacheDir(); - final String fileName = System.currentTimeMillis() + ".jpg"; - final File saveLocation = new File(cacheDir, fileName); - saveLocation.deleteOnExit(); - final ImageCapture.OutputFileOptions outputOptions = - new ImageCapture.OutputFileOptions.Builder(saveLocation).build(); - - assertThat(ImageSaveLocationValidator.isValid(outputOptions)).isTrue(); - } - - @Test - public void cannotSaveToReadOnlyFile() throws IOException { - final File saveLocation = File.createTempFile("test", ".jpg"); - saveLocation.setReadOnly(); - saveLocation.deleteOnExit(); - final ImageCapture.OutputFileOptions outputOptions = - new ImageCapture.OutputFileOptions.Builder(saveLocation).build(); - - assertThat(ImageSaveLocationValidator.isValid(outputOptions)).isFalse(); - } - - @Test - public void canSaveToMediaStore() { - // Skip for Huawei devices - assumeFalse(isHuaweiDevice()); - - // Return a valid insertion Uri for the image - TestContentProvider.sOnInsertCallback = uri -> Uri.parse(uri.toString() + "/1"); - Robolectric.buildContentProvider(TestContentProvider.class).create(ANY_URI.getAuthority()); - - final ImageCapture.OutputFileOptions outputOptions = buildOutputFileOptions(); - assertThat(ImageSaveLocationValidator.isValid(outputOptions)).isTrue(); - } - - @Test - public void cannotSaveToMediaStore_providerFailsInsertion() { - // Skip for Huawei devices - assumeFalse(isHuaweiDevice()); - - // Make the provider fail to return a valid insertion Uri - TestContentProvider.sOnInsertCallback = uri -> null; - Robolectric.buildContentProvider(TestContentProvider.class).create(ANY_URI.getAuthority()); - - final ImageCapture.OutputFileOptions outputOptions = buildOutputFileOptions(); - assertThat(ImageSaveLocationValidator.isValid(outputOptions)).isFalse(); - } - - @Test - public void cannotSaveToMediaStore_providerCrashesOnInsertion() { - // Skip for Huawei devices - assumeFalse(isHuaweiDevice()); - - // Make the provider crash when trying to return a valid insertion Uri - TestContentProvider.sOnInsertCallback = uri -> { - throw new IllegalArgumentException(); - }; - Robolectric.buildContentProvider(TestContentProvider.class).create(ANY_URI.getAuthority()); - - final ImageCapture.OutputFileOptions outputOptions = buildOutputFileOptions(); - assertThat(ImageSaveLocationValidator.isValid(outputOptions)).isFalse(); - } - - @Test - public void canSaveToMediaStore_huaweiDevice() { - ReflectionHelpers.setStaticField(Build.class, "BRAND", "huawei"); - - // Validation should return `true` regardless of any errors that may result from storing - // the image capture result in MediaStore, like the one below. - TestContentProvider.sOnInsertCallback = uri -> null; - Robolectric.buildContentProvider(TestContentProvider.class).create(ANY_URI.getAuthority()); - - final ImageCapture.OutputFileOptions outputOptions = buildOutputFileOptions(); - assertThat(ImageSaveLocationValidator.isValid(outputOptions)).isTrue(); - } - - @Test - public void canSaveToMediaStore_honorDevice() { - ReflectionHelpers.setStaticField(Build.class, "BRAND", "honor"); - - // Validation should return `true` regardless of any errors that may result from storing - // the image capture result in MediaStore, like the one below. - TestContentProvider.sOnInsertCallback = uri -> null; - Robolectric.buildContentProvider(TestContentProvider.class).create(ANY_URI.getAuthority()); - - final ImageCapture.OutputFileOptions outputOptions = buildOutputFileOptions(); - assertThat(ImageSaveLocationValidator.isValid(outputOptions)).isTrue(); - } - - @NonNull - private ImageCapture.OutputFileOptions buildOutputFileOptions() { - final Context context = ApplicationProvider.getApplicationContext(); - final ContentResolver resolver = context.getContentResolver(); - final ContentValues contentValues = new ContentValues(); - contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, "test"); - contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg"); - return new ImageCapture.OutputFileOptions - .Builder(resolver, ANY_URI, contentValues) - .build(); - } - - private boolean isHuaweiDevice() { - return "HUAWEI".equals(Build.BRAND.toUpperCase()) - || "HONOR".equals(Build.BRAND.toUpperCase()); - } - - static class TestContentProvider extends ContentProvider { - - @NonNull - static Function<Uri, Uri> sOnInsertCallback = uri -> null; - - @Override - public boolean onCreate() { - return false; - } - - @Nullable - @Override - public Cursor query(@NonNull Uri uri, @Nullable String[] projection, - @Nullable String selection, @Nullable String[] selectionArgs, - @Nullable String sortOrder) { - return null; - } - - @Nullable - @Override - public String getType(@NonNull Uri uri) { - return null; - } - - @Nullable - @Override - public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) { - return sOnInsertCallback.apply(uri); - } - - @Override - public int delete(@NonNull Uri uri, @Nullable String selection, - @Nullable String[] selectionArgs) { - return 0; - } - - @Override - public int update(@NonNull Uri uri, @Nullable ContentValues values, - @Nullable String selection, @Nullable String[] selectionArgs) { - return 0; - } - } -} |