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));
+ }
+}