From 736b77c04af5cb31d191fafe28be2c9f567b6870 Mon Sep 17 00:00:00 2001 From: Mill Chen Date: Fri, 14 Sep 2018 23:55:35 +0800 Subject: [PATCH] Add Data usage slice in Contextual Settings Homepage - Add Data usage card that implements CustomSliceable in Contextual Settings Homepage. - Add test case for Data usage slice. Bug: 114796538 Test: robotests, manual, SliceViewer Change-Id: I66a046e8f589a477007ea73e1b22420d3efdebde --- res/values/strings.xml | 3 + .../homepage/deviceinfo/DataUsageSlice.java | 151 ++++++++++++++++++ .../settings/slices/CustomSliceManager.java | 2 + .../deviceinfo/DataUsageSliceTest.java | 99 ++++++++++++ .../settings/testutils/SliceTester.java | 7 +- 5 files changed, 260 insertions(+), 2 deletions(-) create mode 100644 src/com/android/settings/homepage/deviceinfo/DataUsageSlice.java create mode 100644 tests/robotests/src/com/android/settings/homepage/deviceinfo/DataUsageSliceTest.java diff --git a/res/values/strings.xml b/res/values/strings.xml index 04852e0b64c..d165df04ae9 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -10090,4 +10090,7 @@ Couldn\u2019t find networks. Try again. (forbidden) + + + No SIM card 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 00000000000..d78b93de688 --- /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 8230a6fa0ca..3d81392735d 100644 --- a/src/com/android/settings/slices/CustomSliceManager.java +++ b/src/com/android/settings/slices/CustomSliceManager.java @@ -20,6 +20,7 @@ import android.content.Context; 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 @@ public class CustomSliceManager { 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 00000000000..c03070c5363 --- /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 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 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 af21dbac206..49a84f28a6f 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 static com.google.common.truth.Truth.assertThat; 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 @@ public class SliceTester { 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();