• 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 com.android.providers.media.photopicker.viewmodel;
18 
19 import static android.provider.CloudMediaProviderContract.AlbumColumns;
20 import static android.provider.CloudMediaProviderContract.MediaColumns;
21 
22 import static com.google.common.truth.Truth.assertThat;
23 
24 import static org.junit.Assert.fail;
25 import static org.mockito.Mockito.mock;
26 import static org.mockito.Mockito.when;
27 
28 import android.app.Application;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.database.Cursor;
32 import android.database.MatrixCursor;
33 import android.provider.MediaStore;
34 import android.text.format.DateUtils;
35 
36 import androidx.annotation.NonNull;
37 import androidx.annotation.Nullable;
38 import androidx.test.InstrumentationRegistry;
39 import androidx.test.runner.AndroidJUnit4;
40 
41 import com.android.providers.media.ConfigStore;
42 import com.android.providers.media.TestConfigStore;
43 import com.android.providers.media.photopicker.PickerSyncController;
44 import com.android.providers.media.photopicker.data.ItemsProvider;
45 import com.android.providers.media.photopicker.data.UserIdManager;
46 import com.android.providers.media.photopicker.data.model.Category;
47 import com.android.providers.media.photopicker.data.model.Item;
48 import com.android.providers.media.photopicker.data.model.ModelTestUtils;
49 import com.android.providers.media.photopicker.data.model.UserId;
50 import com.android.providers.media.util.ForegroundThread;
51 
52 import org.junit.Before;
53 import org.junit.Rule;
54 import org.junit.Test;
55 import org.junit.runner.RunWith;
56 import org.mockito.Mock;
57 import org.mockito.MockitoAnnotations;
58 
59 import java.util.ArrayList;
60 import java.util.List;
61 
62 @RunWith(AndroidJUnit4.class)
63 public class PickerViewModelTest {
64     private static final String FAKE_CATEGORY_NAME = "testCategoryName";
65     private static final String FAKE_ID = "5";
66 
67     @Rule
68     public InstantTaskExecutorRule instantTaskExecutorRule = new InstantTaskExecutorRule();
69 
70     @Mock
71     private Application mApplication;
72 
73     private PickerViewModel mPickerViewModel;
74     private TestItemsProvider mItemsProvider;
75     private TestConfigStore mConfigStore;
76 
77     @Before
setUp()78     public void setUp() {
79         MockitoAnnotations.initMocks(this);
80 
81         final Context context = InstrumentationRegistry.getTargetContext();
82         when(mApplication.getApplicationContext()).thenReturn(context);
83         mConfigStore = new TestConfigStore();
84         mConfigStore.enableCloudMediaFeature();
85         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
86             mPickerViewModel = new PickerViewModel(mApplication) {
87                 @Override
88                 protected ConfigStore getConfigStore() {
89                     return mConfigStore;
90                 }
91             };
92         });
93         mItemsProvider = new TestItemsProvider(context);
94         mPickerViewModel.setItemsProvider(mItemsProvider);
95         UserIdManager userIdManager = mock(UserIdManager.class);
96         when(userIdManager.getCurrentUserProfileId()).thenReturn(UserId.CURRENT_USER);
97         mPickerViewModel.setUserIdManager(userIdManager);
98     }
99 
100     @Test
testGetItems_noItems()101     public void testGetItems_noItems() {
102         final int itemCount = 0;
103         mItemsProvider.setItems(generateFakeImageItemList(itemCount));
104         mPickerViewModel.updateItems();
105         // We use ForegroundThread to execute the loadItems in updateItems(), wait for the thread
106         // idle
107         ForegroundThread.waitForIdle();
108 
109         final List<Item> itemList = mPickerViewModel.getItems().getValue();
110 
111         // No date headers, the size should be 0
112         assertThat(itemList.size()).isEqualTo(itemCount);
113     }
114 
115     @Test
testGetCategories()116     public void testGetCategories() throws Exception {
117         final Context context = InstrumentationRegistry.getTargetContext();
118         final int categoryCount = 2;
119         try (final Cursor fakeCursor = generateCursorForFakeCategories(categoryCount)) {
120             fakeCursor.moveToFirst();
121             final Category fakeFirstCategory = Category.fromCursor(fakeCursor, UserId.CURRENT_USER);
122             fakeCursor.moveToNext();
123             final Category fakeSecondCategory = Category.fromCursor(fakeCursor,
124                     UserId.CURRENT_USER);
125             mItemsProvider.setCategoriesCursor(fakeCursor);
126             // move the cursor to original position
127             fakeCursor.moveToPosition(-1);
128             mPickerViewModel.updateCategories();
129             // We use ForegroundThread to execute the loadCategories in updateCategories(), wait for
130             // the thread idle
131             ForegroundThread.waitForIdle();
132 
133             final List<Category> categoryList = mPickerViewModel.getCategories().getValue();
134 
135             assertThat(categoryList.size()).isEqualTo(categoryCount);
136             // Verify the first category
137             final Category firstCategory = categoryList.get(0);
138             assertThat(firstCategory.getDisplayName(context)).isEqualTo(
139                     fakeFirstCategory.getDisplayName(context));
140             assertThat(firstCategory.getItemCount()).isEqualTo(fakeFirstCategory.getItemCount());
141             assertThat(firstCategory.getCoverUri()).isEqualTo(fakeFirstCategory.getCoverUri());
142             // Verify the second category
143             final Category secondCategory = categoryList.get(1);
144             assertThat(secondCategory.getDisplayName(context)).isEqualTo(
145                     fakeSecondCategory.getDisplayName(context));
146             assertThat(secondCategory.getItemCount()).isEqualTo(fakeSecondCategory.getItemCount());
147             assertThat(secondCategory.getCoverUri()).isEqualTo(fakeSecondCategory.getCoverUri());
148         }
149     }
150 
generateFakeImageItem(String id)151     private static Item generateFakeImageItem(String id) {
152         final long dateTakenMs = System.currentTimeMillis()
153                 + Long.parseLong(id) * DateUtils.DAY_IN_MILLIS;
154         return ModelTestUtils.generateJpegItem(id, dateTakenMs, /* generationModified */ 1L);
155     }
156 
generateFakeImageItemList(int num)157     private static List<Item> generateFakeImageItemList(int num) {
158         final List<Item> itemList = new ArrayList<>();
159         for (int i = 0; i < num; i++) {
160             itemList.add(generateFakeImageItem(String.valueOf(i)));
161         }
162         return itemList;
163     }
164 
generateCursorForFakeCategories(int num)165     private static Cursor generateCursorForFakeCategories(int num) {
166         final MatrixCursor cursor = new MatrixCursor(AlbumColumns.ALL_PROJECTION);
167         final int itemCount = 5;
168         for (int i = 0; i < num; i++) {
169             cursor.addRow(new Object[]{
170                     FAKE_ID + String.valueOf(i),
171                     System.currentTimeMillis(),
172                     FAKE_CATEGORY_NAME + i,
173                     FAKE_ID + String.valueOf(i),
174                     itemCount + i,
175                     PickerSyncController.LOCAL_PICKER_PROVIDER_AUTHORITY
176                     });
177         }
178         return cursor;
179     }
180 
181     private static class TestItemsProvider extends ItemsProvider {
182 
183         private List<Item> mItemList = new ArrayList<>();
184         private Cursor mCategoriesCursor;
185 
TestItemsProvider(Context context)186         public TestItemsProvider(Context context) {
187             super(context);
188         }
189 
190         @Override
getAllItems(Category category, int limit, @Nullable String[] mimeType, @Nullable UserId userId)191         public Cursor getAllItems(Category category, int limit, @Nullable String[] mimeType,
192                 @Nullable UserId userId) throws
193                 IllegalArgumentException, IllegalStateException {
194             final MatrixCursor c = new MatrixCursor(MediaColumns.ALL_PROJECTION);
195 
196             for (Item item : mItemList) {
197                 c.addRow(new String[] {
198                         item.getId(),
199                         String.valueOf(item.getDateTaken()),
200                         String.valueOf(item.getGenerationModified()),
201                         item.getMimeType(),
202                         String.valueOf(item.getSpecialFormat()),
203                         "1", // size_bytes
204                         null, // media_store_uri
205                         String.valueOf(item.getDuration()),
206                         "0", // is_favorite
207                         "/storage/emulated/0/foo",
208                         PickerSyncController.LOCAL_PICKER_PROVIDER_AUTHORITY
209                 });
210             }
211 
212             return c;
213         }
214 
215         @Nullable
getAllCategories(@ullable String[] mimeType, @Nullable UserId userId)216         public Cursor getAllCategories(@Nullable String[] mimeType, @Nullable UserId userId) {
217             if (mCategoriesCursor != null) {
218                 return mCategoriesCursor;
219             }
220 
221             final MatrixCursor c = new MatrixCursor(AlbumColumns.ALL_PROJECTION);
222             return c;
223         }
224 
setItems(@onNull List<Item> itemList)225         public void setItems(@NonNull List<Item> itemList) {
226             mItemList = itemList;
227         }
228 
setCategoriesCursor(@onNull Cursor cursor)229         public void setCategoriesCursor(@NonNull Cursor cursor) {
230             mCategoriesCursor = cursor;
231         }
232     }
233 
234     @Test
testParseValuesFromPickImagesIntent_noMimeType_defaultFalse()235     public void testParseValuesFromPickImagesIntent_noMimeType_defaultFalse() {
236         final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
237 
238         mPickerViewModel.parseValuesFromIntent(intent);
239 
240         assertThat(mPickerViewModel.hasMimeTypeFilters()).isFalse();
241     }
242 
243     @Test
testParseValuesFromGetContentIntent_noMimeType_defaultFalse()244     public void testParseValuesFromGetContentIntent_noMimeType_defaultFalse() {
245         final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
246         intent.setType("*/*");
247 
248         mPickerViewModel.parseValuesFromIntent(intent);
249 
250         assertThat(mPickerViewModel.hasMimeTypeFilters()).isFalse();
251     }
252 
253     @Test
testParseValuesFromIntent_validMimeType()254     public void testParseValuesFromIntent_validMimeType() {
255         final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
256         intent.setType("image/png");
257 
258         mPickerViewModel.parseValuesFromIntent(intent);
259 
260         assertThat(mPickerViewModel.hasMimeTypeFilters()).isTrue();
261     }
262 
263     @Test
testParseValuesFromPickImagesIntent_validExtraMimeType()264     public void testParseValuesFromPickImagesIntent_validExtraMimeType() {
265         final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
266         intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[] {"image/gif", "video/*"});
267 
268         mPickerViewModel.parseValuesFromIntent(intent);
269 
270         assertThat(mPickerViewModel.hasMimeTypeFilters()).isTrue();
271     }
272 
273     @Test
testParseValuesFromPickImagesIntent_invalidExtraMimeType()274     public void testParseValuesFromPickImagesIntent_invalidExtraMimeType() {
275         final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
276         intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[] {"audio/*", "video/*"});
277 
278         try {
279             mPickerViewModel.parseValuesFromIntent(intent);
280             fail("Photo Picker does not support non-media mime type filters");
281         } catch (IllegalArgumentException expected) {
282             // Expected
283         }
284     }
285 
286     @Test
testParseValuesFromPickImagesIntent_localOnlyTrue()287     public void testParseValuesFromPickImagesIntent_localOnlyTrue() {
288         final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
289         intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
290 
291         mPickerViewModel.parseValuesFromIntent(intent);
292 
293         assertThat(mPickerViewModel.isLocalOnly()).isTrue();
294     }
295 
296     @Test
testParseValuesFromPickImagesIntent_localOnlyFalse()297     public void testParseValuesFromPickImagesIntent_localOnlyFalse() {
298         final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
299 
300         mPickerViewModel.parseValuesFromIntent(intent);
301 
302         assertThat(mPickerViewModel.isLocalOnly()).isFalse();
303     }
304 
305     @Test
testParseValuesFromGetContentIntent_validExtraMimeType()306     public void testParseValuesFromGetContentIntent_validExtraMimeType() {
307         final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
308         intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[] {"image/gif", "video/*"});
309 
310         mPickerViewModel.parseValuesFromIntent(intent);
311 
312         assertThat(mPickerViewModel.hasMimeTypeFilters()).isTrue();
313     }
314 
315     @Test
testParseValuesFromGetContentIntent_invalidExtraMimeType()316     public void testParseValuesFromGetContentIntent_invalidExtraMimeType() {
317         final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
318         intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[] {"audio/*", "video/*"});
319 
320         mPickerViewModel.parseValuesFromIntent(intent);
321 
322         // non-media filters for GET_CONTENT show all images and videos
323         assertThat(mPickerViewModel.hasMimeTypeFilters()).isFalse();
324     }
325 
326     @Test
testParseValuesFromGetContentIntent_localOnlyTrue()327     public void testParseValuesFromGetContentIntent_localOnlyTrue() {
328         final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
329         intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[] {"video/*"});
330         intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
331 
332         mPickerViewModel.parseValuesFromIntent(intent);
333 
334         assertThat(mPickerViewModel.isLocalOnly()).isTrue();
335     }
336 
337     @Test
testParseValuesFromGetContentIntent_localOnlyFalse()338     public void testParseValuesFromGetContentIntent_localOnlyFalse() {
339         final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
340         intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[] {"video/*"});
341 
342         mPickerViewModel.parseValuesFromIntent(intent);
343 
344         assertThat(mPickerViewModel.isLocalOnly()).isFalse();
345     }
346 
347     @Test
testShouldShowOnlyLocalFeatures()348     public void testShouldShowOnlyLocalFeatures() {
349         mConfigStore.enableCloudMediaFeature();
350 
351         Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
352         intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
353         mPickerViewModel.parseValuesFromIntent(intent);
354         assertThat(mPickerViewModel.isLocalOnly()).isTrue();
355         assertThat(mPickerViewModel.shouldShowOnlyLocalFeatures()).isTrue();
356 
357         intent.removeExtra(Intent.EXTRA_LOCAL_ONLY);
358         mPickerViewModel.parseValuesFromIntent(intent);
359         assertThat(mPickerViewModel.isLocalOnly()).isFalse();
360         assertThat(mPickerViewModel.shouldShowOnlyLocalFeatures()).isFalse();
361 
362         mConfigStore.disableCloudMediaFeature();
363         assertThat(mPickerViewModel.shouldShowOnlyLocalFeatures()).isTrue();
364     }
365 }
366