blob: 4cf189d87feeff267527a02293298291e1dd16a3 [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;
import static com.android.providers.media.PickerProviderMediaGenerator.ALBUM_COLUMN_TYPE_CLOUD;
import static com.android.providers.media.PickerProviderMediaGenerator.ALBUM_COLUMN_TYPE_FAVORITES;
import static com.android.providers.media.PickerProviderMediaGenerator.ALBUM_COLUMN_TYPE_LOCAL;
import static com.android.providers.media.PickerProviderMediaGenerator.MediaGenerator;
import static com.android.providers.media.photopicker.data.PickerDbFacade.QueryFilterBuilder.BOOLEAN_DEFAULT;
import static com.android.providers.media.photopicker.data.PickerDbFacade.KEY_CLOUD_ID;
import static com.android.providers.media.photopicker.data.PickerDbFacade.KEY_LOCAL_ID;
import static com.android.providers.media.photopicker.data.PickerDbFacade.QueryFilterBuilder.LONG_DEFAULT;
import static com.android.providers.media.photopicker.data.PickerDbFacade.QueryFilterBuilder.STRING_DEFAULT;
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
import android.database.Cursor;
import android.os.Bundle;
import android.os.SystemClock;
import android.provider.CloudMediaProviderContract.AlbumColumns;
import android.provider.CloudMediaProviderContract.MediaColumns;
import android.provider.MediaStore;
import android.util.Pair;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import com.android.providers.media.PickerProviderMediaGenerator;
import com.android.providers.media.photopicker.data.PickerDbFacade;
import com.android.providers.media.photopicker.data.model.Category;
import com.android.providers.media.photopicker.data.model.Item;
import com.android.providers.media.util.BackgroundThread;
import java.util.List;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class PickerSyncControllerTest {
private static final String TAG = "PickerSyncControllerTest";
private static final String LOCAL_PROVIDER_AUTHORITY =
"com.android.providers.media.photopicker.tests.local";
private static final String CLOUD_PRIMARY_PROVIDER_AUTHORITY =
"com.android.providers.media.photopicker.tests.cloud_primary";
private static final String CLOUD_SECONDARY_PROVIDER_AUTHORITY =
"com.android.providers.media.photopicker.tests.cloud_secondary";
private final MediaGenerator mLocalMediaGenerator =
PickerProviderMediaGenerator.getMediaGenerator(LOCAL_PROVIDER_AUTHORITY);
private final MediaGenerator mCloudPrimaryMediaGenerator =
PickerProviderMediaGenerator.getMediaGenerator(CLOUD_PRIMARY_PROVIDER_AUTHORITY);
private final MediaGenerator mCloudSecondaryMediaGenerator =
PickerProviderMediaGenerator.getMediaGenerator(CLOUD_SECONDARY_PROVIDER_AUTHORITY);
private static final String LOCAL_ID_1 = "1";
private static final String LOCAL_ID_2 = "2";
private static final String CLOUD_ID_1 = "1";
private static final String CLOUD_ID_2 = "2";
private static final String ALBUM_ID_1 = "1";
private static final String ALBUM_ID_2 = "2";
private static final String MIME_TYPE_DEFAULT = STRING_DEFAULT;
private static final long SIZE_BYTES_DEFAULT = LONG_DEFAULT;
private static final Pair<String, String> LOCAL_ONLY_1 = Pair.create(LOCAL_ID_1, null);
private static final Pair<String, String> LOCAL_ONLY_2 = Pair.create(LOCAL_ID_2, null);
private static final Pair<String, String> CLOUD_ONLY_1 = Pair.create(null, CLOUD_ID_1);
private static final Pair<String, String> CLOUD_ONLY_2 = Pair.create(null, CLOUD_ID_2);
private static final Pair<String, String> CLOUD_AND_LOCAL_1
= Pair.create(LOCAL_ID_1, CLOUD_ID_1);
private static final String VERSION_1 = "1";
private static final String VERSION_2 = "2";
private static final String IMAGE_MIME_TYPE = "image/jpeg";
private static final String VIDEO_MIME_TYPE = "video/mp4";
private static final long SIZE_BYTES = 50;
private static final long SYNC_DELAY_MS = 1000;
private Context mContext;
private PickerDbFacade mFacade;
private PickerSyncController mController;
@Before
public void setUp() {
mLocalMediaGenerator.resetAll();
mCloudPrimaryMediaGenerator.resetAll();
mCloudSecondaryMediaGenerator.resetAll();
mLocalMediaGenerator.setVersion(VERSION_1);
mCloudPrimaryMediaGenerator.setVersion(VERSION_1);
mCloudSecondaryMediaGenerator.setVersion(VERSION_1);
mContext = InstrumentationRegistry.getTargetContext();
mFacade = new PickerDbFacade(mContext, LOCAL_PROVIDER_AUTHORITY);
mController = new PickerSyncController(mContext, mFacade, LOCAL_PROVIDER_AUTHORITY,
/* syncDelay */ 0);
mFacade.resetMedia(LOCAL_PROVIDER_AUTHORITY);
mFacade.resetMedia(null);
}
@After
public void tearDown() {
// Set cloud provider to null to avoid trying to sync it during other tests
// that might be using an IsolatedContext
mController.setCloudProvider(null);
}
@Test
public void testSyncPickerLocalOnly() {
// 1. Do nothing
mController.syncPicker();
assertEmptyCursor();
// 2. Add local only media
addMedia(mLocalMediaGenerator, LOCAL_ONLY_1);
addMedia(mLocalMediaGenerator, LOCAL_ONLY_2);
mController.syncPicker();
try (Cursor cr = queryMedia()) {
assertThat(cr.getCount()).isEqualTo(2);
assertCursor(cr, LOCAL_ID_2, LOCAL_PROVIDER_AUTHORITY);
assertCursor(cr, LOCAL_ID_1, LOCAL_PROVIDER_AUTHORITY);
}
// 3. Delete one local-only media
deleteMedia(mLocalMediaGenerator, LOCAL_ONLY_1);
mController.syncPicker();
try (Cursor cr = queryMedia()) {
assertThat(cr.getCount()).isEqualTo(1);
assertCursor(cr, LOCAL_ID_2, LOCAL_PROVIDER_AUTHORITY);
}
// 4. Reset media without version bump
mLocalMediaGenerator.resetAll();
mController.syncPicker();
try (Cursor cr = queryMedia()) {
assertThat(cr.getCount()).isEqualTo(1);
assertCursor(cr, LOCAL_ID_2, LOCAL_PROVIDER_AUTHORITY);
}
// 5. Bump version
mLocalMediaGenerator.setVersion(VERSION_2);
mController.syncPicker();
assertEmptyCursor();
}
@Test
public void testSyncPickerCloudOnly() {
// 1. Add media before setting primary cloud provider
addMedia(mCloudPrimaryMediaGenerator, CLOUD_ONLY_1);
addMedia(mCloudPrimaryMediaGenerator, CLOUD_ONLY_2);
mController.syncPicker();
assertEmptyCursor();
// 2. Set secondary cloud provider
mController.setCloudProvider(CLOUD_SECONDARY_PROVIDER_AUTHORITY);
mController.syncPicker();
assertEmptyCursor();
// 3. Set primary cloud provider
mController.setCloudProvider(CLOUD_PRIMARY_PROVIDER_AUTHORITY);
mController.syncPicker();
try (Cursor cr = queryMedia()) {
assertThat(cr.getCount()).isEqualTo(2);
assertCursor(cr, CLOUD_ID_2, CLOUD_PRIMARY_PROVIDER_AUTHORITY);
assertCursor(cr, CLOUD_ID_1, CLOUD_PRIMARY_PROVIDER_AUTHORITY);
}
// 4. Set secondary cloud provider again
mController.setCloudProvider(CLOUD_SECONDARY_PROVIDER_AUTHORITY);
mController.syncPicker();
assertEmptyCursor();
// 5. Set primary cloud provider once again
mController.setCloudProvider(CLOUD_PRIMARY_PROVIDER_AUTHORITY);
mController.syncPicker();
try (Cursor cr = queryMedia()) {
assertThat(cr.getCount()).isEqualTo(2);
assertCursor(cr, CLOUD_ID_2, CLOUD_PRIMARY_PROVIDER_AUTHORITY);
assertCursor(cr, CLOUD_ID_1, CLOUD_PRIMARY_PROVIDER_AUTHORITY);
}
// 6. Clear cloud provider
mController.setCloudProvider(/* authority */ null);
mController.syncPicker();
assertEmptyCursor();
}
@Test
public void testCloudResetSync() {
mController.setCloudProvider(CLOUD_PRIMARY_PROVIDER_AUTHORITY);
// 1. Do nothing
mController.syncPicker();
assertEmptyCursor();
// 2. Add cloud-only item
addMedia(mCloudPrimaryMediaGenerator, CLOUD_ONLY_1);
mController.syncPicker();
try (Cursor cr = queryMedia()) {
assertThat(cr.getCount()).isEqualTo(1);
assertCursor(cr, CLOUD_ID_1, CLOUD_PRIMARY_PROVIDER_AUTHORITY);
}
// 3. Set invalid cloud version
mCloudPrimaryMediaGenerator.setVersion(/* version */ null);
mController.syncPicker();
assertEmptyCursor();
// 4. Set valid cloud version
mCloudPrimaryMediaGenerator.setVersion(VERSION_1);
mController.syncPicker();
try (Cursor cr = queryMedia()) {
assertThat(cr.getCount()).isEqualTo(1);
assertCursor(cr, CLOUD_ID_1, CLOUD_PRIMARY_PROVIDER_AUTHORITY);
}
}
@Test
public void testSyncPickerCloudAndLocal() {
// 1. Do nothing
assertEmptyCursor();
// 2. Set primary cloud provider and add 2 items: cloud+local and local-only
addMedia(mLocalMediaGenerator, LOCAL_ONLY_1);
addMedia(mCloudPrimaryMediaGenerator, CLOUD_AND_LOCAL_1);
mController.setCloudProvider(CLOUD_PRIMARY_PROVIDER_AUTHORITY);
mController.syncPicker();
try (Cursor cr = queryMedia()) {
assertThat(cr.getCount()).isEqualTo(1);
assertCursor(cr, LOCAL_ID_1, LOCAL_PROVIDER_AUTHORITY);
}
// 3. Delete local-only item
deleteMedia(mLocalMediaGenerator, LOCAL_ONLY_1);
mController.syncPicker();
try (Cursor cr = queryMedia()) {
assertThat(cr.getCount()).isEqualTo(1);
assertCursor(cr, CLOUD_ID_1, CLOUD_PRIMARY_PROVIDER_AUTHORITY);
}
// 4. Re-add local-only item
addMedia(mLocalMediaGenerator, LOCAL_ONLY_1);
mController.syncPicker();
try (Cursor cr = queryMedia()) {
assertThat(cr.getCount()).isEqualTo(1);
assertCursor(cr, LOCAL_ID_1, LOCAL_PROVIDER_AUTHORITY);
}
// 5. Delete cloud+local item
deleteMedia(mCloudPrimaryMediaGenerator, CLOUD_AND_LOCAL_1);
mController.syncPicker();
try (Cursor cr = queryMedia()) {
assertThat(cr.getCount()).isEqualTo(1);
assertCursor(cr, LOCAL_ID_1, LOCAL_PROVIDER_AUTHORITY);
}
// 6. Delete local-only item
deleteMedia(mLocalMediaGenerator, LOCAL_ONLY_1);
mController.syncPicker();
assertEmptyCursor();
}
@Test
public void testSetCloudProvider() {
//1. Get local provider assertion out of the way
assertThat(mController.getLocalProvider()).isEqualTo(LOCAL_PROVIDER_AUTHORITY);
// Assert that no cloud provider set on facade
assertThat(mFacade.getCloudProvider()).isNull();
// 2. Can set cloud provider
assertThat(mController.setCloudProvider(CLOUD_PRIMARY_PROVIDER_AUTHORITY)).isTrue();
assertThat(mController.getCloudProvider()).isEqualTo(CLOUD_PRIMARY_PROVIDER_AUTHORITY);
// Assert that setting cloud provider clears facade cloud provider
// And after syncing, the latest provider is set on the facade
assertThat(mFacade.getCloudProvider()).isNull();
mController.syncPicker();
assertThat(mFacade.getCloudProvider()).isEqualTo(CLOUD_PRIMARY_PROVIDER_AUTHORITY);
// 3. Can clear cloud provider
assertThat(mController.setCloudProvider(null)).isTrue();
assertThat(mController.getCloudProvider()).isNull();
// Assert that setting cloud provider clears facade cloud provider
// And after syncing, the latest provider is set on the facade
assertThat(mFacade.getCloudProvider()).isNull();
mController.syncPicker();
assertThat(mFacade.getCloudProvider()).isNull();
// 4. Can set cloud proivder
assertThat(mController.setCloudProvider(CLOUD_PRIMARY_PROVIDER_AUTHORITY)).isTrue();
assertThat(mController.getCloudProvider()).isEqualTo(CLOUD_PRIMARY_PROVIDER_AUTHORITY);
// Assert that setting cloud provider clears facade cloud provider
// And after syncing, the latest provider is set on the facade
assertThat(mFacade.getCloudProvider()).isNull();
mController.syncPicker();
assertThat(mFacade.getCloudProvider()).isEqualTo(CLOUD_PRIMARY_PROVIDER_AUTHORITY);
// Invalid cloud provider is ignored
assertThat(mController.setCloudProvider(LOCAL_PROVIDER_AUTHORITY)).isFalse();
assertThat(mController.getCloudProvider()).isEqualTo(CLOUD_PRIMARY_PROVIDER_AUTHORITY);
// Assert that unsuccessfully setting cloud provider doesn't clear facade cloud provider
// And after syncing, nothing changes
assertThat(mFacade.getCloudProvider()).isEqualTo(CLOUD_PRIMARY_PROVIDER_AUTHORITY);
mController.syncPicker();
assertThat(mFacade.getCloudProvider()).isEqualTo(CLOUD_PRIMARY_PROVIDER_AUTHORITY);
}
@Test
public void testGetSupportedCloudProviders() {
List<String> providers = mController.getSupportedCloudProviders();
assertThat(providers).containsExactly(CLOUD_PRIMARY_PROVIDER_AUTHORITY,
CLOUD_SECONDARY_PROVIDER_AUTHORITY);
}
@Test
public void testNotifyMediaEvent() {
PickerSyncController controller = new PickerSyncController(mContext, mFacade,
LOCAL_PROVIDER_AUTHORITY, SYNC_DELAY_MS);
// 1. Add media and notify
addMedia(mLocalMediaGenerator, LOCAL_ONLY_1);
controller.notifyMediaEvent();
BackgroundThread.waitForIdle();
assertEmptyCursor();
// 2. Sleep for delay
SystemClock.sleep(SYNC_DELAY_MS);
BackgroundThread.waitForIdle();
try (Cursor cr = queryMedia()) {
assertThat(cr.getCount()).isEqualTo(1);
assertCursor(cr, LOCAL_ID_1, LOCAL_PROVIDER_AUTHORITY);
}
}
private static void addMedia(MediaGenerator generator, Pair<String, String> media) {
generator.addMedia(media.first, media.second);
}
private static void addMedia(MediaGenerator generator, Pair<String, String> media,
String albumId, String mimeType, long sizeBytes, boolean isFavorite) {
generator.addMedia(media.first, media.second, albumId, mimeType, sizeBytes, isFavorite);
}
private static void deleteMedia(MediaGenerator generator, Pair<String, String> media) {
generator.deleteMedia(media.first, media.second);
}
private Cursor queryMedia() {
return mFacade.queryMedia(new PickerDbFacade.QueryFilterBuilder(1000).build());
}
private void assertEmptyCursor() {
try (Cursor cr = queryMedia()) {
assertThat(cr.getCount()).isEqualTo(0);
}
}
private static void assertCursor(Cursor cursor, String id, String expectedAuthority) {
cursor.moveToNext();
assertThat(cursor.getString(cursor.getColumnIndex(MediaColumns.ID)))
.isEqualTo(id);
final int authorityIdx = cursor.getColumnIndex(MediaColumns.AUTHORITY);
final String authority;
if (authorityIdx >= 0) {
// Cursor from picker db has authority as a column
authority = cursor.getString(authorityIdx);
} else {
// Cursor from provider directly doesn't have an authority column but will
// have the authority set as an extra
final Bundle bundle = cursor.getExtras();
authority = bundle.getString(MediaColumns.AUTHORITY);
}
assertThat(authority).isEqualTo(expectedAuthority);
}
}