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()) {