blob: f0b6b03436067185634f51cd391f77a06b76012f [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.espresso;
import static androidx.test.InstrumentationRegistry.getTargetContext;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.Manifest;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Process;
import android.provider.MediaStore;
import android.system.ErrnoException;
import android.system.Os;
import androidx.core.util.Supplier;
import androidx.lifecycle.MutableLiveData;
import androidx.test.InstrumentationRegistry;
import androidx.work.testing.WorkManagerTestInitHelper;
import com.android.providers.media.IsolatedContext;
import com.android.providers.media.R;
import com.android.providers.media.photopicker.data.UserIdManager;
import com.android.providers.media.photopicker.data.model.UserId;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.attribute.FileTime;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public class PhotoPickerBaseTest {
protected static final int PICKER_TAB_RECYCLERVIEW_ID = R.id.picker_tab_recyclerview;
protected static final int TAB_VIEW_PAGER_ID = R.id.picker_tab_viewpager;
protected static final int TAB_LAYOUT_ID = R.id.tab_layout;
protected static final int PICKER_PHOTOS_STRING_ID = R.string.picker_photos;
protected static final int PICKER_ALBUMS_STRING_ID = R.string.picker_albums;
protected static final int PICKER_VIDEOS_STRING_ID = R.string.picker_videos;
protected static final int PREVIEW_VIEW_PAGER_ID = R.id.preview_viewPager;
protected static final int ICON_CHECK_ID = R.id.icon_check;
protected static final int ICON_THUMBNAIL_ID = R.id.icon_thumbnail;
protected static final int VIEW_SELECTED_BUTTON_ID = R.id.button_view_selected;
protected static final int PREVIEW_IMAGE_VIEW_ID = R.id.preview_imageView;
protected static final int DRAG_BAR_ID = R.id.drag_bar;
protected static final int PREVIEW_GIF_ID = R.id.preview_gif;
protected static final int PREVIEW_MOTION_PHOTO_ID = R.id.preview_motion_photo;
protected static final int PREVIEW_ADD_OR_SELECT_BUTTON_ID = R.id.preview_add_or_select_button;
protected static final int PRIVACY_TEXT_ID = R.id.privacy_text;
protected static final String GIF_IMAGE_MIME_TYPE = "image/gif";
protected static final String ANIMATED_WEBP_MIME_TYPE = "image/webp";
protected static final String JPEG_IMAGE_MIME_TYPE = "image/jpeg";
protected static final String MP4_VIDEO_MIME_TYPE = "video/mp4";
protected static final String MANAGED_SELECTION_ENABLED_EXTRA = "MANAGED_SELECTION_ENABLE";
protected static final int DIMEN_PREVIEW_ADD_OR_SELECT_WIDTH
= R.dimen.preview_add_or_select_width;
/**
* The position of the first image item in the grid on the Photos tab
*/
protected static final int IMAGE_1_POSITION = 1;
/**
* The position of the second item in the grid on the Photos tab
*/
protected static final int IMAGE_2_POSITION = 2;
/**
* The position of the video item in the grid on the Photos tab
*/
protected static final int VIDEO_POSITION = 3;
/**
* The default position of a banner in the Photos & Albums tab recycler view adapters
*/
static final int DEFAULT_BANNER_POSITION = 0;
private static final Intent sSingleSelectIntent;
static {
sSingleSelectIntent = new Intent(MediaStore.ACTION_PICK_IMAGES);
sSingleSelectIntent.addCategory(Intent.CATEGORY_FRAMEWORK_INSTRUMENTATION_TEST);
}
private static final Intent sMultiSelectionIntent;
static {
sMultiSelectionIntent = new Intent(MediaStore.ACTION_PICK_IMAGES);
Bundle extras = new Bundle();
extras.putInt(MediaStore.EXTRA_PICK_IMAGES_MAX, MediaStore.getPickImagesMaxLimit());
sMultiSelectionIntent.addCategory(Intent.CATEGORY_FRAMEWORK_INSTRUMENTATION_TEST);
sMultiSelectionIntent.putExtras(extras);
}
private static final Intent sUserSelectImagesForAppIntent;
static {
sUserSelectImagesForAppIntent = new Intent(MediaStore.ACTION_USER_SELECT_IMAGES_FOR_APP);
sUserSelectImagesForAppIntent.addCategory(Intent.CATEGORY_FRAMEWORK_INSTRUMENTATION_TEST);
Bundle extras = new Bundle();
extras.putInt(Intent.EXTRA_UID, Process.myUid());
sUserSelectImagesForAppIntent.putExtras(extras);
}
private static final Intent sPickerChoiceManagedSelectionIntent;
static {
sPickerChoiceManagedSelectionIntent = new Intent(
MediaStore.ACTION_USER_SELECT_IMAGES_FOR_APP);
sPickerChoiceManagedSelectionIntent.addCategory(
Intent.CATEGORY_FRAMEWORK_INSTRUMENTATION_TEST);
Bundle extras = new Bundle();
extras.putInt(Intent.EXTRA_UID, Process.myUid());
extras.putBoolean(MANAGED_SELECTION_ENABLED_EXTRA, true);
sPickerChoiceManagedSelectionIntent.putExtras(extras);
}
public static final File IMAGE_1_FILE = new File(Environment.getExternalStorageDirectory(),
Environment.DIRECTORY_DCIM + "/Camera"
+ "/image_" + System.currentTimeMillis() + ".jpeg");
private static final File IMAGE_2_FILE = new File(Environment.getExternalStorageDirectory(),
Environment.DIRECTORY_DOWNLOADS + "/image_" + System.currentTimeMillis() + ".jpeg");
private static final File VIDEO_FILE = new File(Environment.getExternalStorageDirectory(),
Environment.DIRECTORY_MOVIES + "/video_" + System.currentTimeMillis() + ".mp4");
private static final long POLLING_TIMEOUT_MILLIS_LONG = TimeUnit.SECONDS.toMillis(2);
private static final long POLLING_SLEEP_MILLIS = 200;
private static IsolatedContext sIsolatedContext;
private static UserIdManager sUserIdManager;
public static Intent getSingleSelectMimeTypeFilterIntent(String mimeTypeFilter) {
final Intent intent = new Intent(sSingleSelectIntent);
intent.setType(mimeTypeFilter);
return intent;
}
public static Intent getSingleSelectionIntent() {
return sSingleSelectIntent;
}
public static Intent getMultiSelectionIntent() {
return sMultiSelectionIntent;
}
public static Intent getUserSelectImagesForAppIntent() {
return sUserSelectImagesForAppIntent;
}
public static Intent getPickerChoiceManagedSelectionIntent() {
return sPickerChoiceManagedSelectionIntent;
}
public static Intent getMultiSelectionIntent(int max) {
final Intent intent = new Intent(sMultiSelectionIntent);
Bundle extras = new Bundle();
extras.putInt(MediaStore.EXTRA_PICK_IMAGES_MAX, max);
intent.putExtras(extras);
return intent;
}
public static IsolatedContext getIsolatedContext() {
return sIsolatedContext;
}
public static UserIdManager getMockUserIdManager() {
return sUserIdManager;
}
@BeforeClass
public static void setupClass() throws Exception {
MediaStore.waitForIdle(getTargetContext().getContentResolver());
pollForCondition(() -> isExternalStorageStateMounted(), "Timed out while"
+ " waiting for ExternalStorageState to be MEDIA_MOUNTED");
InstrumentationRegistry.getInstrumentation().getUiAutomation()
.adoptShellPermissionIdentity(Manifest.permission.LOG_COMPAT_CHANGE,
Manifest.permission.READ_COMPAT_CHANGE_CONFIG,
Manifest.permission.INTERACT_ACROSS_USERS,
Manifest.permission.READ_DEVICE_CONFIG);
sIsolatedContext = new IsolatedContext(getTargetContext(), "modern",
/* asFuseThread */ false);
sUserIdManager = mock(UserIdManager.class);
when(sUserIdManager.getCurrentUserProfileId()).thenReturn(UserId.CURRENT_USER);
WorkManagerTestInitHelper.initializeTestWorkManager(sIsolatedContext);
createFiles();
}
@AfterClass
public static void destroyClass() {
deleteFiles(/* invalidateMediaStore */ false);
InstrumentationRegistry.getInstrumentation()
.getUiAutomation().dropShellPermissionIdentity();
}
protected static void deleteFiles(boolean invalidateMediaStore) {
deleteFile(IMAGE_1_FILE, invalidateMediaStore);
deleteFile(IMAGE_2_FILE, invalidateMediaStore);
deleteFile(VIDEO_FILE, invalidateMediaStore);
}
private static void deleteFile(File file, boolean invalidateMediaStore) {
file.delete();
if (invalidateMediaStore) {
final Uri uri = MediaStore.scanFile(getIsolatedContext().getContentResolver(), file);
assertThat(uri).isNull();
// Force picker db sync for that db operation
MediaStore.waitForIdle(getIsolatedContext().getContentResolver());
}
}
private static void createFiles() throws Exception {
long timeNow = System.currentTimeMillis();
// Create files and change dateModified so that we can predict the recyclerView item
// position. Set modified date ahead of time, so that even if other files are created,
// the below files always have positions 1, 2 and 3.
createFile(IMAGE_1_FILE, timeNow + 30000);
createFile(IMAGE_2_FILE, timeNow + 20000);
createFile(VIDEO_FILE, timeNow + 10000);
}
private static void pollForCondition(Supplier<Boolean> condition, String errorMessage)
throws Exception {
for (int i = 0; i < POLLING_TIMEOUT_MILLIS_LONG / POLLING_SLEEP_MILLIS; i++) {
if (condition.get()) {
return;
}
Thread.sleep(POLLING_SLEEP_MILLIS);
}
throw new TimeoutException(errorMessage);
}
private static boolean isExternalStorageStateMounted() {
final File target = Environment.getExternalStorageDirectory();
try {
return (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState(target))
&& Os.statvfs(target.getAbsolutePath()).f_blocks > 0);
} catch (ErrnoException ignored) {
}
return false;
}
private static void createFile(File file, long dateModified) throws IOException {
File parentFile = file.getParentFile();
parentFile.mkdirs();
assertThat(parentFile.exists()).isTrue();
assertThat(file.createNewFile()).isTrue();
// Write 1 byte because 0byte files are not valid in the picker db
try (FileOutputStream fos = new FileOutputStream(file)) {
fos.write(1);
}
// Change dateModified so that we can predict the recyclerView item position
Files.setLastModifiedTime(file.toPath(), FileTime.fromMillis(dateModified));
final Uri uri = MediaStore.scanFile(getIsolatedContext().getContentResolver(), file);
MediaStore.waitForIdle(getIsolatedContext().getContentResolver());
assertThat(uri).isNotNull();
}
/**
* Mock UserIdManager class such that the profile button is active and the user is in personal
* profile.
*/
static void setUpActiveProfileButton() {
when(sUserIdManager.isMultiUserProfiles()).thenReturn(true);
when(sUserIdManager.isBlockedByAdmin()).thenReturn(false);
when(sUserIdManager.isWorkProfileOff()).thenReturn(false);
when(sUserIdManager.isCrossProfileAllowed()).thenReturn(true);
when(sUserIdManager.isManagedUserSelected()).thenReturn(false);
// setPersonalAsCurrentUserProfile() is called onClick of Active Profile Button to change
// profiles
doAnswer(invocation -> {
updateIsManagedUserSelected(/* isManagedUserSelected */ false);
return null;
}).when(sUserIdManager).setPersonalAsCurrentUserProfile();
// setManagedAsCurrentUserProfile() is called onClick of Active Profile Button to change
// profiles
doAnswer(invocation -> {
updateIsManagedUserSelected(/* isManagedUserSelected */ true);
return null;
}).when(sUserIdManager).setManagedAsCurrentUserProfile();
when(sUserIdManager.getCrossProfileAllowed()).thenReturn(new MutableLiveData<>(true));
}
/**
* Mock UserIdManager class such that the user is in personal profile and work apps are
* turned off
*/
static void setUpWorkAppsOffProfileButton() {
when(sUserIdManager.isMultiUserProfiles()).thenReturn(true);
when(sUserIdManager.isBlockedByAdmin()).thenReturn(false);
when(sUserIdManager.isWorkProfileOff()).thenReturn(true);
when(sUserIdManager.isCrossProfileAllowed()).thenReturn(false);
when(sUserIdManager.isManagedUserSelected()).thenReturn(false);
}
/**
* Mock UserIdManager class such that the user is in work profile and accessing personal
* profile content is blocked by admin
*/
static void setUpBlockedByAdminProfileButton() {
when(sUserIdManager.isMultiUserProfiles()).thenReturn(true);
when(sUserIdManager.isBlockedByAdmin()).thenReturn(true);
when(sUserIdManager.isWorkProfileOff()).thenReturn(false);
when(sUserIdManager.isCrossProfileAllowed()).thenReturn(false);
when(sUserIdManager.isManagedUserSelected()).thenReturn(true);
when(sUserIdManager.getCrossProfileAllowed()).thenReturn(new MutableLiveData<>(false));
}
private static void updateIsManagedUserSelected(boolean isManagedUserSelected) {
when(sUserIdManager.isManagedUserSelected()).thenReturn(isManagedUserSelected);
}
}