Merge "Add storage slice in Contextual Settings Homepage"
diff --git a/src/com/android/settings/deviceinfo/storage/StorageSummaryDonutPreferenceController.java b/src/com/android/settings/deviceinfo/storage/StorageSummaryDonutPreferenceController.java
index 1a5152e..1dd3d98 100644
--- a/src/com/android/settings/deviceinfo/storage/StorageSummaryDonutPreferenceController.java
+++ b/src/com/android/settings/deviceinfo/storage/StorageSummaryDonutPreferenceController.java
@@ -43,6 +43,20 @@
         super(context);
     }
 
+    /**
+     * Converts a used storage amount to a formatted text.
+     *
+     * @param context Context
+     * @param usedBytes used bytes of storage
+     * @return a formatted text.
+     */
+    public static CharSequence convertUsedBytesToFormattedText(Context context, long usedBytes) {
+        final Formatter.BytesResult result = Formatter.formatBytes(context.getResources(),
+                usedBytes, 0);
+        return TextUtils.expandTemplate(context.getText(R.string.storage_size_large_alternate),
+                result.value, result.units);
+    }
+
     @Override
     public void displayPreference(PreferenceScreen screen) {
         mSummary = (StorageSummaryDonutPreference) screen.findPreference("pref_summary");
@@ -53,11 +67,7 @@
     public void updateState(Preference preference) {
         super.updateState(preference);
         StorageSummaryDonutPreference summary = (StorageSummaryDonutPreference) preference;
-        final Formatter.BytesResult result = Formatter.formatBytes(mContext.getResources(),
-                mUsedBytes, 0);
-        summary.setTitle(TextUtils.expandTemplate(
-                mContext.getText(R.string.storage_size_large_alternate), result.value,
-                result.units));
+        summary.setTitle(convertUsedBytesToFormattedText(mContext, mUsedBytes));
         summary.setSummary(mContext.getString(R.string.storage_volume_total,
                 Formatter.formatShortFileSize(mContext, mTotalBytes)));
         summary.setPercent(mUsedBytes, mTotalBytes);
@@ -83,6 +93,7 @@
 
     /**
      * Updates the state of the donut preference for the next update.
+     *
      * @param used Total number of used bytes on the summarized volume.
      * @param total Total number of bytes on the summarized volume.
      */
@@ -94,6 +105,7 @@
 
     /**
      * Updates the state of the donut preference for the next update using volume to summarize.
+     *
      * @param volume VolumeInfo to use to populate the informayion.
      */
     public void updateSizes(StorageVolumeProvider svp, VolumeInfo volume) {
diff --git a/src/com/android/settings/homepage/CardContentLoader.java b/src/com/android/settings/homepage/CardContentLoader.java
index 9980503..b584084 100644
--- a/src/com/android/settings/homepage/CardContentLoader.java
+++ b/src/com/android/settings/homepage/CardContentLoader.java
@@ -26,6 +26,7 @@
 
 import com.android.settings.homepage.deviceinfo.DataUsageSlice;
 import com.android.settings.homepage.deviceinfo.DeviceInfoSlice;
+import com.android.settings.homepage.deviceinfo.StorageSlice;
 import com.android.settingslib.utils.AsyncLoaderCompat;
 
 import java.util.ArrayList;
@@ -112,6 +113,15 @@
                     .setCardType(ContextualCard.CardType.SLICE)
                     .setIsHalfWidth(true)
                     .build());
+            add(new ContextualCard.Builder()
+                    .setSliceUri(StorageSlice.STORAGE_CARD_URI.toString())
+                    .setName(StorageSlice.PATH_STORAGE_CARD)
+                    .setPackageName(packageName)
+                    .setRankingScore(rankingScore)
+                    .setAppVersion(appVersionCode)
+                    .setCardType(ContextualCard.CardType.SLICE)
+                    .setIsHalfWidth(true)
+                    .build());
         }};
         return result;
     }
diff --git a/src/com/android/settings/homepage/deviceinfo/StorageSlice.java b/src/com/android/settings/homepage/deviceinfo/StorageSlice.java
new file mode 100644
index 0000000..c9464e4
--- /dev/null
+++ b/src/com/android/settings/homepage/deviceinfo/StorageSlice.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2018 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.homepage.deviceinfo;
+
+import android.app.PendingIntent;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.storage.StorageManager;
+import android.text.format.Formatter;
+
+import androidx.core.graphics.drawable.IconCompat;
+import androidx.slice.Slice;
+import androidx.slice.builders.ListBuilder;
+import androidx.slice.builders.SliceAction;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settings.R;
+import com.android.settings.SubSettings;
+import com.android.settings.Utils;
+import com.android.settings.deviceinfo.StorageDashboardFragment;
+import com.android.settings.deviceinfo.storage.StorageSummaryDonutPreferenceController;
+import com.android.settings.slices.CustomSliceable;
+import com.android.settings.slices.SettingsSliceProvider;
+import com.android.settings.slices.SliceBuilderUtils;
+import com.android.settingslib.deviceinfo.PrivateStorageInfo;
+import com.android.settingslib.deviceinfo.StorageManagerVolumeProvider;
+
+public class StorageSlice implements CustomSliceable {
+    private static final String TAG = "StorageSlice";
+
+    /**
+     * The path denotes the unique name of storage slicel
+     */
+    public static final String PATH_STORAGE_CARD = "storage_card";
+
+    /**
+     * Backing Uri for the storage slice.
+     */
+    public static final Uri STORAGE_CARD_URI = new Uri.Builder()
+            .scheme(ContentResolver.SCHEME_CONTENT)
+            .authority(SettingsSliceProvider.SLICE_AUTHORITY)
+            .appendPath(PATH_STORAGE_CARD)
+            .build();
+
+    private final Context mContext;
+
+    public StorageSlice(Context context) {
+        mContext = context;
+    }
+
+    @Override
+    public Uri getUri() {
+        return STORAGE_CARD_URI;
+    }
+
+    /**
+     * Return a storage slice bound to {@link #STORAGE_CARD_URI}
+     */
+    @Override
+    public Slice getSlice() {
+        final IconCompat icon = IconCompat.createWithResource(mContext,
+                R.drawable.ic_homepage_storage);
+        final String title = mContext.getString(R.string.storage_label);
+        final SliceAction primaryAction = new SliceAction(getPrimaryAction(), icon, title);
+        final PrivateStorageInfo info = getPrivateStorageInfo();
+        return new ListBuilder(mContext, STORAGE_CARD_URI, ListBuilder.INFINITY)
+                .setAccentColor(Utils.getColorAccentDefaultColor(mContext))
+                .setHeader(new ListBuilder.HeaderBuilder().setTitle(title))
+                .addRow(new ListBuilder.RowBuilder()
+                        .setTitle(getStorageUsedText(info))
+                        .setSubtitle(getStorageSummaryText(info))
+                        .setPrimaryAction(primaryAction))
+                .build();
+    }
+
+    @Override
+    public Intent getIntent() {
+        final String screenTitle = mContext.getText(R.string.storage_label).toString();
+        final Uri contentUri = new Uri.Builder().appendPath(PATH_STORAGE_CARD).build();
+        return SliceBuilderUtils.buildSearchResultPageIntent(mContext,
+                StorageDashboardFragment.class.getName(), PATH_STORAGE_CARD, screenTitle,
+                MetricsProto.MetricsEvent.SLICE)
+                .setClassName(mContext.getPackageName(), SubSettings.class.getName())
+                .setData(contentUri);
+    }
+
+    private PendingIntent getPrimaryAction() {
+        final Intent intent = getIntent();
+        return PendingIntent.getActivity(mContext, 0  /* requestCode */, intent, 0  /* flags */);
+    }
+
+    @VisibleForTesting
+    PrivateStorageInfo getPrivateStorageInfo() {
+        final StorageManager storageManager = mContext.getSystemService(StorageManager.class);
+        final StorageManagerVolumeProvider smvp = new StorageManagerVolumeProvider(storageManager);
+        return PrivateStorageInfo.getPrivateStorageInfo(smvp);
+    }
+
+    @VisibleForTesting
+    CharSequence getStorageUsedText(PrivateStorageInfo info) {
+        final long usedBytes = info.totalBytes - info.freeBytes;
+        return StorageSummaryDonutPreferenceController.convertUsedBytesToFormattedText(mContext,
+                usedBytes);
+    }
+
+    @VisibleForTesting
+    CharSequence getStorageSummaryText(PrivateStorageInfo info) {
+        return mContext.getString(R.string.storage_volume_total,
+                Formatter.formatShortFileSize(mContext, info.totalBytes));
+    }
+
+    @Override
+    public void onNotifyChange(Intent intent) {
+
+    }
+}
diff --git a/src/com/android/settings/slices/CustomSliceManager.java b/src/com/android/settings/slices/CustomSliceManager.java
index 3d708c7..8fa2fb6 100644
--- a/src/com/android/settings/slices/CustomSliceManager.java
+++ b/src/com/android/settings/slices/CustomSliceManager.java
@@ -22,6 +22,7 @@
 
 import com.android.settings.homepage.deviceinfo.DataUsageSlice;
 import com.android.settings.homepage.deviceinfo.DeviceInfoSlice;
+import com.android.settings.homepage.deviceinfo.StorageSlice;
 import com.android.settings.wifi.WifiSlice;
 
 import java.util.Map;
@@ -91,5 +92,6 @@
         mUriMap.put(WifiSlice.WIFI_URI, WifiSlice.class);
         mUriMap.put(DataUsageSlice.DATA_USAGE_CARD_URI, DataUsageSlice.class);
         mUriMap.put(DeviceInfoSlice.DEVICE_INFO_CARD_URI, DeviceInfoSlice.class);
+        mUriMap.put(StorageSlice.STORAGE_CARD_URI, StorageSlice.class);
     }
 }
\ No newline at end of file
diff --git a/tests/robotests/src/com/android/settings/homepage/deviceinfo/StorageSliceTest.java b/tests/robotests/src/com/android/settings/homepage/deviceinfo/StorageSliceTest.java
new file mode 100644
index 0000000..5609a7a
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/homepage/deviceinfo/StorageSliceTest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2018 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.homepage.deviceinfo;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+
+import android.content.Context;
+import android.content.res.Resources;
+
+import androidx.core.graphics.drawable.IconCompat;
+import androidx.slice.Slice;
+import androidx.slice.SliceItem;
+import androidx.slice.SliceMetadata;
+import androidx.slice.SliceProvider;
+import androidx.slice.core.SliceAction;
+import androidx.slice.widget.SliceLiveData;
+
+import com.android.settings.R;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+import com.android.settings.testutils.SliceTester;
+import com.android.settingslib.deviceinfo.PrivateStorageInfo;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.List;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+public class StorageSliceTest {
+    private static final String USED_BYTES_TEXT = "test used bytes";
+    private static final String SUMMARY_TEXT = "test summary";
+
+    private Context mContext;
+    private StorageSlice mStorageSlice;
+
+    @Before
+    public void setUp() {
+        mContext = spy(RuntimeEnvironment.application);
+
+        // Set-up specs for SliceMetadata.
+        SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS);
+
+        mStorageSlice = spy(new StorageSlice(mContext));
+    }
+
+    @Test
+    public void getSlice_shouldBeCorrectSliceContent() {
+        final PrivateStorageInfo info = new PrivateStorageInfo(100L, 600L);
+        doReturn(info).when(mStorageSlice).getPrivateStorageInfo();
+        doReturn(USED_BYTES_TEXT).when(mStorageSlice).getStorageUsedText(any());
+        doReturn(SUMMARY_TEXT).when(mStorageSlice).getStorageSummaryText(any());
+        final Slice slice = mStorageSlice.getSlice();
+        final SliceMetadata metadata = SliceMetadata.from(mContext, slice);
+        final SliceAction primaryAction = metadata.getPrimaryAction();
+        final IconCompat expectedIcon = IconCompat.createWithResource(mContext,
+                R.drawable.ic_homepage_storage);
+        assertThat(primaryAction.getIcon().toString()).isEqualTo(expectedIcon.toString());
+
+        final List<SliceItem> sliceItems = slice.getItems();
+        SliceTester.assertTitle(sliceItems, mContext.getString(R.string.storage_label));
+    }
+}