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();