Merge "Add Data usage slice in Contextual Settings Homepage"
diff --git a/res/values/strings.xml b/res/values/strings.xml
index d612e81..7308de7 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -10090,4 +10090,7 @@
<string name="network_query_error">Couldn\u2019t find networks. Try again.</string>
<!-- Text to show this network is forbidden [CHAR LIMIT=NONE] -->
<string name="forbidden_network">(forbidden)</string>
+
+ <!-- Message informs the user that has no SIM card in personalized Settings [CHAR LIMIT=30] -->
+ <string name="no_sim_card">No SIM card</string>
</resources>
diff --git a/src/com/android/settings/homepage/deviceinfo/DataUsageSlice.java b/src/com/android/settings/homepage/deviceinfo/DataUsageSlice.java
new file mode 100644
index 0000000..d78b93d
--- /dev/null
+++ b/src/com/android/settings/homepage/deviceinfo/DataUsageSlice.java
@@ -0,0 +1,151 @@
+/*
+ * 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.text.Spannable;
+import android.text.SpannableString;
+import android.text.TextUtils;
+import android.text.format.Formatter;
+import android.text.style.TextAppearanceSpan;
+
+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.datausage.DataUsageSummary;
+import com.android.settings.datausage.DataUsageUtils;
+import com.android.settings.slices.CustomSliceable;
+import com.android.settings.slices.SettingsSliceProvider;
+import com.android.settings.slices.SliceBuilderUtils;
+import com.android.settingslib.net.DataUsageController;
+
+import java.util.concurrent.TimeUnit;
+
+public class DataUsageSlice implements CustomSliceable {
+ private static final String TAG = "DataUsageSlice";
+ private static final long MILLIS_IN_A_DAY = TimeUnit.DAYS.toMillis(1);
+
+ /**
+ * The path denotes the unique name of data usage slice.
+ */
+ public static final String PATH_DATA_USAGE_CARD = "data_usage_card";
+
+ /**
+ * Backing Uri for the Data usage Slice.
+ */
+ public static final Uri DATA_USAGE_CARD_URI = new Uri.Builder()
+ .scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(SettingsSliceProvider.SLICE_AUTHORITY)
+ .appendPath(PATH_DATA_USAGE_CARD)
+ .build();
+
+ private final Context mContext;
+
+ public DataUsageSlice(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public Uri getUri() {
+ return DATA_USAGE_CARD_URI;
+ }
+
+ /**
+ * Return a Data usage Slice bound to {@link #DATA_USAGE_CARD_URI}
+ */
+ @Override
+ public Slice getSlice() {
+ final IconCompat icon = IconCompat.createWithResource(mContext,
+ R.drawable.ic_settings_data_usage);
+ final String title = mContext.getString(R.string.data_usage_summary_title);
+ final SliceAction primaryAction = new SliceAction(getPrimaryAction(), icon, title);
+ final DataUsageController dataUsageController = new DataUsageController(mContext);
+ final DataUsageController.DataUsageInfo info = dataUsageController.getDataUsageInfo();
+ final ListBuilder listBuilder =
+ new ListBuilder(mContext, DATA_USAGE_CARD_URI, ListBuilder.INFINITY)
+ .setAccentColor(Utils.getColorAccentDefaultColor(mContext))
+ .setHeader(new ListBuilder.HeaderBuilder().setTitle(title));
+ if (DataUsageUtils.hasSim(mContext)) {
+ listBuilder.addRow(new ListBuilder.RowBuilder()
+ .setTitle(getDataUsageText(info))
+ .setSubtitle(getCycleTime(info))
+ .setPrimaryAction(primaryAction));
+ } else {
+ listBuilder.addRow(new ListBuilder.RowBuilder()
+ .setTitle(mContext.getText(R.string.no_sim_card))
+ .setPrimaryAction(primaryAction));
+ }
+ return listBuilder.build();
+ }
+
+ @Override
+ public Intent getIntent() {
+ final String screenTitle = mContext.getText(R.string.data_usage_wifi_title).toString();
+ final Uri contentUri = new Uri.Builder().appendPath(PATH_DATA_USAGE_CARD).build();
+ return SliceBuilderUtils.buildSearchResultPageIntent(mContext,
+ DataUsageSummary.class.getName(), PATH_DATA_USAGE_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
+ CharSequence getDataUsageText(DataUsageController.DataUsageInfo info) {
+ final Formatter.BytesResult usedResult = Formatter.formatBytes(mContext.getResources(),
+ info.usageLevel, Formatter.FLAG_CALCULATE_ROUNDED | Formatter.FLAG_IEC_UNITS);
+ final SpannableString usageNumberText = new SpannableString(usedResult.value);
+ usageNumberText.setSpan(
+ new TextAppearanceSpan(mContext, android.R.style.TextAppearance_Large), 0,
+ usageNumberText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ return TextUtils.expandTemplate(mContext.getText(R.string.data_used_formatted),
+ usageNumberText, usedResult.units);
+ }
+
+ @VisibleForTesting
+ CharSequence getCycleTime(DataUsageController.DataUsageInfo info) {
+ final long millisLeft = info.cycleEnd - System.currentTimeMillis();
+ if (millisLeft <= 0) {
+ return mContext.getString(R.string.billing_cycle_none_left);
+ } else {
+ final int daysLeft = (int) (millisLeft / MILLIS_IN_A_DAY);
+ return daysLeft < 1 ? mContext.getString(R.string.billing_cycle_less_than_one_day_left)
+ : mContext.getResources().getQuantityString(R.plurals.billing_cycle_days_left,
+ daysLeft, daysLeft);
+ }
+ }
+
+ @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 8230a6f..3d81392 100644
--- a/src/com/android/settings/slices/CustomSliceManager.java
+++ b/src/com/android/settings/slices/CustomSliceManager.java
@@ -20,6 +20,7 @@
import android.net.Uri;
import android.util.ArrayMap;
+import com.android.settings.homepage.deviceinfo.DataUsageSlice;
import com.android.settings.wifi.WifiSlice;
import java.util.Map;
@@ -87,5 +88,6 @@
private void addSlices() {
mUriMap.put(WifiSlice.WIFI_URI, WifiSlice.class);
+ mUriMap.put(DataUsageSlice.DATA_USAGE_CARD_URI, DataUsageSlice.class);
}
}
\ No newline at end of file
diff --git a/tests/robotests/src/com/android/settings/homepage/deviceinfo/DataUsageSliceTest.java b/tests/robotests/src/com/android/settings/homepage/deviceinfo/DataUsageSliceTest.java
new file mode 100644
index 0000000..c03070c
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/homepage/deviceinfo/DataUsageSliceTest.java
@@ -0,0 +1,99 @@
+/*
+ * 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.settings.testutils.shadow.ShadowDataUsageUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import java.util.List;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(shadows = ShadowDataUsageUtils.class)
+public class DataUsageSliceTest {
+ private static final String DATA_USAGE_TITLE = "Data usage";
+ private static final String DATA_USAGE_SUMMARY = "test_summary";
+
+ private Context mContext;
+ private DataUsageSlice mDataUsageSlice;
+
+ @Before
+ public void setUp() {
+ mContext = spy(RuntimeEnvironment.application);
+
+ // Prevent crash in SliceMetadata.
+ Resources resources = spy(mContext.getResources());
+ doReturn(60).when(resources).getDimensionPixelSize(anyInt());
+ doReturn(resources).when(mContext).getResources();
+
+ // Set-up specs for SliceMetadata.
+ SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS);
+
+ mDataUsageSlice = spy(new DataUsageSlice(mContext));
+ }
+
+ @Test
+ public void getSlice_hasSim_shouldBeCorrectSliceContent() {
+ ShadowDataUsageUtils.HAS_SIM = true;
+ doReturn(DATA_USAGE_TITLE).when(mDataUsageSlice).getDataUsageText(any());
+ doReturn(DATA_USAGE_SUMMARY).when(mDataUsageSlice).getCycleTime(any());
+ final Slice slice = mDataUsageSlice.getSlice();
+ final SliceMetadata metadata = SliceMetadata.from(mContext, slice);
+ final SliceAction primaryAction = metadata.getPrimaryAction();
+ final IconCompat expectedIcon = IconCompat.createWithResource(mContext,
+ R.drawable.ic_settings_data_usage);
+ assertThat(primaryAction.getIcon().toString()).isEqualTo(expectedIcon.toString());
+
+ final List<SliceItem> sliceItems = slice.getItems();
+ SliceTester.assertTitle(sliceItems, mContext.getString(R.string.data_usage_summary_title));
+ }
+
+ @Test
+ public void getSlice_hasNoSim_shouldShowNoSimCard() {
+ ShadowDataUsageUtils.HAS_SIM = false;
+ final Slice slice = mDataUsageSlice.getSlice();
+ final List<SliceItem> sliceItems = slice.getItems();
+
+ SliceTester.assertTitle(sliceItems, mContext.getString(R.string.data_usage_summary_title));
+ SliceTester.assertTitle(sliceItems, mContext.getString(R.string.no_sim_card));
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/testutils/SliceTester.java b/tests/robotests/src/com/android/settings/testutils/SliceTester.java
index af21dba..49a84f2 100644
--- a/tests/robotests/src/com/android/settings/testutils/SliceTester.java
+++ b/tests/robotests/src/com/android/settings/testutils/SliceTester.java
@@ -25,6 +25,7 @@
import android.app.PendingIntent;
import android.content.Context;
+import android.text.TextUtils;
import androidx.core.graphics.drawable.IconCompat;
import androidx.slice.Slice;
@@ -209,9 +210,11 @@
continue;
}
- hasTitle = true;
for (SliceItem subTitleItem : titleItems) {
- assertThat(subTitleItem.getText()).isEqualTo(title);
+ if (TextUtils.equals(subTitleItem.getText(), title)) {
+ hasTitle = true;
+ assertThat(subTitleItem.getText()).isEqualTo(title);
+ }
}
}
assertThat(hasTitle).isTrue();