blob: 2eb0aa1d130c0f054c643c26cbe2e15c35e31785 [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 com.android.providers.media.photopicker.viewmodel;
import static android.provider.CloudMediaProviderContract.AlbumColumns;
import static android.provider.CloudMediaProviderContract.MediaColumns;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.provider.MediaStore;
import android.text.format.DateUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import com.android.providers.media.ConfigStore;
import com.android.providers.media.TestConfigStore;
import com.android.providers.media.photopicker.PickerSyncController;
import com.android.providers.media.photopicker.data.ItemsProvider;
import com.android.providers.media.photopicker.data.UserIdManager;
import com.android.providers.media.photopicker.data.model.Category;
import com.android.providers.media.photopicker.data.model.Item;
import com.android.providers.media.photopicker.data.model.ModelTestUtils;
import com.android.providers.media.photopicker.data.model.UserId;
import com.android.providers.media.util.ForegroundThread;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.List;
@RunWith(AndroidJUnit4.class)
public class PickerViewModelTest {
private static final String FAKE_CATEGORY_NAME = "testCategoryName";
private static final String FAKE_ID = "5";
@Rule
public InstantTaskExecutorRule instantTaskExecutorRule = new InstantTaskExecutorRule();
@Mock
private Application mApplication;
private PickerViewModel mPickerViewModel;
private TestItemsProvider mItemsProvider;
private TestConfigStore mConfigStore;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
final Context context = InstrumentationRegistry.getTargetContext();
when(mApplication.getApplicationContext()).thenReturn(context);
mConfigStore = new TestConfigStore();
mConfigStore.enableCloudMediaFeature();
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
mPickerViewModel = new PickerViewModel(mApplication) {
@Override
protected ConfigStore getConfigStore() {
return mConfigStore;
}
};
});
mItemsProvider = new TestItemsProvider(context);
mPickerViewModel.setItemsProvider(mItemsProvider);
UserIdManager userIdManager = mock(UserIdManager.class);
when(userIdManager.getCurrentUserProfileId()).thenReturn(UserId.CURRENT_USER);
mPickerViewModel.setUserIdManager(userIdManager);
}
@Test
public void testGetItems_noItems() {
final int itemCount = 0;
mItemsProvider.setItems(generateFakeImageItemList(itemCount));
mPickerViewModel.updateItems();
// We use ForegroundThread to execute the loadItems in updateItems(), wait for the thread
// idle
ForegroundThread.waitForIdle();
final List<Item> itemList = mPickerViewModel.getItems().getValue();
// No date headers, the size should be 0
assertThat(itemList.size()).isEqualTo(itemCount);
}
@Test
public void testGetCategories() throws Exception {
final Context context = InstrumentationRegistry.getTargetContext();
final int categoryCount = 2;
try (final Cursor fakeCursor = generateCursorForFakeCategories(categoryCount)) {
fakeCursor.moveToFirst();
final Category fakeFirstCategory = Category.fromCursor(fakeCursor, UserId.CURRENT_USER);
fakeCursor.moveToNext();
final Category fakeSecondCategory = Category.fromCursor(fakeCursor,
UserId.CURRENT_USER);
mItemsProvider.setCategoriesCursor(fakeCursor);
// move the cursor to original position
fakeCursor.moveToPosition(-1);
mPickerViewModel.updateCategories();
// We use ForegroundThread to execute the loadCategories in updateCategories(), wait for
// the thread idle
ForegroundThread.waitForIdle();
final List<Category> categoryList = mPickerViewModel.getCategories().getValue();
assertThat(categoryList.size()).isEqualTo(categoryCount);
// Verify the first category
final Category firstCategory = categoryList.get(0);
assertThat(firstCategory.getDisplayName(context)).isEqualTo(
fakeFirstCategory.getDisplayName(context));
assertThat(firstCategory.getItemCount()).isEqualTo(fakeFirstCategory.getItemCount());
assertThat(firstCategory.getCoverUri()).isEqualTo(fakeFirstCategory.getCoverUri());
// Verify the second category
final Category secondCategory = categoryList.get(1);
assertThat(secondCategory.getDisplayName(context)).isEqualTo(
fakeSecondCategory.getDisplayName(context));
assertThat(secondCategory.getItemCount()).isEqualTo(fakeSecondCategory.getItemCount());
assertThat(secondCategory.getCoverUri()).isEqualTo(fakeSecondCategory.getCoverUri());
}
}
private static Item generateFakeImageItem(String id) {
final long dateTakenMs = System.currentTimeMillis()
+ Long.parseLong(id) * DateUtils.DAY_IN_MILLIS;
return ModelTestUtils.generateJpegItem(id, dateTakenMs, /* generationModified */ 1L);
}
private static List<Item> generateFakeImageItemList(int num) {
final List<Item> itemList = new ArrayList<>();
for (int i = 0; i < num; i++) {
itemList.add(generateFakeImageItem(String.valueOf(i)));
}
return itemList;
}
private static Cursor generateCursorForFakeCategories(int num) {
final MatrixCursor cursor = new MatrixCursor(AlbumColumns.ALL_PROJECTION);
final int itemCount = 5;
for (int i = 0; i < num; i++) {
cursor.addRow(new Object[]{
FAKE_ID + String.valueOf(i),
System.currentTimeMillis(),
FAKE_CATEGORY_NAME + i,
FAKE_ID + String.valueOf(i),
itemCount + i,
PickerSyncController.LOCAL_PICKER_PROVIDER_AUTHORITY
});
}
return cursor;
}
private static class TestItemsProvider extends ItemsProvider {
private List<Item> mItemList = new ArrayList<>();
private Cursor mCategoriesCursor;
public TestItemsProvider(Context context) {
super(context);
}
@Override
public Cursor getAllItems(Category category, int limit, @Nullable String[] mimeType,
@Nullable UserId userId) throws
IllegalArgumentException, IllegalStateException {
final MatrixCursor c = new MatrixCursor(MediaColumns.ALL_PROJECTION);
for (Item item : mItemList) {
c.addRow(new String[] {
item.getId(),
String.valueOf(item.getDateTaken()),
String.valueOf(item.getGenerationModified()),
item.getMimeType(),
String.valueOf(item.getSpecialFormat()),
"1", // size_bytes
null, // media_store_uri
String.valueOf(item.getDuration()),
"0", // is_favorite
"/storage/emulated/0/foo",
PickerSyncController.LOCAL_PICKER_PROVIDER_AUTHORITY
});
}
return c;
}
@Nullable
public Cursor getAllCategories(@Nullable String[] mimeType, @Nullable UserId userId) {
if (mCategoriesCursor != null) {
return mCategoriesCursor;
}
final MatrixCursor c = new MatrixCursor(AlbumColumns.ALL_PROJECTION);
return c;
}
public void setItems(@NonNull List<Item> itemList) {
mItemList = itemList;
}
public void setCategoriesCursor(@NonNull Cursor cursor) {
mCategoriesCursor = cursor;
}
}
@Test
public void testParseValuesFromPickImagesIntent_noMimeType_defaultFalse() {
final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
mPickerViewModel.parseValuesFromIntent(intent);
assertThat(mPickerViewModel.hasMimeTypeFilters()).isFalse();
}
@Test
public void testParseValuesFromGetContentIntent_noMimeType_defaultFalse() {
final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
intent.setType("*/*");
mPickerViewModel.parseValuesFromIntent(intent);
assertThat(mPickerViewModel.hasMimeTypeFilters()).isFalse();
}
@Test
public void testParseValuesFromIntent_validMimeType() {
final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
intent.setType("image/png");
mPickerViewModel.parseValuesFromIntent(intent);
assertThat(mPickerViewModel.hasMimeTypeFilters()).isTrue();
}
@Test
public void testParseValuesFromPickImagesIntent_validExtraMimeType() {
final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[] {"image/gif", "video/*"});
mPickerViewModel.parseValuesFromIntent(intent);
assertThat(mPickerViewModel.hasMimeTypeFilters()).isTrue();
}
@Test
public void testParseValuesFromPickImagesIntent_invalidExtraMimeType() {
final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[] {"audio/*", "video/*"});
try {
mPickerViewModel.parseValuesFromIntent(intent);
fail("Photo Picker does not support non-media mime type filters");
} catch (IllegalArgumentException expected) {
// Expected
}
}
@Test
public void testParseValuesFromPickImagesIntent_localOnlyTrue() {
final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
mPickerViewModel.parseValuesFromIntent(intent);
assertThat(mPickerViewModel.isLocalOnly()).isTrue();
}
@Test
public void testParseValuesFromPickImagesIntent_localOnlyFalse() {
final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
mPickerViewModel.parseValuesFromIntent(intent);
assertThat(mPickerViewModel.isLocalOnly()).isFalse();
}
@Test
public void testParseValuesFromGetContentIntent_validExtraMimeType() {
final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[] {"image/gif", "video/*"});
mPickerViewModel.parseValuesFromIntent(intent);
assertThat(mPickerViewModel.hasMimeTypeFilters()).isTrue();
}
@Test
public void testParseValuesFromGetContentIntent_invalidExtraMimeType() {
final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[] {"audio/*", "video/*"});
mPickerViewModel.parseValuesFromIntent(intent);
// non-media filters for GET_CONTENT show all images and videos
assertThat(mPickerViewModel.hasMimeTypeFilters()).isFalse();
}
@Test
public void testParseValuesFromGetContentIntent_localOnlyTrue() {
final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[] {"video/*"});
intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
mPickerViewModel.parseValuesFromIntent(intent);
assertThat(mPickerViewModel.isLocalOnly()).isTrue();
}
@Test
public void testParseValuesFromGetContentIntent_localOnlyFalse() {
final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[] {"video/*"});
mPickerViewModel.parseValuesFromIntent(intent);
assertThat(mPickerViewModel.isLocalOnly()).isFalse();
}
@Test
public void testShouldShowOnlyLocalFeatures() {
mConfigStore.enableCloudMediaFeature();
Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
mPickerViewModel.parseValuesFromIntent(intent);
assertThat(mPickerViewModel.isLocalOnly()).isTrue();
assertThat(mPickerViewModel.shouldShowOnlyLocalFeatures()).isTrue();
intent.removeExtra(Intent.EXTRA_LOCAL_ONLY);
mPickerViewModel.parseValuesFromIntent(intent);
assertThat(mPickerViewModel.isLocalOnly()).isFalse();
assertThat(mPickerViewModel.shouldShowOnlyLocalFeatures()).isFalse();
mConfigStore.disableCloudMediaFeature();
assertThat(mPickerViewModel.shouldShowOnlyLocalFeatures()).isTrue();
}
}