Add a music apps view to the Storage Settings.

When you tap the Music & Audio preference, it takes you a
hybridized applications view. In this view, there also is
a line item which defines the amount of storage used by
audio files on the device. Using the new storage query, we
can add this information very quickly to the view.

Bug: 33199077
Test: Settings robo tests
Change-Id: If51cba0b3de20805543a39049367eb13613081e7
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 4b93d8c..1b0c368 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -8146,6 +8146,11 @@
     <!-- Title of games app storage screen [CHAR LIMIT=30] -->
     <string name="game_storage_settings">Games</string>
 
+    <!-- Title for audio files preference. [CHAR LIMIT=50] -->
+    <string name="audio_files_title">Audio files</string>
+    <!-- Title for the Audio storage view. [CHAR LIMIT=50] -->
+    <string name="audio_storage_title">Music</string>
+
     <!-- UI webview setting: WebView uninstalled-for-user explanatory text [CHAR LIMIT=30] -->
     <string name="webview_uninstalled_for_user">Uninstalled for user <xliff:g id="user" example="John Doe">%s</xliff:g>\n</string>
     <!-- UI webview setting: WebView disabled-for-user explanatory text [CHAR LIMIT=30] -->
diff --git a/src/com/android/settings/Utils.java b/src/com/android/settings/Utils.java
index 248358c..f5bd2ce 100644
--- a/src/com/android/settings/Utils.java
+++ b/src/com/android/settings/Utils.java
@@ -25,6 +25,7 @@
 import android.app.IActivityManager;
 import android.app.KeyguardManager;
 import android.app.admin.DevicePolicyManager;
+import android.content.ActivityNotFoundException;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -1224,4 +1225,24 @@
             return null;
         }
     }
+
+    /**
+     * Launches an intent which may optionally have a user id defined.
+     * @param fragment Fragment to use to launch the activity.
+     * @param intent Intent to launch.
+     */
+    public static void launchIntent(Fragment fragment, Intent intent) {
+        try {
+            final int userId = intent.getIntExtra(Intent.EXTRA_USER_ID, -1);
+
+            if (userId == -1) {
+                fragment.startActivity(intent);
+            } else {
+                fragment.getActivity().startActivityAsUser(intent, new UserHandle(userId));
+            }
+        } catch (ActivityNotFoundException e) {
+            Log.w(TAG, "No activity found for " + intent);
+        }
+    }
+
 }
diff --git a/src/com/android/settings/applications/FileViewHolderController.java b/src/com/android/settings/applications/FileViewHolderController.java
new file mode 100644
index 0000000..e8af722
--- /dev/null
+++ b/src/com/android/settings/applications/FileViewHolderController.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2016 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.settings.applications;
+
+import android.app.Fragment;
+import android.view.View;
+
+/**
+ * FileViewHolderController handles adapting the AppViewHolder to work as a general purpose
+ * storage categorization preference in the ManageApplications view.
+ */
+public interface FileViewHolderController {
+    /**
+     * Begins a synchronous query for statistics for the files.
+     */
+    void queryStats();
+
+    /**
+     * Returns if the preference should be shown.
+     */
+    boolean shouldShow();
+
+    /**
+     * Initializes the view within an AppViewHolder.
+     * @param holder The holder to use to initialize.
+     */
+    void setupView(AppViewHolder holder);
+
+    /**
+     * Handles the behavior when the view is clicked.
+     * @param fragment Fragment where the click originated.
+     */
+    void onClick(Fragment fragment);
+}
diff --git a/src/com/android/settings/applications/ManageApplications.java b/src/com/android/settings/applications/ManageApplications.java
index f6b303c..443ce34 100644
--- a/src/com/android/settings/applications/ManageApplications.java
+++ b/src/com/android/settings/applications/ManageApplications.java
@@ -17,6 +17,7 @@
 package com.android.settings.applications;
 
 import android.app.Activity;
+import android.app.usage.StorageStatsManager;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
@@ -32,6 +33,7 @@
 import android.os.UserManager;
 import android.preference.PreferenceFrameLayout;
 import android.text.TextUtils;
+import android.text.format.Formatter;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.Menu;
@@ -71,6 +73,7 @@
 import com.android.settings.applications.AppStateUsageBridge.UsageState;
 import com.android.settings.core.InstrumentedPreferenceFragment;
 import com.android.settings.dashboard.SummaryLoader;
+import com.android.settings.deviceinfo.storage.StorageStatsSource;
 import com.android.settings.fuelgauge.HighPowerDetail;
 import com.android.settings.fuelgauge.PowerWhitelistBackend;
 import com.android.settings.notification.AppNotificationSettings;
@@ -108,6 +111,7 @@
     // Used for storage only.
     public static final String EXTRA_VOLUME_UUID = "volumeUuid";
     public static final String EXTRA_VOLUME_NAME = "volumeName";
+    public static final String EXTRA_STORAGE_TYPE = "storageType";
 
     private static final String EXTRA_SORT_ORDER = "sortOrder";
     private static final String EXTRA_SHOW_SYSTEM = "showSystem";
@@ -140,6 +144,10 @@
     public static final int FILTER_APPS_WRITE_SETTINGS = 10;
     public static final int FILTER_APPS_INSTALL_SOURCES = 12;
 
+    // Storage types. Used to determine what the extra item in the list of preferences is.
+    public static final int STORAGE_TYPE_DEFAULT = 0;
+    public static final int STORAGE_TYPE_MUSIC = 1;
+
     // This is the string labels for the filter modes above, the order must be kept in sync.
     public static final int[] FILTER_LABELS = new int[]{
             R.string.high_power_filter_on, // High power whitelist, on
@@ -227,6 +235,7 @@
     private ResetAppsHelper mResetAppsHelper;
     private String mVolumeUuid;
     private String mVolumeName;
+    private int mStorageType;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
@@ -250,6 +259,7 @@
             if (args != null && args.containsKey(EXTRA_VOLUME_UUID)) {
                 mVolumeUuid = args.getString(EXTRA_VOLUME_UUID);
                 mVolumeName = args.getString(EXTRA_VOLUME_NAME);
+                mStorageType = args.getInt(EXTRA_STORAGE_TYPE, STORAGE_TYPE_DEFAULT);
                 mListType = LIST_TYPE_STORAGE;
             } else {
                 // No volume selected, display a normal list, sorted by size.
@@ -316,6 +326,13 @@
                 mApplications.mHasReceivedBridgeCallback =
                         savedInstanceState.getBoolean(EXTRA_HAS_BRIDGE, false);
             }
+            if (mStorageType == STORAGE_TYPE_MUSIC) {
+                Context context = getContext();
+                mApplications.setExtraViewController(new MusicViewHolderController(
+                        context,
+                        new StorageStatsSource(context),
+                        mVolumeUuid));
+            }
             mListView.setAdapter(mApplications);
             mListView.setRecyclerListener(mApplications);
             mListView.setFastScrollEnabled(isFastScrollEnabled());
@@ -361,7 +378,11 @@
             mFilterAdapter.enableFilter(FILTER_APPS_POWER_WHITELIST_ALL);
         }
         if (mListType == LIST_TYPE_STORAGE) {
-            mApplications.setOverrideFilter(new VolumeFilter(mVolumeUuid));
+            AppFilter filter = new VolumeFilter(mVolumeUuid);
+            if (mStorageType == STORAGE_TYPE_MUSIC) {
+                filter = new CompoundFilter(ApplicationsState.FILTER_AUDIO, filter);
+            }
+            mApplications.setOverrideFilter(filter);
         }
         if (mListType == LIST_TYPE_GAMES) {
             mApplications.setOverrideFilter(ApplicationsState.FILTER_GAMES);
@@ -626,11 +647,17 @@
 
     @Override
     public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
-        if (mApplications != null && mApplications.getCount() > position) {
+        if (mApplications == null) {
+            return;
+        }
+
+        if (mApplications.getApplicationCount() > position) {
             ApplicationsState.AppEntry entry = mApplications.getAppEntry(position);
             mCurrentPkgName = entry.info.packageName;
             mCurrentUid = entry.info.uid;
             startApplicationDetailsActivity();
+        } else {
+            mApplications.mExtraViewController.onClick(this);
         }
     }
 
@@ -769,6 +796,7 @@
         private AppFilter mOverrideFilter;
         private boolean mHasReceivedLoadEntries;
         private boolean mHasReceivedBridgeCallback;
+        private FileViewHolderController mExtraViewController;
 
         // These two variables are used to remember and restore the last scroll position when this
         // fragment is paused. We need this special handling because app entries are added gradually
@@ -839,6 +867,10 @@
             rebuild(true);
         }
 
+        public void setExtraViewController(FileViewHolderController extraViewController) {
+            mExtraViewController = extraViewController;
+        }
+
         public void resume(int sort) {
             if (DEBUG) Log.i(TAG, "Resume!  mResumed=" + mResumed);
             if (!mResumed) {
@@ -889,7 +921,6 @@
                 // Don't rebuild the list until all the app entries are loaded.
                 return;
             }
-            if (DEBUG) Log.i(TAG, "Rebuilding app list...");
             ApplicationsState.AppFilter filterObj;
             Comparator<AppEntry> comparatorObj;
             boolean emulated = Environment.isExternalStorageEmulated();
@@ -924,8 +955,12 @@
                     comparatorObj = ApplicationsState.ALPHA_COMPARATOR;
                     break;
             }
-            filterObj = new CompoundFilter(filterObj, ApplicationsState.FILTER_NOT_HIDE);
 
+            if (mExtraViewController != null) {
+                mExtraViewController.queryStats();
+            }
+
+            filterObj = new CompoundFilter(filterObj, ApplicationsState.FILTER_NOT_HIDE);
             AppFilter finalFilterObj = filterObj;
             mBgHandler.post(() -> {
                 final ArrayList<AppEntry> entries = mSession.rebuild(finalFilterObj,
@@ -1104,6 +1139,10 @@
         public void onPackageSizeChanged(String packageName) {
             for (int i = 0; i < mActive.size(); i++) {
                 AppViewHolder holder = (AppViewHolder) mActive.get(i).getTag();
+                ApplicationInfo info = holder.entry.info;
+                if (info == null) {
+                    continue;
+                }
                 if (holder.entry.info.packageName.equals(packageName)) {
                     synchronized (holder.entry) {
                         updateSummary(holder);
@@ -1136,10 +1175,22 @@
         }
 
         public int getCount() {
+            if (mEntries == null) {
+                return 0;
+            }
+            int extraViewAddition =
+                    (mExtraViewController != null && mExtraViewController.shouldShow()) ? 1 : 0;
+            return mEntries.size() + extraViewAddition;
+        }
+
+        public int getApplicationCount() {
             return mEntries != null ? mEntries.size() : 0;
         }
 
         public Object getItem(int position) {
+            if (position == mEntries.size()) {
+                return mExtraViewController;
+            }
             return mEntries.get(position);
         }
 
@@ -1148,6 +1199,9 @@
         }
 
         public long getItemId(int position) {
+            if (position == mEntries.size()) {
+                return -1;
+            }
             return mEntries.get(position).id;
         }
 
@@ -1158,6 +1212,11 @@
 
         @Override
         public boolean isEnabled(int position) {
+            if (position == mEntries.size() && mExtraViewController != null &&
+                    mExtraViewController.shouldShow()) {
+                return true;
+            }
+
             if (mManageApplications.mListType != LIST_TYPE_HIGH_POWER) {
                 return true;
             }
@@ -1172,31 +1231,38 @@
                     convertView);
             convertView = holder.rootView;
 
-            // Bind the data efficiently with the holder
-            ApplicationsState.AppEntry entry = mEntries.get(position);
-            synchronized (entry) {
-                holder.entry = entry;
-                if (entry.label != null) {
-                    holder.appName.setText(entry.label);
+            // Handle the extra view if it is the last entry.
+            if (mEntries != null && mExtraViewController != null && position == mEntries.size()) {
+                mExtraViewController.setupView(holder);
+                convertView.setEnabled(true);
+            } else {
+                // Bind the data efficiently with the holder
+                ApplicationsState.AppEntry entry = mEntries.get(position);
+                synchronized (entry) {
+                    holder.entry = entry;
+                    if (entry.label != null) {
+                        holder.appName.setText(entry.label);
+                    }
+                    mState.ensureIcon(entry);
+                    if (entry.icon != null) {
+                        holder.appIcon.setImageDrawable(entry.icon);
+                    }
+                    updateSummary(holder);
+                    if ((entry.info.flags & ApplicationInfo.FLAG_INSTALLED) == 0) {
+                        holder.disabled.setVisibility(View.VISIBLE);
+                        holder.disabled.setText(R.string.not_installed);
+                    } else if (!entry.info.enabled) {
+                        holder.disabled.setVisibility(View.VISIBLE);
+                        holder.disabled.setText(R.string.disabled);
+                    } else {
+                        holder.disabled.setVisibility(View.GONE);
+                    }
                 }
-                mState.ensureIcon(entry);
-                if (entry.icon != null) {
-                    holder.appIcon.setImageDrawable(entry.icon);
-                }
-                updateSummary(holder);
-                if ((entry.info.flags & ApplicationInfo.FLAG_INSTALLED) == 0) {
-                    holder.disabled.setVisibility(View.VISIBLE);
-                    holder.disabled.setText(R.string.not_installed);
-                } else if (!entry.info.enabled) {
-                    holder.disabled.setVisibility(View.VISIBLE);
-                    holder.disabled.setText(R.string.disabled);
-                } else {
-                    holder.disabled.setVisibility(View.GONE);
-                }
+                convertView.setEnabled(isEnabled(position));
             }
+
             mActive.remove(convertView);
             mActive.add(convertView);
-            convertView.setEnabled(isEnabled(position));
             return convertView;
         }
 
diff --git a/src/com/android/settings/applications/MusicViewHolderController.java b/src/com/android/settings/applications/MusicViewHolderController.java
new file mode 100644
index 0000000..18c87db
--- /dev/null
+++ b/src/com/android/settings/applications/MusicViewHolderController.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2016 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.settings.applications;
+
+import android.app.Fragment;
+import android.content.Context;
+import android.content.Intent;
+import android.os.UserHandle;
+import android.provider.DocumentsContract;
+import android.support.annotation.WorkerThread;
+import android.text.format.Formatter;
+
+import com.android.settings.R;
+import com.android.settings.Utils;
+import com.android.settings.deviceinfo.storage.StorageStatsSource;
+
+/**
+ * MusicViewHolderController controls an Audio/Music file view in the ManageApplications view.
+ */
+public class MusicViewHolderController implements FileViewHolderController {
+    private static final String AUTHORITY_MEDIA = "com.android.providers.media.documents";
+
+    private Context mContext;
+    private StorageStatsSource mSource;
+    private String mVolumeUuid;
+    private long mMusicSize;
+
+    public MusicViewHolderController(
+            Context context, StorageStatsSource source, String volumeUuid) {
+        mContext = context;
+        mSource = source;
+        mVolumeUuid = volumeUuid;
+    }
+
+    @Override
+    @WorkerThread
+    public void queryStats() {
+        mMusicSize = mSource.getExternalStorageStats(mVolumeUuid, UserHandle.CURRENT).audioBytes;
+    }
+
+    @Override
+    public boolean shouldShow() {
+        return true;
+    }
+
+    @Override
+    public void setupView(AppViewHolder holder) {
+        holder.appIcon.setImageDrawable(mContext.getDrawable(R.drawable.empty_icon));
+        holder.appName.setText(mContext.getText(R.string.audio_files_title));
+        holder.summary.setText(Formatter.formatFileSize(mContext, mMusicSize));
+    }
+
+    @Override
+    public void onClick(Fragment fragment) {
+        Intent intent = new Intent(DocumentsContract.ACTION_BROWSE);
+        intent.setData(DocumentsContract.buildRootUri(AUTHORITY_MEDIA, "audio_root"));
+        intent.addCategory(Intent.CATEGORY_DEFAULT);
+        intent.putExtra(Intent.EXTRA_USER_ID, UserHandle.CURRENT);
+        Utils.launchIntent(fragment, intent);
+    }
+}
diff --git a/src/com/android/settings/deviceinfo/PrivateVolumeSettings.java b/src/com/android/settings/deviceinfo/PrivateVolumeSettings.java
index b0a9902..ce26c0f 100644
--- a/src/com/android/settings/deviceinfo/PrivateVolumeSettings.java
+++ b/src/com/android/settings/deviceinfo/PrivateVolumeSettings.java
@@ -533,7 +533,7 @@
         if (intent != null) {
             intent.putExtra(Intent.EXTRA_USER_ID, userId);
 
-            launchIntent(this, intent);
+            Utils.launchIntent(this, intent);
             return true;
         }
         return super.onPreferenceTreeClick(pref);
@@ -670,20 +670,6 @@
         return total;
     }
 
-    private static void launchIntent(Fragment fragment, Intent intent) {
-        try {
-            final int userId = intent.getIntExtra(Intent.EXTRA_USER_ID, -1);
-
-            if (userId == -1) {
-                fragment.startActivity(intent);
-            } else {
-                fragment.getActivity().startActivityAsUser(intent, new UserHandle(userId));
-            }
-        } catch (ActivityNotFoundException e) {
-            Log.w(TAG, "No activity found for " + intent);
-        }
-    }
-
     private final StorageEventListener mStorageListener = new StorageEventListener() {
         @Override
         public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
@@ -815,7 +801,7 @@
                     new DialogInterface.OnClickListener() {
                         @Override
                         public void onClick(DialogInterface dialog, int which) {
-                            launchIntent(OtherInfoFragment.this, intent);
+                            Utils.launchIntent(OtherInfoFragment.this, intent);
                         }
                     });
             builder.setNegativeButton(android.R.string.cancel, null);
diff --git a/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java b/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java
index 22f9c4c..16dcd18 100644
--- a/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java
+++ b/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java
@@ -243,10 +243,15 @@
     }
 
     private Intent getAudioIntent() {
-        Intent intent = new Intent(DocumentsContract.ACTION_BROWSE);
-        intent.setData(DocumentsContract.buildRootUri(AUTHORITY_MEDIA, "audio_root"));
-        intent.addCategory(Intent.CATEGORY_DEFAULT);
-        return intent;
+        Bundle args = new Bundle();
+        args.putString(ManageApplications.EXTRA_CLASSNAME,
+                Settings.StorageUseActivity.class.getName());
+        args.putString(ManageApplications.EXTRA_VOLUME_UUID, mVolume.getFsUuid());
+        args.putString(ManageApplications.EXTRA_VOLUME_NAME, mVolume.getDescription());
+        args.putInt(ManageApplications.EXTRA_STORAGE_TYPE, ManageApplications.STORAGE_TYPE_MUSIC);
+        return Utils.onBuildStartFragmentIntent(mContext,
+                ManageApplications.class.getName(), args, null, R.string.audio_storage_title, null,
+                false);
     }
 
     private Intent getAppsIntent() {
diff --git a/src/com/android/settings/deviceinfo/storage/StorageStatsSource.java b/src/com/android/settings/deviceinfo/storage/StorageStatsSource.java
new file mode 100644
index 0000000..b6e03fb
--- /dev/null
+++ b/src/com/android/settings/deviceinfo/storage/StorageStatsSource.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2017 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.settings.deviceinfo.storage;
+
+import android.app.usage.StorageStatsManager;
+import android.content.Context;
+import android.os.UserHandle;
+
+/**
+ * StorageStatsSource wraps the StorageStatsManager for testability purposes.
+ */
+public class StorageStatsSource {
+    private StorageStatsManager mSsm;
+
+    public StorageStatsSource(Context context) {
+        mSsm = context.getSystemService(StorageStatsManager.class);
+    }
+
+    public ExternalStorageStats getExternalStorageStats(String volumeUuid, UserHandle user) {
+        return new ExternalStorageStats(mSsm.queryExternalStatsForUser(volumeUuid, user));
+    }
+
+    public static class ExternalStorageStats {
+        public long totalBytes;
+        public long audioBytes;
+        public long videoBytes;
+        public long imageBytes;
+
+        public ExternalStorageStats(long totalBytes, long audioBytes, long videoBytes,
+                long imageBytes) {
+            this.totalBytes = totalBytes;
+            this.audioBytes = audioBytes;
+            this.videoBytes = videoBytes;
+            this.imageBytes = imageBytes;
+        }
+
+        public ExternalStorageStats(android.app.usage.ExternalStorageStats stats) {
+            totalBytes = stats.getTotalBytes();
+            audioBytes = stats.getAudioBytes();
+            videoBytes = stats.getVideoBytes();
+            imageBytes = stats.getImageBytes();
+        }
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/applications/MusicViewHolderControllerTest.java b/tests/robotests/src/com/android/settings/applications/MusicViewHolderControllerTest.java
new file mode 100644
index 0000000..2a2de66
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/applications/MusicViewHolderControllerTest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2017 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.settings.applications;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Fragment;
+import android.content.Context;
+import android.content.Intent;
+import android.os.UserHandle;
+import android.os.storage.VolumeInfo;
+import android.provider.DocumentsContract;
+import android.view.LayoutInflater;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.deviceinfo.storage.StorageStatsSource;
+import com.android.settingslib.deviceinfo.StorageVolumeProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class MusicViewHolderControllerTest {
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Fragment mFragment;
+    @Mock
+    private StorageVolumeProvider mSvp;
+    @Mock
+    private StorageStatsSource mSource;
+
+    private Context mContext;
+    private MusicViewHolderController mController;
+    private VolumeInfo mVolume;
+    private AppViewHolder mHolder;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+        mVolume = new VolumeInfo("id", 0, null, "id");
+        mController = new MusicViewHolderController(mContext, mSource, mVolume.fsUuid);
+
+        LayoutInflater inflater = LayoutInflater.from(mContext);
+        mHolder = AppViewHolder.createOrRecycle(inflater, null);
+    }
+
+    @Test
+    public void storageShouldBeZeroBytesIfQueriedBeforeStorageQueryFinishes() {
+        mController.setupView(mHolder);
+
+        assertThat(mHolder.summary.getText().toString()).isEqualTo("0.00B");
+    }
+
+    @Test
+    public void storageShouldRepresentStorageStatsQuery() {
+        when(mSource.getExternalStorageStats(any(String.class), any(UserHandle.class))).thenReturn(
+                new StorageStatsSource.ExternalStorageStats(1, 1, 0, 0));
+
+        mController.queryStats();
+        mController.setupView(mHolder);
+
+        assertThat(mHolder.summary.getText().toString()).isEqualTo("1.00B");
+    }
+
+    @Test
+    public void clickingShouldIntentIntoFilesApp() {
+        mController.onClick(mFragment);
+
+        final ArgumentCaptor<Intent> argumentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mFragment).startActivity(argumentCaptor.capture());
+        Intent intent = argumentCaptor.getValue();
+
+        assertThat(intent.getAction()).isEqualTo(DocumentsContract.ACTION_BROWSE);
+        assertThat(intent.getData()).isEqualTo(DocumentsContract.buildRootUri(
+                "com.android.providers.media.documents",
+                "audio_root"));
+    }
+}
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 a377505..f7baba3 100644
--- a/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceControllerTest.java
@@ -35,6 +35,7 @@
 import android.widget.LinearLayout;
 
 import com.android.settings.R;
+import com.android.settings.Settings;
 import com.android.settings.SettingsActivity;
 import com.android.settings.SettingsRobolectricTestRunner;
 import com.android.settings.SubSettings;
@@ -118,12 +119,15 @@
         final ArgumentCaptor<Intent> argumentCaptor = ArgumentCaptor.forClass(Intent.class);
         verify(mFragment.getActivity()).startActivityAsUser(argumentCaptor.capture(),
                 any(UserHandle.class));
-
         Intent intent = argumentCaptor.getValue();
-        assertThat(intent.getAction()).isEqualTo(DocumentsContract.ACTION_BROWSE);
-        assertThat(intent.getData()).isEqualTo(DocumentsContract.buildRootUri(
-                "com.android.providers.media.documents",
-                "audio_root"));
+
+        assertThat(intent.getAction()).isEqualTo(Intent.ACTION_MAIN);
+        assertThat(intent.getComponent().getClassName()).isEqualTo(SubSettings.class.getName());
+        assertThat(intent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT)).isEqualTo(
+                ManageApplications.class.getName());
+        assertThat(intent.getBundleExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS).getInt(
+                ManageApplications.EXTRA_STORAGE_TYPE, 0)).isEqualTo(
+                ManageApplications.STORAGE_TYPE_MUSIC);
     }
 
     @Test
diff --git a/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageSummaryDonutPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageSummaryDonutPreferenceControllerTest.java
index fa0851e..990223a 100644
--- a/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageSummaryDonutPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageSummaryDonutPreferenceControllerTest.java
@@ -61,7 +61,6 @@
         final View view =
                 inflater.inflate(
                         mPreference.getLayoutResource(), new LinearLayout(mContext), false);
-
         mHolder = new PreferenceViewHolder(view);
     }