blob: d9749f27420029fbe8f5985777967bc479b8eb9c [file] [log] [blame]
/*
* Copyright (C) 2021 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 android.photopicker.cts.util;
import static android.provider.MediaStore.PickerMediaColumns;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.fail;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.UriPermission;
import android.database.Cursor;
import android.media.ExifInterface;
import android.net.Uri;
import android.os.FileUtils;
import android.os.ParcelFileDescriptor;
import androidx.test.InstrumentationRegistry;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Photo Picker Utility methods for test assertions.
*/
public class PhotoPickerAssertionsUtils {
private static final String TAG = "PhotoPickerTestAssertions";
public static void assertPickerUriFormat(Uri uri, int expectedUserId) {
// content://media/picker/<user-id>/<media-id>
final int userId = Integer.parseInt(uri.getPathSegments().get(1));
assertThat(userId).isEqualTo(expectedUserId);
final String auth = uri.getPathSegments().get(0);
assertThat(auth).isEqualTo("picker");
}
public static void assertPersistedGrant(Uri uri, ContentResolver resolver) {
resolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
final List<UriPermission> uriPermissions = resolver.getPersistedUriPermissions();
final List<Uri> uris = new ArrayList<>();
for (UriPermission perm : uriPermissions) {
if (perm.isReadPermission()) {
uris.add(perm.getUri());
}
}
assertThat(uris).contains(uri);
}
public static void assertMimeType(Uri uri, String expectedMimeType) throws Exception {
final Context context = InstrumentationRegistry.getTargetContext();
final String resultMimeType = context.getContentResolver().getType(uri);
assertThat(resultMimeType).isEqualTo(expectedMimeType);
}
public static void assertRedactedReadOnlyAccess(Uri uri) throws Exception {
assertThat(uri).isNotNull();
final String[] projection = new String[]{ PickerMediaColumns.MIME_TYPE };
final Context context = InstrumentationRegistry.getTargetContext();
final ContentResolver resolver = context.getContentResolver();
try (Cursor c = resolver.query(uri, projection, null, null)) {
assertThat(c).isNotNull();
assertThat(c.moveToFirst()).isTrue();
final String mimeType = c.getString(c.getColumnIndex(PickerMediaColumns.MIME_TYPE));
if (mimeType.startsWith("image")) {
assertImageRedactedReadOnlyAccess(uri, resolver);
} else if (mimeType.startsWith("video")) {
assertVideoRedactedReadOnlyAccess(uri, resolver);
} else {
fail("The mime type is not as expected: " + mimeType);
}
}
}
private static void assertVideoRedactedReadOnlyAccess(Uri uri, ContentResolver resolver)
throws Exception {
// The location is redacted
// TODO(b/201505595): Make this method work for test_video.mp4. Currently it works only for
// test_video_dng.mp4
try (InputStream in = resolver.openInputStream(uri);
ByteArrayOutputStream out = new ByteArrayOutputStream()) {
FileUtils.copy(in, out);
byte[] bytes = out.toByteArray();
byte[] xmpBytes = Arrays.copyOfRange(bytes, 3269, 3269 + 13197);
String xmp = new String(xmpBytes);
assertWithMessage("Failed to redact XMP longitude")
.that(xmp.contains("10,41.751000E")).isFalse();
assertWithMessage("Failed to redact XMP latitude")
.that(xmp.contains("53,50.070500N")).isFalse();
assertWithMessage("Redacted non-location XMP")
.that(xmp.contains("13166/7763")).isTrue();
}
assertNoWriteAccess(uri, resolver);
}
private static void assertImageRedactedReadOnlyAccess(Uri uri, ContentResolver resolver)
throws Exception {
// Assert URI access
// The location is redacted
try (InputStream is = resolver.openInputStream(uri)) {
assertImageExifRedacted(is);
}
// Assert no write access
try (ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "w")) {
fail("Does not grant write access to uri " + uri.toString());
} catch (SecurityException | FileNotFoundException expected) {
}
// Assert file path access
try (Cursor c = resolver.query(uri, null, null, null)) {
assertThat(c).isNotNull();
assertThat(c.moveToFirst()).isTrue();
File file = new File(c.getString(c.getColumnIndex(PickerMediaColumns.DATA)));
// The location is redacted
try (InputStream is = new FileInputStream(file)) {
assertImageExifRedacted(is);
}
assertNoWriteAccess(uri, resolver);
}
}
private static void assertImageExifRedacted(InputStream is) throws IOException {
final ExifInterface exif = new ExifInterface(is);
final float[] latLong = new float[2];
exif.getLatLong(latLong);
assertWithMessage("Failed to redact latitude")
.that(latLong[0]).isWithin(0.001f).of(0);
assertWithMessage("Failed to redact longitude")
.that(latLong[1]).isWithin(0.001f).of(0);
String xmp = exif.getAttribute(ExifInterface.TAG_XMP);
assertWithMessage("Failed to redact XMP longitude")
.that(xmp.contains("10,41.751000E")).isFalse();
assertWithMessage("Failed to redact XMP latitude")
.that(xmp.contains("53,50.070500N")).isFalse();
assertWithMessage("Redacted non-location XMP")
.that(xmp.contains("LensDefaults")).isTrue();
}
public static void assertReadOnlyAccess(Uri uri, ContentResolver resolver) throws Exception {
try (ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "r")) {
assertThat(pfd).isNotNull();
}
assertNoWriteAccess(uri, resolver);
}
private static void assertNoWriteAccess(Uri uri, ContentResolver resolver) throws Exception {
try (ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "w")) {
fail("Does not grant write access to uri " + uri.toString());
} catch (SecurityException | FileNotFoundException expected) {
}
}
}