diff options
author | Shubhi Saxena <shubhisaxena@google.com> | 2023-02-27 13:56:35 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2023-02-27 13:56:35 +0000 |
commit | 7dd2ef7631703b1064ee30beecd7338ea0a38ce9 (patch) | |
tree | cb856ff7b383db7304dd2b5e287b1ec5bbb393aa | |
parent | ba096ab4695b23c485d161d4003c47b6a27b7683 (diff) | |
parent | 535ba803eaa9946019ada37c8a40f3f057b953e1 (diff) | |
download | MediaProvider-7dd2ef7631703b1064ee30beecd7338ea0a38ce9.tar.gz |
Merge "Add unit tests for Picker Settings" into tm-mainline-prod
4 files changed, 295 insertions, 60 deletions
diff --git a/src/com/android/providers/media/photopicker/ui/settings/SettingsCloudMediaViewModel.java b/src/com/android/providers/media/photopicker/ui/settings/SettingsCloudMediaViewModel.java index b3e6ec994..a60a32823 100644 --- a/src/com/android/providers/media/photopicker/ui/settings/SettingsCloudMediaViewModel.java +++ b/src/com/android/providers/media/photopicker/ui/settings/SettingsCloudMediaViewModel.java @@ -17,10 +17,10 @@ package com.android.providers.media.photopicker.ui.settings; import static android.provider.MediaStore.AUTHORITY; -import static android.provider.MediaStore.EXTRA_CLOUD_PROVIDER; -import static android.provider.MediaStore.GET_CLOUD_PROVIDER_CALL; -import static android.provider.MediaStore.GET_CLOUD_PROVIDER_RESULT; -import static android.provider.MediaStore.SET_CLOUD_PROVIDER_CALL; + +import static com.android.providers.media.photopicker.util.CloudProviderUtils.fetchProviderAuthority; +import static com.android.providers.media.photopicker.util.CloudProviderUtils.getAllAvailableCloudProviders; +import static com.android.providers.media.photopicker.util.CloudProviderUtils.persistSelectedProvider; import static java.util.Objects.requireNonNull; @@ -28,20 +28,18 @@ import android.content.ContentProviderClient; import android.content.Context; import android.content.pm.PackageManager; import android.graphics.drawable.Drawable; -import android.os.Bundle; import android.os.UserHandle; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.appcompat.content.res.AppCompatResources; +import androidx.annotation.VisibleForTesting; import androidx.lifecycle.ViewModel; import com.android.providers.media.ConfigStore; import com.android.providers.media.R; import com.android.providers.media.photopicker.data.CloudProviderInfo; import com.android.providers.media.photopicker.data.model.UserId; -import com.android.providers.media.photopicker.util.CloudProviderUtils; import java.util.ArrayList; import java.util.List; @@ -62,7 +60,9 @@ public class SettingsCloudMediaViewModel extends ViewModel { @Nullable private String mSelectedProviderAuthority; - public SettingsCloudMediaViewModel(@NonNull Context context, @NonNull UserId userId) { + public SettingsCloudMediaViewModel( + @NonNull Context context, + @NonNull UserId userId) { super(); mContext = requireNonNull(context); @@ -100,10 +100,21 @@ public class SettingsCloudMediaViewModel extends ViewModel { */ public boolean updateSelectedProvider(@NonNull String newPreferenceKey) { final String newCloudProvider = getProviderAuthority(newPreferenceKey); - final boolean success = persistSelectedProvider(newCloudProvider); - if (success) { - mSelectedProviderAuthority = newCloudProvider; - return true; + try (ContentProviderClient client = getContentProviderClient()) { + if (client == null) { + // This could happen when work profile is turned off after opening the Settings + // page. The work tab would still be visible but the MP process for work profile + // will not be running. + return false; + } + final boolean success = + persistSelectedProvider(client, newCloudProvider); + if (success) { + mSelectedProviderAuthority = newCloudProvider; + return true; + } + } catch (Exception e) { + Log.e(TAG, "Could not persist selected cloud provider", e); } return false; } @@ -128,14 +139,27 @@ public class SettingsCloudMediaViewModel extends ViewModel { } private void refreshSelectedProvider() { - mSelectedProviderAuthority = fetchCurrentProviderAuthority(); + try (ContentProviderClient client = getContentProviderClient()) { + if (client == null) { + // TODO(b/266927613): Handle the edge case where work profile is turned off + // while user is on the settings page but work tab's data is not fetched yet. + throw new IllegalArgumentException("Could not get selected cloud provider" + + " because Media Provider client is null."); + } + mSelectedProviderAuthority = + fetchProviderAuthority(client, /* default */ NONE_PREF_KEY); + } catch (Exception e) { + // Since displaying the current cloud provider is the core function of the Settings + // page, if we're not able to fetch this info, there is no point in displaying this + // activity. + throw new IllegalArgumentException("Could not get selected cloud provider", e); + } } @NonNull private List<CloudMediaProviderOption> fetchProviderOptions(@NonNull ConfigStore configStore) { // Get info of available cloud providers. - List<CloudProviderInfo> cloudProviders = - CloudProviderUtils.getAllAvailableCloudProviders( + List<CloudProviderInfo> cloudProviders = getAllAvailableCloudProviders( mContext, configStore, UserHandle.of(mUserId.getIdentifier())); return getProviderOptionsFromCloudProviderInfos(cloudProviders); @@ -157,55 +181,18 @@ public class SettingsCloudMediaViewModel extends ViewModel { @NonNull private CloudMediaProviderOption getNoneProviderOption() { - final Drawable nonePrefIcon = AppCompatResources - .getDrawable(this.mContext, R.drawable.ic_cloud_picker_off); - final String nonePrefLabel = this.mContext.getString(R.string.picker_settings_no_provider); + final Drawable nonePrefIcon = mContext.getDrawable(R.drawable.ic_cloud_picker_off); + final String nonePrefLabel = mContext.getString(R.string.picker_settings_no_provider); return new CloudMediaProviderOption(NONE_PREF_KEY, nonePrefLabel, nonePrefIcon); } @Nullable - private String fetchCurrentProviderAuthority() { - try (ContentProviderClient client = getContentProviderClient()) { - if (client == null) { - // TODO(b/266927613): Handle the edge case where work profile is turned off while - // user is on the settings page but work tab's data is not fetched yet. - throw new IllegalArgumentException("Could not get selected cloud provider because " - + "Media Provider client is null."); - } - final Bundle result = client.call(GET_CLOUD_PROVIDER_CALL, - /* arg */ null, /* extras */ null); - return result.getString(GET_CLOUD_PROVIDER_RESULT, NONE_PREF_KEY); - } catch (Exception e) { - // Since displaying the current cloud provider is the core function of the Settings - // page, if we're not able to fetch this info, there is no point in displaying this - // activity. - throw new IllegalArgumentException("Could not get selected cloud provider", e); - } - } - - private boolean persistSelectedProvider(@Nullable String newCloudProvider) { - try (ContentProviderClient client = getContentProviderClient()) { - if (client == null) { - // This could happen when work profile is turned off after opening the Settings - // page. The work tab would still be visible but the MP process for work profile - // will not be running. - return false; - } - final Bundle input = new Bundle(); - input.putString(EXTRA_CLOUD_PROVIDER, newCloudProvider); - client.call(SET_CLOUD_PROVIDER_CALL, /* arg */ null, /* extras */ input); - return true; - } catch (Exception e) { - Log.e(TAG, "Could not persist selected cloud provider", e); - return false; - } - } - - @Nullable - private ContentProviderClient getContentProviderClient() + @VisibleForTesting + public ContentProviderClient getContentProviderClient() throws PackageManager.NameNotFoundException { - return mUserId.getContentResolver(mContext) + return mUserId + .getContentResolver(mContext) .acquireUnstableContentProviderClient(AUTHORITY); } } diff --git a/src/com/android/providers/media/photopicker/ui/settings/SettingsProfileSelectFragment.java b/src/com/android/providers/media/photopicker/ui/settings/SettingsProfileSelectFragment.java index 9b10a1ed6..d49068437 100644 --- a/src/com/android/providers/media/photopicker/ui/settings/SettingsProfileSelectFragment.java +++ b/src/com/android/providers/media/photopicker/ui/settings/SettingsProfileSelectFragment.java @@ -81,8 +81,7 @@ public class SettingsProfileSelectFragment extends ProfileSelectFragment { final int previouslySelectedTab = mSettingsViewModel.getSelectedTab(); if (previouslySelectedTab != SettingsViewModel.TAB_NOT_SET) { // Selected tab state has previously been set in onPause() and we should restore it. - final TabLayout.Tab tab = mTabLayout.getTabAt(previouslySelectedTab); - tab.select(); + mTabLayout.getTabAt(previouslySelectedTab).select(); } } @@ -121,6 +120,7 @@ public class SettingsProfileSelectFragment extends ProfileSelectFragment { return fragment; } + @UserIdInt private int getTabUserId(int tabPosition) { final UserIdManager userIdManager = mSettingsViewModel.getUserIdManager(); diff --git a/src/com/android/providers/media/photopicker/util/CloudProviderUtils.java b/src/com/android/providers/media/photopicker/util/CloudProviderUtils.java index 4f5493e37..04c32165d 100644 --- a/src/com/android/providers/media/photopicker/util/CloudProviderUtils.java +++ b/src/com/android/providers/media/photopicker/util/CloudProviderUtils.java @@ -17,18 +17,26 @@ package com.android.providers.media.photopicker.util; import static android.provider.CloudMediaProviderContract.MANAGE_CLOUD_MEDIA_PROVIDERS_PERMISSION; +import static android.provider.MediaStore.EXTRA_CLOUD_PROVIDER; +import static android.provider.MediaStore.GET_CLOUD_PROVIDER_CALL; +import static android.provider.MediaStore.GET_CLOUD_PROVIDER_RESULT; +import static android.provider.MediaStore.SET_CLOUD_PROVIDER_CALL; +import android.content.ContentProviderClient; import android.content.Context; import android.content.Intent; import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; +import android.os.Bundle; import android.os.Process; +import android.os.RemoteException; import android.os.UserHandle; import android.provider.CloudMediaProvider; import android.provider.CloudMediaProviderContract; import android.util.Log; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.android.providers.media.ConfigStore; import com.android.providers.media.photopicker.data.CloudProviderInfo; @@ -140,4 +148,31 @@ public class CloudProviderUtils { return context.getPackageManager() .queryIntentContentProvidersAsUser(intent, 0, userHandle); } + + /** + * Request content provider to change cloud provider. + */ + public static boolean persistSelectedProvider( + @NonNull ContentProviderClient client, + @Nullable String newCloudProvider) throws RemoteException { + final Bundle input = new Bundle(); + input.putString(EXTRA_CLOUD_PROVIDER, newCloudProvider); + client.call(SET_CLOUD_PROVIDER_CALL, /* arg */ null, /* extras */ input); + return true; + } + + /** + * Fetch selected cloud provider from content provider. + * @param defaultAuthority is the default returned in case query result is null. + * @return fetched cloud provider authority if it is non-null. + * Otherwise return defaultAuthority. + */ + @Nullable + public static String fetchProviderAuthority( + @NonNull ContentProviderClient client, + @NonNull String defaultAuthority) throws RemoteException { + final Bundle result = client.call(GET_CLOUD_PROVIDER_CALL, /* arg */ null, + /* extras */ null); + return result.getString(GET_CLOUD_PROVIDER_RESULT, defaultAuthority); + } } diff --git a/tests/src/com/android/providers/media/photopicker/ui/settings/SettingsCloudMediaViewModelTest.java b/tests/src/com/android/providers/media/photopicker/ui/settings/SettingsCloudMediaViewModelTest.java new file mode 100644 index 000000000..4cdb5d2f0 --- /dev/null +++ b/tests/src/com/android/providers/media/photopicker/ui/settings/SettingsCloudMediaViewModelTest.java @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2023 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.ui.settings; + +import static android.provider.CloudMediaProviderContract.MANAGE_CLOUD_MEDIA_PROVIDERS_PERMISSION; +import static android.provider.MediaStore.GET_CLOUD_PROVIDER_CALL; +import static android.provider.MediaStore.GET_CLOUD_PROVIDER_RESULT; +import static android.provider.MediaStore.SET_CLOUD_PROVIDER_CALL; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.content.ContentProviderClient; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ProviderInfo; +import android.content.pm.ResolveInfo; +import android.content.res.Resources; +import android.os.Bundle; +import android.os.RemoteException; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.test.runner.AndroidJUnit4; + +import com.android.providers.media.ConfigStore; +import com.android.providers.media.photopicker.data.model.UserId; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.List; + +@RunWith(AndroidJUnit4.class) +public class SettingsCloudMediaViewModelTest { + private static final List<String> sProviderAuthorities = + List.of("cloud_provider_1", "cloud_provider_2"); + private static final List<ResolveInfo> sAvailableProviders = getAvailableProviders(); + + @Mock + private ConfigStore mConfigStore; + @Mock + private Context mContext; + @Mock + private Resources mResources; + @Mock + private PackageManager mPackageManager; + @Mock + private ContentProviderClient mContentProviderClient; + @NonNull + private SettingsCloudMediaViewModel mCloudMediaViewModel; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mCloudMediaViewModel = + Mockito.spy(new SettingsCloudMediaViewModel(mContext, UserId.CURRENT_USER)); + + doReturn(mPackageManager).when(mContext).getPackageManager(); + doReturn(mResources).when(mContext).getResources(); + doReturn(mContentProviderClient).when(mCloudMediaViewModel).getContentProviderClient(); + doAnswer(i -> { + ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.packageName = (String) i.getArgument(0); + applicationInfo.uid = 0; + return applicationInfo; + }).when(mPackageManager).getApplicationInfo(any(), anyInt()); + } + + @Test + public void testLoadDataWithMultipleProviders() throws RemoteException { + final String expectedCloudProvider = sProviderAuthorities.get(0); + setUpCurrentCloudProvider(expectedCloudProvider); + setUpAvailableCloudProviders(sAvailableProviders); + + mCloudMediaViewModel.loadData(mConfigStore); + + // Verify cloud provider options + final List<CloudMediaProviderOption> providerOptions = + mCloudMediaViewModel.getProviderOptions(); + assertThat(providerOptions.size()).isEqualTo(sProviderAuthorities.size() + 1); + for (int i = 0; i < sProviderAuthorities.size(); i++) { + assertThat(providerOptions.get(i).getKey()).isEqualTo(sProviderAuthorities.get(i)); + } + assertThat(providerOptions.get(providerOptions.size() - 1).getKey()) + .isEqualTo(SettingsCloudMediaViewModel.NONE_PREF_KEY); + + // Verify selected cloud provider + final String resultCloudProvider = + mCloudMediaViewModel.getSelectedProviderAuthority(); + assertThat(resultCloudProvider).isEqualTo(expectedCloudProvider); + } + + @Test + public void testLoadDataWithNoProvider() throws RemoteException { + final String expectedCloudProvider = SettingsCloudMediaViewModel.NONE_PREF_KEY; + setUpCurrentCloudProvider(expectedCloudProvider); + setUpAvailableCloudProviders(new ArrayList<>()); + mCloudMediaViewModel.loadData(mConfigStore); + + // Verify cloud provider options + final List<CloudMediaProviderOption> providerOptions = + mCloudMediaViewModel.getProviderOptions(); + assertThat(providerOptions.size()).isEqualTo(1); + assertThat(providerOptions.get(0).getKey()) + .isEqualTo(SettingsCloudMediaViewModel.NONE_PREF_KEY); + + // Verify selected cloud provider + final String resultCloudProvider = + mCloudMediaViewModel.getSelectedProviderAuthority(); + assertThat(resultCloudProvider).isEqualTo(expectedCloudProvider); + } + + @Test + public void testUpdateProvider() throws RemoteException { + final String expectedCloudProvider = sProviderAuthorities.get(0); + setUpCurrentCloudProvider(expectedCloudProvider); + setUpAvailableCloudProviders(sAvailableProviders); + + mCloudMediaViewModel.loadData(mConfigStore); + + // Verify selected cloud provider + final String resultCloudProvider = + mCloudMediaViewModel.getSelectedProviderAuthority(); + assertThat(resultCloudProvider).isEqualTo(expectedCloudProvider); + + // Update cloud provider + final String newCloudProvider = sProviderAuthorities.get(1); + final boolean success = mCloudMediaViewModel.updateSelectedProvider(newCloudProvider); + + // Verify selected cloud provider + assertThat(success).isTrue(); + final String resultNewCloudProvider = + mCloudMediaViewModel.getSelectedProviderAuthority(); + assertThat(resultNewCloudProvider).isEqualTo(newCloudProvider); + verify(mContentProviderClient, times(1)) + .call(eq(SET_CLOUD_PROVIDER_CALL), any(), any()); + } + + private void setUpAvailableCloudProviders(@NonNull List<ResolveInfo> availableProviders) { + doReturn(availableProviders).when(mPackageManager) + .queryIntentContentProvidersAsUser(any(), eq(0), any()); + } + + private void setUpCurrentCloudProvider(@Nullable String providerAuthority) + throws RemoteException { + final Bundle result = new Bundle(); + result.putString(GET_CLOUD_PROVIDER_RESULT, providerAuthority); + doReturn(result).when(mContentProviderClient) + .call(eq(GET_CLOUD_PROVIDER_CALL), any(), any()); + } + + @NonNull + private static List<ResolveInfo> getAvailableProviders() { + final List<ResolveInfo> availableProviders = new ArrayList<>(); + for (String authority : sProviderAuthorities) { + availableProviders.add(createResolveInfo(authority)); + } + return availableProviders; + } + + @NonNull + private static ResolveInfo createResolveInfo(@NonNull String authority) { + final ResolveInfo resolveInfo = new ResolveInfo(); + resolveInfo.providerInfo = createProviderInfo(authority); + return resolveInfo; + } + + @NonNull + private static ProviderInfo createProviderInfo(@NonNull String authority) { + final ProviderInfo providerInfo = new ProviderInfo(); + providerInfo.authority = authority; + providerInfo.readPermission = MANAGE_CLOUD_MEDIA_PROVIDERS_PERMISSION; + providerInfo.applicationInfo = createApplicationInfo(authority); + return providerInfo; + } + + @NonNull + private static ApplicationInfo createApplicationInfo(@NonNull String authority) { + final ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.packageName = authority; + applicationInfo.uid = 0; + return applicationInfo; + } +} |