• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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 android.photopicker.cts.util;
18 
19 import static android.provider.MediaStore.PickerMediaColumns;
20 
21 import static com.google.common.truth.Truth.assertThat;
22 import static com.google.common.truth.Truth.assertWithMessage;
23 
24 import static org.junit.Assert.fail;
25 
26 import android.content.ContentResolver;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.UriPermission;
30 import android.database.Cursor;
31 import android.media.ExifInterface;
32 import android.net.Uri;
33 import android.os.FileUtils;
34 import android.os.ParcelFileDescriptor;
35 
36 import androidx.annotation.NonNull;
37 import androidx.test.InstrumentationRegistry;
38 
39 import java.io.ByteArrayOutputStream;
40 import java.io.File;
41 import java.io.FileInputStream;
42 import java.io.FileNotFoundException;
43 import java.io.IOException;
44 import java.io.InputStream;
45 import java.util.ArrayList;
46 import java.util.Arrays;
47 import java.util.List;
48 import java.util.Map;
49 
50 /**
51  * Photo Picker Utility methods for PhotoPicker result assertions.
52  */
53 public class ResultsAssertionsUtils {
54     private static final String TAG = "PhotoPickerTestAssertions";
55 
assertPickerUriFormat(Uri uri, int expectedUserId)56     public static void assertPickerUriFormat(Uri uri, int expectedUserId) {
57         // content://media/picker/<user-id>/<media-id>
58         final int userId = Integer.parseInt(uri.getPathSegments().get(1));
59         assertThat(userId).isEqualTo(expectedUserId);
60 
61         final String auth = uri.getPathSegments().get(0);
62         assertThat(auth).isEqualTo("picker");
63     }
64 
assertPersistedGrant(Uri uri, ContentResolver resolver)65     public static void assertPersistedGrant(Uri uri, ContentResolver resolver) {
66         resolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
67 
68         final List<UriPermission> uriPermissions = resolver.getPersistedUriPermissions();
69         final List<Uri> uris = new ArrayList<>();
70         for (UriPermission perm : uriPermissions) {
71             if (perm.isReadPermission()) {
72                 uris.add(perm.getUri());
73             }
74         }
75 
76         assertThat(uris).contains(uri);
77     }
78 
assertMimeType(Uri uri, String expectedMimeType)79     public static void assertMimeType(Uri uri, String expectedMimeType) throws Exception {
80         final Context context = InstrumentationRegistry.getTargetContext();
81         final String resultMimeType = context.getContentResolver().getType(uri);
82         assertThat(resultMimeType).isEqualTo(expectedMimeType);
83     }
84 
assertContainsMimeType(Uri uri, String[] expectedMimeTypes)85     public static void assertContainsMimeType(Uri uri, String[] expectedMimeTypes) {
86         final Context context = InstrumentationRegistry.getTargetContext();
87         final String resultMimeType = context.getContentResolver().getType(uri);
88         assertThat(Arrays.asList(expectedMimeTypes).contains(resultMimeType)).isTrue();
89     }
90 
assertRedactedReadOnlyAccess(Uri uri)91     public static void assertRedactedReadOnlyAccess(Uri uri) throws Exception {
92         assertThat(uri).isNotNull();
93         final String[] projection = new String[]{ PickerMediaColumns.MIME_TYPE };
94         final Context context = InstrumentationRegistry.getTargetContext();
95         final ContentResolver resolver = context.getContentResolver();
96         try (Cursor c = resolver.query(uri, projection, null, null)) {
97             assertThat(c).isNotNull();
98             assertThat(c.moveToFirst()).isTrue();
99 
100             final String mimeType = c.getString(c.getColumnIndex(PickerMediaColumns.MIME_TYPE));
101 
102             if (mimeType.startsWith("image")) {
103                 assertImageRedactedReadOnlyAccess(uri, resolver);
104             } else if (mimeType.startsWith("video")) {
105                 assertVideoRedactedReadOnlyAccess(uri, resolver);
106             } else {
107                 fail("The mime type is not as expected: " + mimeType);
108             }
109         }
110     }
111 
assertExtension(@onNull Uri uri, @NonNull Map<String, String> mimeTypeToExpectedExtensionMap)112     public static void assertExtension(@NonNull Uri uri,
113             @NonNull Map<String, String> mimeTypeToExpectedExtensionMap) {
114         assertThat(uri).isNotNull();
115 
116         final ContentResolver resolver =
117                 InstrumentationRegistry.getTargetContext().getContentResolver();
118         final String[] projection =
119                 new String[]{ PickerMediaColumns.MIME_TYPE, PickerMediaColumns.DISPLAY_NAME };
120 
121         try (Cursor c = resolver.query(
122                 uri, projection, /* queryArgs */ null, /* cancellationSignal */ null)) {
123             assertThat(c).isNotNull();
124             assertThat(c.moveToFirst()).isTrue();
125 
126             final String mimeType = c.getString(c.getColumnIndex(PickerMediaColumns.MIME_TYPE));
127             final String expectedExtension = mimeTypeToExpectedExtensionMap.get(mimeType);
128 
129             final String displayName =
130                     c.getString(c.getColumnIndex(PickerMediaColumns.DISPLAY_NAME));
131             final String[] displayNameParts = displayName.split("\\.");
132             final String resultExtension = displayNameParts[displayNameParts.length - 1];
133 
134             assertWithMessage("Unexpected picker file extension")
135                     .that(resultExtension)
136                     .isEqualTo(expectedExtension);
137         }
138     }
139 
assertVideoRedactedReadOnlyAccess(Uri uri, ContentResolver resolver)140     private static void assertVideoRedactedReadOnlyAccess(Uri uri, ContentResolver resolver)
141             throws Exception {
142         // The location is redacted
143         // TODO(b/201505595): Make this method work for test_video.mp4. Currently it works only for
144         //  test_video_mj2.mp4
145         try (InputStream in = resolver.openInputStream(uri);
146                 ByteArrayOutputStream out = new ByteArrayOutputStream()) {
147             FileUtils.copy(in, out);
148             byte[] bytes = out.toByteArray();
149             byte[] xmpBytes = Arrays.copyOfRange(bytes, 3269, 3269 + 13197);
150             String xmp = new String(xmpBytes);
151             assertWithMessage("Failed to redact XMP longitude")
152                     .that(xmp.contains("10,41.751000E")).isFalse();
153             assertWithMessage("Failed to redact XMP latitude")
154                     .that(xmp.contains("53,50.070500N")).isFalse();
155             assertWithMessage("Redacted non-location XMP")
156                     .that(xmp.contains("13166/7763")).isTrue();
157         }
158 
159         assertNoWriteAccess(uri, resolver);
160     }
161 
assertImageRedactedReadOnlyAccess(Uri uri, ContentResolver resolver)162     private static void assertImageRedactedReadOnlyAccess(Uri uri, ContentResolver resolver)
163             throws Exception {
164         // Assert URI access
165         // The location is redacted
166         try (InputStream is = resolver.openInputStream(uri)) {
167             assertImageExifRedacted(is);
168         }
169 
170         // Assert no write access
171         try (ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "w")) {
172             fail("Does not grant write access to uri " + uri.toString());
173         } catch (SecurityException | FileNotFoundException expected) {
174         }
175 
176         // Assert file path access
177         try (Cursor c = resolver.query(uri, null, null, null)) {
178             assertThat(c).isNotNull();
179             assertThat(c.moveToFirst()).isTrue();
180 
181             File file = new File(c.getString(c.getColumnIndex(PickerMediaColumns.DATA)));
182 
183             // The location is redacted
184             try (InputStream is = new FileInputStream(file)) {
185                 assertImageExifRedacted(is);
186             }
187 
188             assertNoWriteAccess(uri, resolver);
189         }
190     }
191 
assertImageExifRedacted(InputStream is)192     private static void assertImageExifRedacted(InputStream is) throws IOException {
193         final ExifInterface exif = new ExifInterface(is);
194         final float[] latLong = new float[2];
195         exif.getLatLong(latLong);
196         assertWithMessage("Failed to redact latitude")
197                 .that(latLong[0]).isWithin(0.001f).of(0);
198         assertWithMessage("Failed to redact longitude")
199                 .that(latLong[1]).isWithin(0.001f).of(0);
200 
201         String xmp = exif.getAttribute(ExifInterface.TAG_XMP);
202         assertWithMessage("Failed to redact XMP longitude")
203                 .that(xmp.contains("10,41.751000E")).isFalse();
204         assertWithMessage("Failed to redact XMP latitude")
205                 .that(xmp.contains("53,50.070500N")).isFalse();
206         assertWithMessage("Redacted non-location XMP")
207                 .that(xmp.contains("LensDefaults")).isTrue();
208     }
209 
assertReadOnlyAccess(Uri uri, ContentResolver resolver)210     public static void assertReadOnlyAccess(Uri uri, ContentResolver resolver) throws Exception {
211         try (ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "r")) {
212             assertThat(pfd).isNotNull();
213         }
214 
215         assertNoWriteAccess(uri, resolver);
216     }
217 
assertNoWriteAccess(Uri uri, ContentResolver resolver)218     private static void assertNoWriteAccess(Uri uri, ContentResolver resolver) throws Exception {
219         try (ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "w")) {
220             fail("Does not grant write access to uri " + uri.toString());
221         } catch (SecurityException | FileNotFoundException expected) {
222         }
223     }
224 }
225