Show "Files" category for public storage in Storage Settings

Users can click this preference to see public storage files
in external file browsers.

Bug: 170918505
Test: make RunSettingsRoboTests ROBOTEST_FILTER=StorageItemPreferenceControllerTest
      manual
      Select private storage and public storage and observe UI
Change-Id: If3f0d59b2ebf941d70e81e60c4c2015b80e7cf33
diff --git a/res/xml/storage_dashboard_fragment.xml b/res/xml/storage_dashboard_fragment.xml
index 076405f..83972e3 100644
--- a/res/xml/storage_dashboard_fragment.xml
+++ b/res/xml/storage_dashboard_fragment.xml
@@ -44,38 +44,51 @@
         android:title="@string/storage_free_up_space_title"
         android:summary="@string/storage_free_up_space_summary"/>
     <!-- Preference order 100~200 are 'ONLY' for storage category preferences below. -->
+    <Preference
+        android:key="pref_public_storage"
+        android:title="@string/storage_files"
+        android:icon="@drawable/ic_folder_vd_theme_24"
+        android:order="100"/>
     <com.android.settings.deviceinfo.StorageItemPreference
         android:key="pref_images"
         android:title="@string/storage_images"
-        android:icon="@drawable/ic_photo_library"/>
+        android:icon="@drawable/ic_photo_library"
+        android:order="101"/>
     <com.android.settings.deviceinfo.StorageItemPreference
         android:key="pref_videos"
         android:title="@string/storage_videos"
-        android:icon="@drawable/ic_local_movies"/>
+        android:icon="@drawable/ic_local_movies"
+        android:order="102"/>
     <com.android.settings.deviceinfo.StorageItemPreference
         android:key="pref_audios"
         android:title="@string/storage_audios"
-        android:icon="@drawable/ic_media_stream"/>
+        android:icon="@drawable/ic_media_stream"
+        android:order="103"/>
     <com.android.settings.deviceinfo.StorageItemPreference
         android:key="pref_apps"
         android:title="@string/storage_apps"
-        android:icon="@drawable/ic_storage_apps"/>
+        android:icon="@drawable/ic_storage_apps"
+        android:order="104"/>
     <com.android.settings.deviceinfo.StorageItemPreference
         android:key="pref_games"
         android:title="@string/storage_games"
-        android:icon="@drawable/ic_videogame_vd_theme_24"/>
+        android:icon="@drawable/ic_videogame_vd_theme_24"
+        android:order="105"/>
     <com.android.settings.deviceinfo.StorageItemPreference
         android:key="pref_documents_and_other"
         android:title="@string/storage_documents_and_other"
-        android:icon="@drawable/ic_folder_vd_theme_24"/>
+        android:icon="@drawable/ic_folder_vd_theme_24"
+        android:order="106"/>
     <com.android.settings.deviceinfo.StorageItemPreference
         android:key="pref_system"
         android:title="@string/storage_system"
-        android:icon="@drawable/ic_system_update"/>
+        android:icon="@drawable/ic_system_update"
+        android:order="107"/>
     <com.android.settings.deviceinfo.StorageItemPreference
         android:key="pref_trash"
         android:title="@string/storage_trash"
-        android:icon="@drawable/ic_trash_can"/>
+        android:icon="@drawable/ic_trash_can"
+        android:order="108"/>
     <!-- Preference order 100~200 are 'ONLY' for storage category preferences above. -->
     <PreferenceCategory
         android:key="pref_secondary_users"
diff --git a/src/com/android/settings/deviceinfo/StorageDashboardFragment.java b/src/com/android/settings/deviceinfo/StorageDashboardFragment.java
index b8c4e28..03a7b97 100644
--- a/src/com/android/settings/deviceinfo/StorageDashboardFragment.java
+++ b/src/com/android/settings/deviceinfo/StorageDashboardFragment.java
@@ -248,15 +248,19 @@
 
         mPreferenceController.setVolume(mSelectedStorageEntry.getVolumeInfo());
 
-        if (mSelectedStorageEntry.isPrivate() && mSelectedStorageEntry.isMounted()) {
+        if (!mSelectedStorageEntry.isMounted()) {
+            // Set null volume to hide category stats.
+            mPreferenceController.setVolume(null);
+            return;
+        }
+        if (mSelectedStorageEntry.isPrivate()) {
             // Stats data is only available on private volumes.
             getLoaderManager().restartLoader(STORAGE_JOB_ID, Bundle.EMPTY, this);
             getLoaderManager()
                  .restartLoader(VOLUME_SIZE_JOB_ID, Bundle.EMPTY, new VolumeSizeCallbacks());
             getLoaderManager().restartLoader(ICON_JOB_ID, Bundle.EMPTY, new IconLoaderCallbacks());
         } else {
-            // Set null volume to hide category stats.
-            mPreferenceController.setVolume(null);
+            mPreferenceController.setVolume(mSelectedStorageEntry.getVolumeInfo());
         }
     }
 
diff --git a/src/com/android/settings/deviceinfo/storage/StorageEntry.java b/src/com/android/settings/deviceinfo/storage/StorageEntry.java
index f718116..9ea0292 100644
--- a/src/com/android/settings/deviceinfo/storage/StorageEntry.java
+++ b/src/com/android/settings/deviceinfo/storage/StorageEntry.java
@@ -236,6 +236,14 @@
         return mVolumeInfo == null ? false : mVolumeInfo.getType() == VolumeInfo.TYPE_PUBLIC;
     }
 
+    /**
+     * Stub volume is a volume that is maintained by external party such as the ChromeOS processes
+     * in ARC++.
+     */
+    public boolean isStub() {
+        return mVolumeInfo == null ? false : mVolumeInfo.getType() == VolumeInfo.TYPE_STUB;
+    }
+
     /** Returns description. */
     public String getDescription() {
         if (isVolumeInfo()) {
diff --git a/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java b/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java
index 7d77687..e5259f9 100644
--- a/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java
+++ b/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java
@@ -70,6 +70,8 @@
     private static final String SYSTEM_FRAGMENT_TAG = "SystemInfo";
 
     @VisibleForTesting
+    static final String PUBLIC_STORAGE_KEY = "pref_public_storage";
+    @VisibleForTesting
     static final String IMAGES_KEY = "pref_images";
     @VisibleForTesting
     static final String VIDEOS_KEY = "pref_videos";
@@ -103,9 +105,11 @@
     private long mUsedBytes;
     private long mTotalSize;
 
-    private List<StorageItemPreference> mStorageItemPreferences;
+    private List<StorageItemPreference> mPrivateStorageItemPreferences;
     private PreferenceScreen mScreen;
     @VisibleForTesting
+    Preference mPublicStoragePreference;
+    @VisibleForTesting
     StorageItemPreference mImagesPreference;
     @VisibleForTesting
     StorageItemPreference mVideosPreference;
@@ -167,6 +171,9 @@
             return false;
         }
         switch (preference.getKey()) {
+            case PUBLIC_STORAGE_KEY:
+                launchPublicStorageIntent();
+                return true;
             case IMAGES_KEY:
                 launchImagesIntent();
                 return true;
@@ -210,37 +217,45 @@
     public void setVolume(VolumeInfo volume) {
         mVolume = volume;
 
-        final boolean isValidVolume = isValidVolume();
-        setCategoryPreferencesVisibility(isValidVolume);
-        if (isValidVolume) {
-            updateCategoryPreferencesOrder();
-        }
+        updateCategoryPreferencesVisibility();
+        updatePrivateStorageCategoryPreferencesOrder();
     }
 
     // Stats data is only available on private volumes.
-    private boolean isValidVolume() {
+    private boolean isValidPrivateVolume() {
         return mVolume != null
                 && mVolume.getType() == VolumeInfo.TYPE_PRIVATE
                 && (mVolume.getState() == VolumeInfo.STATE_MOUNTED
                 || mVolume.getState() == VolumeInfo.STATE_MOUNTED_READ_ONLY);
     }
 
-    private void setCategoryPreferencesVisibility(boolean visible) {
+    private boolean isValidPublicVolume() {
+        return mVolume != null
+                && (mVolume.getType() == VolumeInfo.TYPE_PUBLIC
+                || mVolume.getType() == VolumeInfo.TYPE_STUB)
+                && (mVolume.getState() == VolumeInfo.STATE_MOUNTED
+                || mVolume.getState() == VolumeInfo.STATE_MOUNTED_READ_ONLY);
+    }
+
+    private void updateCategoryPreferencesVisibility() {
         if (mScreen == null) {
             return;
         }
 
-        mImagesPreference.setVisible(visible);
-        mVideosPreference.setVisible(visible);
-        mAudiosPreference.setVisible(visible);
-        mAppsPreference.setVisible(visible);
-        mGamesPreference.setVisible(visible);
-        mDocumentsAndOtherPreference.setVisible(visible);
-        mSystemPreference.setVisible(visible);
+        mPublicStoragePreference.setVisible(isValidPublicVolume());
+
+        final boolean privateStoragePreferencesVisible = isValidPrivateVolume();
+        mImagesPreference.setVisible(privateStoragePreferencesVisible);
+        mVideosPreference.setVisible(privateStoragePreferencesVisible);
+        mAudiosPreference.setVisible(privateStoragePreferencesVisible);
+        mAppsPreference.setVisible(privateStoragePreferencesVisible);
+        mGamesPreference.setVisible(privateStoragePreferencesVisible);
+        mDocumentsAndOtherPreference.setVisible(privateStoragePreferencesVisible);
+        mSystemPreference.setVisible(privateStoragePreferencesVisible);
         // TODO(b/170918505): Shows trash category after trash category feature complete.
         mTrashPreference.setVisible(false);
 
-        if (visible) {
+        if (privateStoragePreferencesVisible) {
             final VolumeInfo sharedVolume = mSvp.findEmulatedForPrivate(mVolume);
             // If we don't have a shared volume for our internal storage (or the shared volume isn't
             // mounted as readable for whatever reason), we should hide the File preference.
@@ -250,22 +265,22 @@
         }
     }
 
-    private void updateCategoryPreferencesOrder() {
-        if (mScreen == null) {
+    private void updatePrivateStorageCategoryPreferencesOrder() {
+        if (mScreen == null || !isValidPrivateVolume()) {
             return;
         }
 
-        if (mStorageItemPreferences == null) {
-            mStorageItemPreferences = new ArrayList<>();
+        if (mPrivateStorageItemPreferences == null) {
+            mPrivateStorageItemPreferences = new ArrayList<>();
 
-            mStorageItemPreferences.add(mImagesPreference);
-            mStorageItemPreferences.add(mVideosPreference);
-            mStorageItemPreferences.add(mAudiosPreference);
-            mStorageItemPreferences.add(mAppsPreference);
-            mStorageItemPreferences.add(mGamesPreference);
-            mStorageItemPreferences.add(mDocumentsAndOtherPreference);
-            mStorageItemPreferences.add(mSystemPreference);
-            mStorageItemPreferences.add(mTrashPreference);
+            mPrivateStorageItemPreferences.add(mImagesPreference);
+            mPrivateStorageItemPreferences.add(mVideosPreference);
+            mPrivateStorageItemPreferences.add(mAudiosPreference);
+            mPrivateStorageItemPreferences.add(mAppsPreference);
+            mPrivateStorageItemPreferences.add(mGamesPreference);
+            mPrivateStorageItemPreferences.add(mDocumentsAndOtherPreference);
+            mPrivateStorageItemPreferences.add(mSystemPreference);
+            mPrivateStorageItemPreferences.add(mTrashPreference);
         }
         mScreen.removePreference(mImagesPreference);
         mScreen.removePreference(mVideosPreference);
@@ -277,10 +292,10 @@
         mScreen.removePreference(mTrashPreference);
 
         // Sort display order by size.
-        Collections.sort(mStorageItemPreferences,
+        Collections.sort(mPrivateStorageItemPreferences,
                 Comparator.comparingLong(StorageItemPreference::getStorageSize));
         int orderIndex = LAST_STORAGE_CATEGORY_PREFERENCE_ORDER;
-        for (StorageItemPreference preference : mStorageItemPreferences) {
+        for (StorageItemPreference preference : mPrivateStorageItemPreferences) {
             preference.setOrder(orderIndex--);
             mScreen.addPreference(preference);
         }
@@ -292,6 +307,7 @@
     public void setUserId(UserHandle userHandle) {
         mUserId = userHandle.getIdentifier();
 
+        tintPreference(mPublicStoragePreference);
         tintPreference(mImagesPreference);
         tintPreference(mVideosPreference);
         tintPreference(mAudiosPreference);
@@ -320,6 +336,7 @@
     @Override
     public void displayPreference(PreferenceScreen screen) {
         mScreen = screen;
+        mPublicStoragePreference = screen.findPreference(PUBLIC_STORAGE_KEY);
         mImagesPreference = screen.findPreference(IMAGES_KEY);
         mVideosPreference = screen.findPreference(VIDEOS_KEY);
         mAudiosPreference = screen.findPreference(AUDIOS_KEY);
@@ -329,11 +346,8 @@
         mSystemPreference = screen.findPreference(SYSTEM_KEY);
         mTrashPreference = screen.findPreference(TRASH_KEY);
 
-        final boolean isValidVolume = isValidVolume();
-        setCategoryPreferencesVisibility(isValidVolume);
-        if (isValidVolume) {
-            updateCategoryPreferencesOrder();
-        }
+        updateCategoryPreferencesVisibility();
+        updatePrivateStorageCategoryPreferencesOrder();
     }
 
     public void onLoadFinished(SparseArray<StorageAsyncLoader.AppsStorageResult> result,
@@ -371,7 +385,7 @@
             mSystemPreference.setStorageSize(systemSize, mTotalSize);
         }
 
-        updateCategoryPreferencesOrder();
+        updatePrivateStorageCategoryPreferencesOrder();
     }
 
     public void setUsedSize(long usedSizeBytes) {
@@ -382,6 +396,13 @@
         mTotalSize = totalSizeBytes;
     }
 
+    private void launchPublicStorageIntent() {
+        final Intent intent = mVolume.buildBrowseIntent();
+        if (intent != null) {
+            mContext.startActivity(intent);
+        }
+    }
+
     // TODO(b/183078080): To simplify StorageItemPreferenceController, move launchxxxIntent to a
     //                    utility object.
     private void launchImagesIntent() {
diff --git a/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceControllerTest.java
index 6f9c451..b7bf7f5 100644
--- a/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceControllerTest.java
@@ -104,6 +104,8 @@
     }
 
     private PreferenceScreen getPreferenceScreen() {
+        final StorageItemPreference publicStorage = spy(new StorageItemPreference(mContext));
+        publicStorage.setIcon(R.drawable.ic_folder_vd_theme_24);
         final StorageItemPreference images = spy(new StorageItemPreference(mContext));
         images.setIcon(R.drawable.ic_photo_library);
         final StorageItemPreference videos = spy(new StorageItemPreference(mContext));
@@ -122,6 +124,8 @@
         trash.setIcon(R.drawable.ic_trash_can);
 
         final PreferenceScreen screen = mock(PreferenceScreen.class);
+        when(screen.findPreference(eq(StorageItemPreferenceController.PUBLIC_STORAGE_KEY)))
+                .thenReturn(publicStorage);
         when(screen.findPreference(eq(StorageItemPreferenceController.IMAGES_KEY)))
                 .thenReturn(images);
         when(screen.findPreference(eq(StorageItemPreferenceController.VIDEOS_KEY)))
@@ -149,6 +153,24 @@
     }
 
     @Test
+    public void launchPublicStorageIntent_nonNullBrowseIntent_settingsIntent() {
+        final String fakeBrowseAction = "FAKE_BROWSE_ACTION";
+        final Intent fakeBrowseIntent = new Intent(fakeBrowseAction);
+        // mContext is not the activity, add FLAG_ACTIVITY_NEW_TASK to avoid AndroidRuntimeException
+        // during this test.
+        fakeBrowseIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        when(mVolume.buildBrowseIntent()).thenReturn(fakeBrowseIntent);
+        mPreference.setKey(StorageItemPreferenceController.PUBLIC_STORAGE_KEY);
+        mController.handlePreferenceTreeClick(mPreference);
+
+        final ArgumentCaptor<Intent> argumentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext).startActivity(argumentCaptor.capture());
+
+        final Intent intent = argumentCaptor.getValue();
+        assertThat(intent.getAction()).isEqualTo(fakeBrowseAction);
+    }
+
+    @Test
     public void launchImagesIntent_resolveActionViewNull_settingsIntent() {
         mPreference.setKey(StorageItemPreferenceController.IMAGES_KEY);
         mController.handlePreferenceTreeClick(mPreference);
@@ -191,6 +213,7 @@
 
         mController.setVolume(null);
 
+        assertThat(mController.mPublicStoragePreference.isVisible()).isFalse();
         assertThat(mController.mImagesPreference.isVisible()).isFalse();
         assertThat(mController.mVideosPreference.isVisible()).isFalse();
         assertThat(mController.mAudiosPreference.isVisible()).isFalse();
@@ -347,6 +370,7 @@
 
         mController.setUserId(new UserHandle(10));
 
+        verify(mController.mPublicStoragePreference, times(2)).setIcon(nullable(Drawable.class));
         verify(mController.mImagesPreference, times(2)).setIcon(nullable(Drawable.class));
         verify(mController.mVideosPreference, times(2)).setIcon(nullable(Drawable.class));
         verify(mController.mAudiosPreference, times(2)).setIcon(nullable(Drawable.class));
@@ -418,4 +442,26 @@
 
         assertThat(mController.mDocumentsAndOtherPreference.isVisible()).isTrue();
     }
+
+    @Test
+    public void setVolume_publicStorage_showFilePreference() {
+        // This will hide it initially.
+        mController.displayPreference(mPreferenceScreen);
+        when(mVolume.getType()).thenReturn(VolumeInfo.TYPE_PUBLIC);
+        when(mVolume.getState()).thenReturn(VolumeInfo.STATE_MOUNTED);
+        when(mVolume.isMountedReadable()).thenReturn(true);
+
+        // And we bring it back.
+        mController.setVolume(mVolume);
+
+        assertThat(mController.mPublicStoragePreference.isVisible()).isTrue();
+        assertThat(mController.mImagesPreference.isVisible()).isFalse();
+        assertThat(mController.mVideosPreference.isVisible()).isFalse();
+        assertThat(mController.mAudiosPreference.isVisible()).isFalse();
+        assertThat(mController.mAppsPreference.isVisible()).isFalse();
+        assertThat(mController.mGamesPreference.isVisible()).isFalse();
+        assertThat(mController.mDocumentsAndOtherPreference.isVisible()).isFalse();
+        assertThat(mController.mSystemPreference.isVisible()).isFalse();
+        assertThat(mController.mTrashPreference.isVisible()).isFalse();
+    }
 }
diff --git a/tests/unit/src/com/android/settings/deviceinfo/storage/StorageEntryTest.java b/tests/unit/src/com/android/settings/deviceinfo/storage/StorageEntryTest.java
index cf1b6b2..3cd3539 100644
--- a/tests/unit/src/com/android/settings/deviceinfo/storage/StorageEntryTest.java
+++ b/tests/unit/src/com/android/settings/deviceinfo/storage/StorageEntryTest.java
@@ -209,6 +209,24 @@
     }
 
     @Test
+    public void isPublic_prublicVolume_shouldReturnTrue() {
+        final VolumeInfo publicVolumeInfo = mock(VolumeInfo.class);
+        final StorageEntry publicStorage = new StorageEntry(mContext, publicVolumeInfo);
+        when(publicVolumeInfo.getType()).thenReturn(VolumeInfo.TYPE_PUBLIC);
+
+        assertThat(publicStorage.isPublic()).isTrue();
+    }
+
+    @Test
+    public void isStub_stubVolume_shouldReturnTrue() {
+        final VolumeInfo stubVolumeInfo = mock(VolumeInfo.class);
+        final StorageEntry stubStorage = new StorageEntry(mContext, stubVolumeInfo);
+        when(stubVolumeInfo.getType()).thenReturn(VolumeInfo.TYPE_STUB);
+
+        assertThat(stubStorage.isStub()).isTrue();
+    }
+
+    @Test
     public void isPrivate_nonVolumeInfo_shouldReturnFalse() {
         final DiskInfo diskInfo = mock(DiskInfo.class);
         final StorageEntry diskStorage = new StorageEntry(diskInfo);