diff --git a/res/layout/data_usage_summary_preference.xml b/res/layout/data_usage_summary_preference.xml index c2e1581d9e7..eda8a81e250 100644 --- a/res/layout/data_usage_summary_preference.xml +++ b/res/layout/data_usage_summary_preference.xml @@ -35,15 +35,34 @@ android:textColor="?android:attr/textColorSecondary" android:text="@string/data_usage_title" /> - + android:paddingTop="12dp" + android:paddingBottom="4dp" + android:orientation="horizontal"> + + + + + + + + Primary data - + ^1 used - - , ^1 left + + ^1 over - - %1$s left in this cycle + + ^1 left + + + %d days left + + + + %d day left + %d days left + + + + Less than 1 day left Updated by %1$s %2$s diff --git a/src/com/android/settings/datausage/DataUsageSummary.java b/src/com/android/settings/datausage/DataUsageSummary.java index 2d505892ab3..6d9ef3836c8 100644 --- a/src/com/android/settings/datausage/DataUsageSummary.java +++ b/src/com/android/settings/datausage/DataUsageSummary.java @@ -234,12 +234,17 @@ public class DataUsageSummary extends DataUsageBaseFragment implements Indexable static CharSequence formatUsage(Context context, String template, long usageLevel) { final float LARGER_SIZE = 1.25f * 1.25f; // (1/0.8)^2 final float SMALLER_SIZE = 1.0f / LARGER_SIZE; // 0.8^2 + return formatUsage(context, template, usageLevel, LARGER_SIZE, SMALLER_SIZE); + } + + static CharSequence formatUsage(Context context, String template, long usageLevel, + float larger, float smaller) { final int FLAGS = Spannable.SPAN_INCLUSIVE_INCLUSIVE; final Formatter.BytesResult usedResult = Formatter.formatBytes(context.getResources(), usageLevel, Formatter.FLAG_CALCULATE_ROUNDED); final SpannableString enlargedValue = new SpannableString(usedResult.value); - enlargedValue.setSpan(new RelativeSizeSpan(LARGER_SIZE), 0, enlargedValue.length(), FLAGS); + enlargedValue.setSpan(new RelativeSizeSpan(larger), 0, enlargedValue.length(), FLAGS); final SpannableString amountTemplate = new SpannableString( context.getString(com.android.internal.R.string.fileSizeSuffix) @@ -248,7 +253,7 @@ public class DataUsageSummary extends DataUsageBaseFragment implements Indexable enlargedValue, usedResult.units); final SpannableString fullTemplate = new SpannableString(template); - fullTemplate.setSpan(new RelativeSizeSpan(SMALLER_SIZE), 0, fullTemplate.length(), FLAGS); + fullTemplate.setSpan(new RelativeSizeSpan(smaller), 0, fullTemplate.length(), FLAGS); return TextUtils.expandTemplate(fullTemplate, BidiFormatter.getInstance().unicodeWrap(formattedUsage.toString())); } diff --git a/src/com/android/settings/datausage/DataUsageSummaryPreference.java b/src/com/android/settings/datausage/DataUsageSummaryPreference.java index dbf87fe5bed..e8715aac296 100644 --- a/src/com/android/settings/datausage/DataUsageSummaryPreference.java +++ b/src/com/android/settings/datausage/DataUsageSummaryPreference.java @@ -21,6 +21,7 @@ import android.content.Intent; import android.support.v7.preference.Preference; import android.support.v7.preference.PreferenceViewHolder; import android.text.TextUtils; +import android.text.format.Formatter; import android.util.AttributeSet; import android.view.View; import android.widget.Button; @@ -28,19 +29,29 @@ import android.widget.ProgressBar; import android.widget.TextView; import com.android.settings.R; +import com.android.settingslib.Utils; import com.android.settingslib.utils.StringUtil; import java.util.Objects; +import java.util.concurrent.TimeUnit; /** * Provides a summary of data usage. */ public class DataUsageSummaryPreference extends Preference { + private static final long MILLIS_IN_A_DAY = TimeUnit.DAYS.toMillis(1); + private static final long WARNING_AGE = TimeUnit.HOURS.toMillis(6L); private boolean mChartEnabled = true; private String mStartLabel; private String mEndLabel; + /** large vs small size is 36/16 ~ 2.25 */ + private static final float LARGER_FONT_RATIO = 2.25f; + private static final float SMALLER_FONT_RATIO = 1.0f; + + private boolean mDefaultTextColorSet; + private int mDefaultTextColor; private int mNumPlans; /** The ending time of the billing cycle in milliseconds since epoch. */ private long mCycleEndTimeMs; @@ -53,6 +64,16 @@ public class DataUsageSummaryPreference extends Preference { /** Progress to display on ProgressBar */ private float mProgress; + private boolean mHasMobileData; + + /** + * The size of the first registered plan if one exists or the size of the warning if it is set. + * -1 if no information is available. + */ + private long mDataplanSize; + + /** The number of bytes used since the start of the cycle. */ + private long mDataplanUse; public DataUsageSummaryPreference(Context context, AttributeSet attrs) { super(context, attrs); @@ -94,10 +115,18 @@ public class DataUsageSummaryPreference extends Preference { notifyChanged(); } + void setUsageNumbers(long used, long dataPlanSize, boolean hasMobileData) { + mDataplanUse = used; + mDataplanSize = dataPlanSize; + mHasMobileData = hasMobileData; + notifyChanged(); + } + @Override public void onBindViewHolder(PreferenceViewHolder holder) { super.onBindViewHolder(holder); + if (mChartEnabled && (!TextUtils.isEmpty(mStartLabel) || !TextUtils.isEmpty(mEndLabel))) { holder.findViewById(R.id.label_bar).setVisibility(View.VISIBLE); ProgressBar bar = (ProgressBar) holder.findViewById(R.id.determinateBar); @@ -108,16 +137,15 @@ public class DataUsageSummaryPreference extends Preference { holder.findViewById(R.id.label_bar).setVisibility(View.GONE); } + updateDataUsageLabels(holder); + TextView usageTitle = (TextView) holder.findViewById(R.id.usage_title); usageTitle.setVisibility(mNumPlans > 1 ? View.VISIBLE : View.GONE); - TextView cycleTime = (TextView) holder.findViewById(R.id.cycle_left_time); - cycleTime.setText(getContext().getString(R.string.cycle_left_time_text, - StringUtil.formatElapsedTime(getContext(), - mCycleEndTimeMs - System.currentTimeMillis(),false /* withSeconds */))); + updateCycleTimeText(holder); - TextView carrierInfo = (TextView) holder.findViewById(R.id.carrier_and_update); - setCarrierInfo(carrierInfo, mCarrierName, mSnapshotTimeMs); + + updateCarrierInfo((TextView) holder.findViewById(R.id.carrier_and_update)); Button launchButton = (Button) holder.findViewById(R.id.launch_mdp_app_button); launchButton.setOnClickListener((view) -> { @@ -135,18 +163,61 @@ public class DataUsageSummaryPreference extends Preference { limitInfo.setText(mLimitInfoText); } - private void setCarrierInfo(TextView carrierInfo, CharSequence carrierName, long updateAge) { - if (mNumPlans > 0 && updateAge >= 0L) { + + private void updateDataUsageLabels(PreferenceViewHolder holder) { + TextView usageNumberField = (TextView) holder.findViewById(R.id.data_usage_view); + usageNumberField.setText(TextUtils.expandTemplate( + getContext().getString(R.string.data_used), + Formatter.formatFileSize(getContext(), mDataplanUse))); + if (mHasMobileData && mNumPlans >= 0 && mDataplanSize > 0L) { + TextView usageRemainingField = (TextView) holder.findViewById(R.id.data_remaining_view); + long dataRemaining = mDataplanSize - mDataplanUse; + if (dataRemaining >= 0) { + usageRemainingField.setText( + TextUtils.expandTemplate(getContext().getText(R.string.data_remaining), + Formatter.formatFileSize(getContext(), dataRemaining))); + } else { + usageRemainingField.setText( + TextUtils.expandTemplate(getContext().getText(R.string.data_overusage), + Formatter.formatFileSize(getContext(), -dataRemaining))); + } + } + } + + private void updateCycleTimeText(PreferenceViewHolder holder) { + float daysLeft = + ((float) mCycleEndTimeMs - System.currentTimeMillis()) / MILLIS_IN_A_DAY; + if (daysLeft < 0) { + daysLeft = 0; + } + + TextView cycleTime = (TextView) holder.findViewById(R.id.cycle_left_time); + cycleTime.setText( + (daysLeft > 0 && daysLeft < 1) + ? getContext().getString(R.string.billing_cycle_less_than_one_day_left) + : getContext().getResources().getQuantityString( + R.plurals.billing_cycle_days_left, (int) daysLeft, (int) daysLeft)); + } + + + private void updateCarrierInfo(TextView carrierInfo) { + if (mNumPlans > 0 && mSnapshotTimeMs >= 0L) { + long updateAge = System.currentTimeMillis() - mSnapshotTimeMs; carrierInfo.setVisibility(View.VISIBLE); - if (carrierName != null) { + if (mCarrierName != null) { carrierInfo.setText(getContext().getString(R.string.carrier_and_update_text, - carrierName, StringUtil.formatRelativeTime( + mCarrierName, StringUtil.formatRelativeTime( getContext(), updateAge, false /* withSeconds */))); } else { carrierInfo.setText(getContext().getString(R.string.no_carrier_update_text, StringUtil.formatRelativeTime( getContext(), updateAge, false /* withSeconds */))); } + + carrierInfo.setTextColor( + updateAge <= WARNING_AGE + ? Utils.getColorAttr(getContext(), android.R.attr.textColorPrimary) + : Utils.getColorAttr(getContext(), android.R.attr.colorError)); } else { carrierInfo.setVisibility(View.GONE); } diff --git a/src/com/android/settings/datausage/DataUsageSummaryPreferenceController.java b/src/com/android/settings/datausage/DataUsageSummaryPreferenceController.java index 0729fff1517..281c8b6970c 100644 --- a/src/com/android/settings/datausage/DataUsageSummaryPreferenceController.java +++ b/src/com/android/settings/datausage/DataUsageSummaryPreferenceController.java @@ -174,19 +174,7 @@ public class DataUsageSummaryPreferenceController extends BasePreferenceControll summaryPreference.setLimitInfo(null); } - final StringBuilder title = new StringBuilder(); - if (mHasMobileData) { - title.append(formatUsage(mContext, mContext.getString(R.string.data_used), - mDataplanUse)); - if (mDataplanCount >= 0 && mDataplanSize > 0L) { - title.append(formatUsage(mContext, mContext.getString(R.string.data_remaining), - mDataplanSize - mDataplanUse)); - } - } else { - title.append(formatUsage(mContext, mContext.getString(mDataUsageTemplate), - mDataplanUse)); - } - summaryPreference.setTitle(title.toString()); + summaryPreference.setUsageNumbers(mDataplanUse, mDataplanSize, mHasMobileData); if (mDataplanSize <= 0) { summaryPreference.setChartEnabled(false); @@ -231,7 +219,7 @@ public class DataUsageSummaryPreferenceController extends BasePreferenceControll mCycleStart = rule.start.toEpochSecond() * 1000L; mCycleEnd = rule.end.toEpochSecond() * 1000L; } - mSnapshotTime = System.currentTimeMillis() - primaryPlan.getDataUsageTime(); + mSnapshotTime = primaryPlan.getDataUsageTime(); } } mManageSubscriptionIntent = diff --git a/tests/robotests/src/com/android/settings/datausage/DataUsageSummaryPreferenceTest.java b/tests/robotests/src/com/android/settings/datausage/DataUsageSummaryPreferenceTest.java index 57f1f8c6f66..638ee792045 100644 --- a/tests/robotests/src/com/android/settings/datausage/DataUsageSummaryPreferenceTest.java +++ b/tests/robotests/src/com/android/settings/datausage/DataUsageSummaryPreferenceTest.java @@ -16,8 +16,6 @@ package com.android.settings.datausage; -import static com.google.common.truth.Truth.assertThat; - import android.content.Context; import android.content.Intent; import android.support.v7.preference.PreferenceViewHolder; @@ -31,7 +29,9 @@ import android.widget.TextView; import com.android.settings.R; import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.testutils.shadow.SettingsShadowResourcesImpl; +import com.android.settingslib.Utils; import com.android.settingslib.utils.StringUtil; +import java.util.concurrent.TimeUnit; import org.junit.Before; import org.junit.Test; @@ -40,6 +40,10 @@ import org.mockito.MockitoAnnotations; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; +import java.util.concurrent.TimeUnit; + +import static com.google.common.truth.Truth.assertThat; + @RunWith(SettingsRobolectricTestRunner.class) @Config(shadows = SettingsShadowResourcesImpl.class) public class DataUsageSummaryPreferenceTest { @@ -55,6 +59,8 @@ public class DataUsageSummaryPreferenceTest { private TextView mCycleTime; private TextView mCarrierInfo; private TextView mDataLimits; + private TextView mDataUsed; + private TextView mDataRemaining; private Button mLaunchButton; private LinearLayout mLabelBar; private TextView mLabel1; @@ -116,6 +122,31 @@ public class DataUsageSummaryPreferenceTest { assertThat(mCarrierInfo.getVisibility()).isEqualTo(View.GONE); } + @Test + public void testSetUsageInfo_withRecentCarrierUpdate_doesNotSetCarrierInfoWarningColor() { + final long updateTime = System.currentTimeMillis() - TimeUnit.HOURS.toMillis(1); + mCarrierInfo = (TextView) mHolder.findViewById(R.id.carrier_and_update); + mSummaryPreference.setUsageInfo(mCycleEnd, updateTime, DUMMY_CARRIER, 1 /* numPlans */, + new Intent()); + + bindViewHolder(); + assertThat(mCarrierInfo.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mCarrierInfo.getCurrentTextColor()).isEqualTo( + Utils.getColorAttr(mContext, android.R.attr.textColorPrimary)); + } + + @Test + public void testSetUsageInfo_withStaleCarrierUpdate_setsCarrierInfoWarningColor() { + final long updateTime = System.currentTimeMillis() - TimeUnit.HOURS.toMillis(7); + mSummaryPreference.setUsageInfo(mCycleEnd, updateTime, DUMMY_CARRIER, 1 /* numPlans */, + new Intent()); + + bindViewHolder(); + assertThat(mCarrierInfo.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mCarrierInfo.getCurrentTextColor()).isEqualTo( + Utils.getColorAttr(mContext, android.R.attr.colorError)); + } + @Test public void testSetUsageInfo_withNoDataPlans_usageTitleNotShown() { mSummaryPreference.setUsageInfo(mCycleEnd, mUpdateTime, DUMMY_CARRIER, 0 /* numPlans */, @@ -135,16 +166,41 @@ public class DataUsageSummaryPreferenceTest { } @Test - public void testSetUsageInfo_cycleRemainingTimeShown() { - mSummaryPreference.setUsageInfo(mCycleEnd, mUpdateTime, DUMMY_CARRIER, 0 /* numPlans */, + public void testSetUsageInfo_cycleRemainingTimeIsLessOneDay() { + // just under one day + final long cycleEnd = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(23); + mSummaryPreference.setUsageInfo(cycleEnd, mUpdateTime, DUMMY_CARRIER, 0 /* numPlans */, new Intent()); - String cyclePrefix = StringUtil.formatElapsedTime(mContext, CYCLE_DURATION_MILLIS, - false /* withSeconds */).toString(); - String text = mContext.getString(R.string.cycle_left_time_text, cyclePrefix); bindViewHolder(); assertThat(mCycleTime.getVisibility()).isEqualTo(View.VISIBLE); - assertThat(mCycleTime.getText()).isEqualTo(text); + assertThat(mCycleTime.getText()).isEqualTo( + mContext.getString(R.string.billing_cycle_less_than_one_day_left)); + } + + @Test + public void testSetUsageInfo_cycleRemainingTimeNegativeDaysLeft_shouldDisplayZeroDays() { + final long cycleEnd = System.currentTimeMillis() - 1L; + mSummaryPreference.setUsageInfo(cycleEnd, mUpdateTime, DUMMY_CARRIER, 0 /* numPlans */, + new Intent()); + + bindViewHolder(); + assertThat(mCycleTime.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mCycleTime.getText()).isEqualTo( + mContext.getResources().getQuantityString(R.plurals.billing_cycle_days_left, 0, 0)); + } + + @Test + public void testSetUsageInfo_cycleRemainingTimeDaysLeft_shouldUsePlurals() { + final int daysLeft = 3; + final long cycleEnd = System.currentTimeMillis() + TimeUnit.DAYS.toMillis(daysLeft) + + TimeUnit.HOURS.toMillis(1); + mSummaryPreference.setUsageInfo(cycleEnd, mUpdateTime, DUMMY_CARRIER, 0 /* numPlans */, + new Intent()); + + bindViewHolder(); + assertThat(mCycleTime.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mCycleTime.getText()).isEqualTo(daysLeft + " days left"); } @Test @@ -205,12 +261,47 @@ public class DataUsageSummaryPreferenceTest { mSummaryPreference.setLabels("0.0 GB", "5.0 GB"); } + @Test + public void testSetUsageAndRemainingInfo_withUsageInfo_dataUsageAndRemainingShown() { + mSummaryPreference.setUsageInfo(mCycleEnd, mUpdateTime, DUMMY_CARRIER, 1 /* numPlans */, + new Intent()); + mSummaryPreference.setUsageNumbers(1000000L, 10000000L, true); + + bindViewHolder(); + assertThat(mDataUsed.getText().toString()).isEqualTo("1.00 MB used"); + assertThat(mDataRemaining.getText().toString()).isEqualTo("9.00 MB left"); + } + + @Test + public void testSetUsageInfo_withDataOverusage() { + mSummaryPreference.setUsageInfo(mCycleEnd, mUpdateTime, DUMMY_CARRIER, 1 /* numPlans */, + new Intent()); + mSummaryPreference.setUsageNumbers(11_000_000L, 10_000_000L, true); + + bindViewHolder(); + assertThat(mDataUsed.getText().toString()).isEqualTo("11.00 MB used"); + assertThat(mDataRemaining.getText().toString()).isEqualTo("1.00 MB over"); + } + + @Test + public void testSetUsageInfo_withUsageInfo_dataUsageShown() { + mSummaryPreference.setUsageInfo(mCycleEnd, mUpdateTime, DUMMY_CARRIER, 0 /* numPlans */, + new Intent()); + mSummaryPreference.setUsageNumbers(1000000L, -1L, true); + + bindViewHolder(); + assertThat(mDataUsed.getText().toString()).isEqualTo("1.00 MB used"); + assertThat(mDataRemaining.getText()).isEqualTo(""); + } + private void bindViewHolder() { mSummaryPreference.onBindViewHolder(mHolder); mUsageTitle = (TextView) mHolder.findViewById(R.id.usage_title); mCycleTime = (TextView) mHolder.findViewById(R.id.cycle_left_time); mCarrierInfo = (TextView) mHolder.findViewById(R.id.carrier_and_update); mDataLimits = (TextView) mHolder.findViewById(R.id.data_limits); + mDataUsed = (TextView) mHolder.findViewById(R.id.data_usage_view); + mDataRemaining = (TextView) mHolder.findViewById(R.id.data_remaining_view); mLaunchButton = (Button) mHolder.findViewById(R.id.launch_mdp_app_button); mLabelBar = (LinearLayout) mHolder.findViewById(R.id.label_bar); mLabel1 = (TextView) mHolder.findViewById(R.id.text1);