Merge "Add an option to open the PhotoPicker in the specified tab" into udc-mainline-prod
diff --git a/apex/framework/api/current.txt b/apex/framework/api/current.txt
index cddfcfa..d760d14 100644
--- a/apex/framework/api/current.txt
+++ b/apex/framework/api/current.txt
@@ -151,6 +151,7 @@
field public static final String EXTRA_MEDIA_TITLE = "android.intent.extra.title";
field public static final String EXTRA_OUTPUT = "output";
field @FlaggedApi("com.android.providers.media.flags.pick_ordered_images") public static final String EXTRA_PICK_IMAGES_IN_ORDER = "android.provider.extra.PICK_IMAGES_IN_ORDER";
+ field @FlaggedApi("com.android.providers.media.flags.picker_default_tab") public static final String EXTRA_PICK_IMAGES_LAUNCH_TAB = "android.provider.extra.PICK_IMAGES_LAUNCH_TAB";
field public static final String EXTRA_PICK_IMAGES_MAX = "android.provider.extra.PICK_IMAGES_MAX";
field public static final String EXTRA_SCREEN_ORIENTATION = "android.intent.extra.screenOrientation";
field public static final String EXTRA_SHOW_ACTION_ICONS = "android.intent.extra.showActionIcons";
@@ -172,6 +173,8 @@
field public static final String MEDIA_SCANNER_VOLUME = "volume";
field public static final String META_DATA_REVIEW_GALLERY_PREWARM_SERVICE = "android.media.review_gallery_prewarm_service";
field public static final String META_DATA_STILL_IMAGE_CAMERA_PREWARM_SERVICE = "android.media.still_image_camera_preview_service";
+ field @FlaggedApi("com.android.providers.media.flags.picker_default_tab") public static final int PICK_IMAGES_TAB_ALBUMS = 0; // 0x0
+ field @FlaggedApi("com.android.providers.media.flags.picker_default_tab") public static final int PICK_IMAGES_TAB_IMAGES = 1; // 0x1
field public static final String QUERY_ARG_INCLUDE_RECENTLY_UNMOUNTED_VOLUMES = "android:query-arg-recently-unmounted-volumes";
field public static final String QUERY_ARG_MATCH_FAVORITE = "android:query-arg-match-favorite";
field public static final String QUERY_ARG_MATCH_PENDING = "android:query-arg-match-pending";
diff --git a/apex/framework/java/android/provider/MediaStore.java b/apex/framework/java/android/provider/MediaStore.java
index b1172af..d232fd3 100644
--- a/apex/framework/java/android/provider/MediaStore.java
+++ b/apex/framework/java/android/provider/MediaStore.java
@@ -872,6 +872,41 @@
}
/**
+ * The name of an optional intent-extra used to allow apps to specify the tab the picker should
+ * open with. The extra can only be specified in {@link MediaStore#ACTION_PICK_IMAGES}.
+ * <p>
+ * The value of this intent-extra must be one of: {@link MediaStore#PICK_IMAGES_TAB_ALBUMS}
+ * for the albums tab and {@link MediaStore#PICK_IMAGES_TAB_IMAGES} for the photos tab.
+ * The system will decide which tab to open by default and in most cases,
+ * it is {@link MediaStore#PICK_IMAGES_TAB_IMAGES} i.e. the photos tab.
+ */
+ @FlaggedApi("com.android.providers.media.flags.picker_default_tab")
+ public static final String EXTRA_PICK_IMAGES_LAUNCH_TAB =
+ "android.provider.extra.PICK_IMAGES_LAUNCH_TAB";
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "PICK_IMAGES_TAB_" }, value = {
+ PICK_IMAGES_TAB_ALBUMS,
+ PICK_IMAGES_TAB_IMAGES
+ })
+ public @interface PickImagesTab { }
+
+ /**
+ * One of the permitted values for {@link MediaStore#EXTRA_PICK_IMAGES_LAUNCH_TAB} to open the
+ * picker with albums tab.
+ */
+ @FlaggedApi("com.android.providers.media.flags.picker_default_tab")
+ public static final int PICK_IMAGES_TAB_ALBUMS = 0;
+
+ /**
+ * One of the permitted values for {@link MediaStore#EXTRA_PICK_IMAGES_LAUNCH_TAB} to open the
+ * picker with photos tab.
+ */
+ @FlaggedApi("com.android.providers.media.flags.picker_default_tab")
+ public static final int PICK_IMAGES_TAB_IMAGES = 1;
+
+ /**
* Specify that the caller wants to receive the original media format without transcoding.
*
* <b>Caution: using this flag can cause app
diff --git a/src/com/android/providers/media/photopicker/PhotoPickerActivity.java b/src/com/android/providers/media/photopicker/PhotoPickerActivity.java
index c742cf4..f5f6916 100644
--- a/src/com/android/providers/media/photopicker/PhotoPickerActivity.java
+++ b/src/com/android/providers/media/photopicker/PhotoPickerActivity.java
@@ -132,6 +132,9 @@
private int mToolbarHeight = 0;
private boolean mShouldLogCancelledResult = true;
+ private AccessibilityManager mAccessibilityManager;
+ private boolean mIsAccessibilityEnabled;
+
@Override
public void onCreate(Bundle savedInstanceState) {
// This is required as GET_CONTENT with type "*/*" is also received by PhotoPicker due
@@ -185,6 +188,9 @@
mTabLayout = findViewById(R.id.tab_layout);
+ mAccessibilityManager = getSystemService(AccessibilityManager.class);
+ mIsAccessibilityEnabled = mAccessibilityManager.isEnabled();
+
initBottomSheetBehavior();
// Save the fragment container layout so that we can adjust the padding based on preview or
@@ -201,6 +207,7 @@
observeRefreshUiNotificationLiveData();
// Restore state operation should always be kept at the end of this method.
restoreState(savedInstanceState);
+
// Call this after state is restored, to use the correct LOGGER_INSTANCE_ID_ARG
if (savedInstanceState == null) {
final String intentAction = intent != null ? intent.getAction() : null;
@@ -501,7 +508,7 @@
*/
@VisibleForTesting
protected boolean isAccessibilityEnabled() {
- return getSystemService(AccessibilityManager.class).isEnabled();
+ return mIsAccessibilityEnabled;
}
private static int getBottomSheetPeekHeight(Context context) {
diff --git a/src/com/android/providers/media/photopicker/ui/TabContainerFragment.java b/src/com/android/providers/media/photopicker/ui/TabContainerFragment.java
index 8e070b5..8f06fb5 100644
--- a/src/com/android/providers/media/photopicker/ui/TabContainerFragment.java
+++ b/src/com/android/providers/media/photopicker/ui/TabContainerFragment.java
@@ -18,6 +18,7 @@
import static com.android.providers.media.util.MimeUtils.isVideoMimeType;
import android.os.Bundle;
+import android.provider.MediaStore;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -68,11 +69,17 @@
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
- mTabContainerAdapter = new TabContainerAdapter(/* fragment */ this);
mViewPager = view.findViewById(R.id.picker_tab_viewpager);
- mViewPager.setAdapter(mTabContainerAdapter);
final ViewModelProvider viewModelProvider = new ViewModelProvider(requireActivity());
mPickerViewModel = viewModelProvider.get(PickerViewModel.class);
+ mTabContainerAdapter = new TabContainerAdapter(/* fragment */ this);
+ mViewPager.setAdapter(mTabContainerAdapter);
+
+ // Launch in albums tab if the app requests so
+ if (mPickerViewModel.getPickerLaunchTab() == MediaStore.PICK_IMAGES_TAB_ALBUMS) {
+ // Launch the picker in Albums tab without any switch animation
+ mViewPager.setCurrentItem(ALBUMS_TAB_POSITION, /* smoothScroll */ false);
+ }
// If the ViewPager2 has more than one page with BottomSheetBehavior, the scrolled view
// (e.g. RecyclerView) on the second page can't be scrolled. The workaround is to update
diff --git a/src/com/android/providers/media/photopicker/viewmodel/PickerViewModel.java b/src/com/android/providers/media/photopicker/viewmodel/PickerViewModel.java
index ff5e5c0..7208675 100644
--- a/src/com/android/providers/media/photopicker/viewmodel/PickerViewModel.java
+++ b/src/com/android/providers/media/photopicker/viewmodel/PickerViewModel.java
@@ -105,7 +105,6 @@
public static final String TAG = "PhotoPicker";
private static final int RECENT_MINIMUM_COUNT = 12;
-
private static final int INSTANCE_ID_MAX = 1 << 15;
private static final int DELAY_MILLIS = 0;
@@ -122,6 +121,8 @@
private final MuteStatus mMuteStatus;
public boolean mEmptyPageDisplayed = false;
+ @MediaStore.PickImagesTab
+ private int mPickerLaunchTab = MediaStore.PICK_IMAGES_TAB_IMAGES;
// TODO(b/193857982): We keep these four data sets now, we may need to find a way to reduce the
// data set to reduce memories.
@@ -227,6 +228,14 @@
}
}
+ public int getPickerLaunchTab() {
+ return mPickerLaunchTab;
+ }
+
+ public void setPickerLaunchTab(int launchTab) {
+ mPickerLaunchTab = launchTab;
+ }
+
@VisibleForTesting
protected void initConfigStore() {
mConfigStore = MediaApplication.getConfigStore();
@@ -863,6 +872,23 @@
* Parse values from {@code intent} and set corresponding fields
*/
public void parseValuesFromIntent(Intent intent) throws IllegalArgumentException {
+ final Bundle extras = intent.getExtras();
+ if (extras != null && extras.containsKey(MediaStore.EXTRA_PICK_IMAGES_LAUNCH_TAB)) {
+ if (intent.getAction().equals(ACTION_GET_CONTENT)) {
+ Log.e(TAG, "EXTRA_PICKER_LAUNCH_TAB cannot be passed as an extra in "
+ + "ACTION_GET_CONTENT");
+ } else if (intent.getAction().equals(MediaStore.ACTION_USER_SELECT_IMAGES_FOR_APP)) {
+ throw new IllegalArgumentException("EXTRA_PICKER_LAUNCH_TAB cannot be passed as an "
+ + "extra in ACTION_USER_SELECT_IMAGES_FOR_APP");
+ } else {
+ mPickerLaunchTab = extras.getInt(MediaStore.EXTRA_PICK_IMAGES_LAUNCH_TAB);
+ if (!checkPickerLaunchOptionValidity(mPickerLaunchTab)) {
+ throw new IllegalArgumentException("Incorrect value " + mPickerLaunchTab
+ + " received for the intent extra: "
+ + MediaStore.EXTRA_PICK_IMAGES_LAUNCH_TAB);
+ }
+ }
+ }
mUserIdManager.setIntentAndCheckRestrictions(intent);
mMimeTypeFilters = MimeFilterUtils.getMimeTypeFilters(intent);
@@ -899,6 +925,11 @@
}
}
+ private boolean checkPickerLaunchOptionValidity(int launchOption) {
+ return launchOption == MediaStore.PICK_IMAGES_TAB_IMAGES
+ || launchOption == MediaStore.PICK_IMAGES_TAB_ALBUMS;
+ }
+
private void initBannerManager() {
mBannerManager = shouldShowOnlyLocalFeatures()
? new BannerManager(mAppContext, mUserIdManager, mConfigStore)
diff --git a/tests/src/com/android/providers/media/photopicker/viewmodel/PickerViewModelTest.java b/tests/src/com/android/providers/media/photopicker/viewmodel/PickerViewModelTest.java
index 4ca8dd9..f9f9351 100644
--- a/tests/src/com/android/providers/media/photopicker/viewmodel/PickerViewModelTest.java
+++ b/tests/src/com/android/providers/media/photopicker/viewmodel/PickerViewModelTest.java
@@ -598,6 +598,53 @@
}
@Test
+ public void testParseValuesFromPickImagesIntent_launchPickerInPhotosTab() {
+ final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+ intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_LAUNCH_TAB, MediaStore.PICK_IMAGES_TAB_IMAGES);
+
+ mPickerViewModel.parseValuesFromIntent(intent);
+
+ assertThat(mPickerViewModel.getPickerLaunchTab()).isEqualTo(
+ MediaStore.PICK_IMAGES_TAB_IMAGES);
+ }
+
+ @Test
+ public void testParseValuesFromPickImagesIntent_launchPickerInAlbumsTab() {
+ final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+ intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_LAUNCH_TAB, MediaStore.PICK_IMAGES_TAB_ALBUMS);
+
+ mPickerViewModel.parseValuesFromIntent(intent);
+
+ assertThat(mPickerViewModel.getPickerLaunchTab()).isEqualTo(
+ MediaStore.PICK_IMAGES_TAB_ALBUMS);
+ }
+
+ @Test
+ public void testParseValuesFromPickImagesIntent_launchPickerWithIncorrectTabOption() {
+ final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+ intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_LAUNCH_TAB, 2);
+
+ try {
+ mPickerViewModel.parseValuesFromIntent(intent);
+ fail("Incorrect value passed for the picker launch tab option in the intent");
+ } catch (IllegalArgumentException expected) {
+ // Expected
+ }
+ }
+
+ @Test
+ public void testParseValuesFromGetContentIntent_extraPickerLaunchTab() {
+ final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+ intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_LAUNCH_TAB, MediaStore.PICK_IMAGES_TAB_ALBUMS);
+
+ mPickerViewModel.parseValuesFromIntent(intent);
+
+ // GET_CONTENT doesn't support this option. Launch tab will always default to photos
+ assertThat(mPickerViewModel.getPickerLaunchTab()).isEqualTo(
+ MediaStore.PICK_IMAGES_TAB_IMAGES);
+ }
+
+ @Test
public void testShouldShowOnlyLocalFeatures() {
mConfigStore.enableCloudMediaFeature();
diff --git a/tools/photopicker/res/layout/activity_main.xml b/tools/photopicker/res/layout/activity_main.xml
index 6348a4e..d5db47d 100644
--- a/tools/photopicker/res/layout/activity_main.xml
+++ b/tools/photopicker/res/layout/activity_main.xml
@@ -100,6 +100,7 @@
android:textSize="16sp" />
</LinearLayout>
+
<CheckBox
android:id="@+id/cbx_ordered_selection"
android:layout_width="wrap_content"
@@ -107,6 +108,37 @@
android:text="ORDERED SELECTION"
android:textSize="16sp" />
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <CheckBox
+ android:id="@+id/cbx_set_picker_launch_tab"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/picker_launch_tab_option"
+ android:textSize="16sp" />
+
+ <RadioGroup
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <RadioButton android:id="@+id/rb_albums"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Albums"
+ android:enabled="false"/>
+ <RadioButton android:id="@+id/rb_photos"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Photos"
+ android:enabled="false"/>
+ </RadioGroup>
+
+ </LinearLayout>
+
+
<Button
android:id="@+id/launch_button"
android:layout_width="match_parent"
diff --git a/tools/photopicker/res/values/strings.xml b/tools/photopicker/res/values/strings.xml
new file mode 100644
index 0000000..b6b6131
--- /dev/null
+++ b/tools/photopicker/res/values/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<resources>
+ <!-- Picker launch tab checkbox label -->
+ <string name="picker_launch_tab_option">SET PICKER LAUNCH TAB</string>
+</resources>
\ No newline at end of file
diff --git a/tools/photopicker/src/com/android/providers/media/tools/photopicker/PhotoPickerToolActivity.java b/tools/photopicker/src/com/android/providers/media/tools/photopicker/PhotoPickerToolActivity.java
index 1d549f3..98e58c0 100644
--- a/tools/photopicker/src/com/android/providers/media/tools/photopicker/PhotoPickerToolActivity.java
+++ b/tools/photopicker/src/com/android/providers/media/tools/photopicker/PhotoPickerToolActivity.java
@@ -34,6 +34,7 @@
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
+import android.widget.RadioButton;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.VideoView;
@@ -48,6 +49,8 @@
private static final String TAG = "PhotoPickerToolActivity";
private static final String EXTRA_PICK_IMAGES_MAX = "android.provider.extra.PICK_IMAGES_MAX";
private static final String ACTION_PICK_IMAGES = "android.provider.action.PICK_IMAGES";
+ private static final String EXTRA_PICK_IMAGES_LAUNCH_TAB =
+ "android.provider.extra.PICK_IMAGES_LAUNCH_TAB";
private static final int PICK_IMAGES_MAX_LIMIT = 100;
private static final int REQUEST_CODE = 42;
@@ -63,10 +66,17 @@
private CheckBox mSetSelectionCountCheckBox;
private CheckBox mAllowMultipleCheckBox;
private CheckBox mGetContentCheckBox;
+
private CheckBox mOrderedSelectionCheckBox;
+
+ private CheckBox mPickerLaunchTabCheckBox;
+
private EditText mMaxCountText;
private EditText mMimeTypeText;
+ private RadioButton mAlbumsRadioButton;
+ private RadioButton mPhotosRadioButton;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -82,12 +92,17 @@
mMaxCountText = findViewById(R.id.edittext_max_count);
mMimeTypeText = findViewById(R.id.edittext_mime_type);
mScrollView = findViewById(R.id.scrollview);
+ mPickerLaunchTabCheckBox = findViewById(R.id.cbx_set_picker_launch_tab);
+ mAlbumsRadioButton = findViewById(R.id.rb_albums);
+ mPhotosRadioButton = findViewById(R.id.rb_photos);
mSetImageOnlyCheckBox.setOnCheckedChangeListener(this::onShowImageOnlyCheckedChanged);
mSetVideoOnlyCheckBox.setOnCheckedChangeListener(this::onShowVideoOnlyCheckedChanged);
mSetMimeTypeCheckBox.setOnCheckedChangeListener(this::onSetMimeTypeCheckedChanged);
mSetSelectionCountCheckBox.setOnCheckedChangeListener(
this::onSetSelectionCountCheckedChanged);
+ mPickerLaunchTabCheckBox.setOnCheckedChangeListener(
+ this::onSetPickerLaunchTabCheckedChanged);
mMaxCountText.addTextChangedListener(new TextWatcher() {
@Override
@@ -157,6 +172,11 @@
mMaxCountText.setEnabled(isChecked);
}
+ private void onSetPickerLaunchTabCheckedChanged(View view, boolean isChecked) {
+ mAlbumsRadioButton.setEnabled(isChecked);
+ mPhotosRadioButton.setEnabled(isChecked);
+ }
+
private void onLaunchButtonClicked(View view) {
final Intent intent;
if (mGetContentCheckBox.isChecked()) {
@@ -164,6 +184,16 @@
intent.setType("*/*");
} else {
intent = new Intent(ACTION_PICK_IMAGES);
+ // This extra is not permitted in GET_CONTENT
+ if (mPickerLaunchTabCheckBox.isChecked()) {
+ int launchTab;
+ if (mAlbumsRadioButton.isChecked()) {
+ launchTab = 0;
+ } else {
+ launchTab = 1;
+ }
+ intent.putExtra(EXTRA_PICK_IMAGES_LAUNCH_TAB, launchTab);
+ }
}
if (mAllowMultipleCheckBox.isChecked()) {