1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.car.settings.datausage; 18 19 import android.car.drivingstate.CarUxRestrictions; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.net.NetworkTemplate; 23 import android.telephony.SubscriptionInfo; 24 import android.telephony.SubscriptionManager; 25 import android.telephony.SubscriptionPlan; 26 import android.telephony.TelephonyManager; 27 import android.text.Spannable; 28 import android.text.SpannableString; 29 import android.text.TextUtils; 30 import android.text.format.Formatter; 31 import android.text.style.AbsoluteSizeSpan; 32 import android.util.RecurrenceRule; 33 34 import androidx.annotation.Nullable; 35 import androidx.annotation.VisibleForTesting; 36 37 import com.android.car.settings.R; 38 import com.android.car.settings.common.FragmentController; 39 import com.android.car.settings.common.PreferenceController; 40 import com.android.car.settings.network.NetworkUtils; 41 import com.android.settingslib.net.DataUsageController; 42 import com.android.settingslib.utils.StringUtil; 43 44 import java.util.List; 45 import java.util.concurrent.TimeUnit; 46 47 /** 48 * Business logic for setting the {@link DataUsageSummaryPreference} with the current data usage and 49 * the appropriate summary text. 50 */ 51 public class DataUsageSummaryPreferenceController extends 52 PreferenceController<DataUsageSummaryPreference> { 53 54 private static final long MILLIS_IN_A_DAY = TimeUnit.DAYS.toMillis(1); 55 private static final long MILLIS_IN_AN_HOUR = TimeUnit.HOURS.toMillis(1); 56 private static final long MILLIS_IN_A_MINUTE = TimeUnit.MINUTES.toMillis(1); 57 private static final long MILLIS_IN_A_SECOND = TimeUnit.SECONDS.toMillis(1); 58 private static final int MAX_PROGRESS_BAR_VALUE = 1000; 59 60 @VisibleForTesting 61 static final long WARNING_AGE = TimeUnit.HOURS.toMillis(6L); 62 63 private final SubscriptionManager mSubscriptionManager; 64 private final TelephonyManager mTelephonyManager; 65 private final DataUsageController mDataUsageController; 66 private final NetworkTemplate mDefaultTemplate; 67 68 /** Name of the carrier, or null if not available */ 69 @Nullable 70 private CharSequence mCarrierName; 71 /** The number of registered plans, [0,N] */ 72 private int mDataplanCount; 73 /** The time of the last update in milliseconds since the epoch, or -1 if unknown */ 74 private long mSnapshotTime; 75 /** The size of the first registered plan if one exists. -1 if no information is available. */ 76 private long mDataplanSize = -1; 77 /** 78 * Limit to track. Size of the first registered plan if one exists. Otherwise size of data limit 79 * or warning. 80 */ 81 private long mDataplanTrackingThreshold; 82 /** The number of bytes used since the start of the cycle. */ 83 private long mDataplanUse; 84 /** The ending time of the billing cycle in ms since the epoch */ 85 private long mCycleEnd; 86 private Intent mManageSubscriptionIntent; 87 DataUsageSummaryPreferenceController(Context context, String preferenceKey, FragmentController fragmentController, CarUxRestrictions uxRestrictions)88 public DataUsageSummaryPreferenceController(Context context, String preferenceKey, 89 FragmentController fragmentController, CarUxRestrictions uxRestrictions) { 90 super(context, preferenceKey, fragmentController, uxRestrictions); 91 mSubscriptionManager = context.getSystemService(SubscriptionManager.class); 92 mTelephonyManager = context.getSystemService(TelephonyManager.class); 93 mDataUsageController = new DataUsageController(context); 94 95 int defaultSubId = DataUsageUtils.getDefaultSubscriptionId(mSubscriptionManager); 96 mDefaultTemplate = DataUsageUtils.getMobileNetworkTemplate(mTelephonyManager, defaultSubId); 97 } 98 99 @Override getPreferenceType()100 protected Class<DataUsageSummaryPreference> getPreferenceType() { 101 return DataUsageSummaryPreference.class; 102 } 103 104 @Override getAvailabilityStatus()105 protected int getAvailabilityStatus() { 106 return NetworkUtils.hasSim(mTelephonyManager) ? AVAILABLE : CONDITIONALLY_UNAVAILABLE; 107 } 108 109 @Override onCreateInternal()110 protected void onCreateInternal() { 111 getPreference().setMin(0); 112 getPreference().setMax(MAX_PROGRESS_BAR_VALUE); 113 } 114 115 @Override updateState(DataUsageSummaryPreference preference)116 protected void updateState(DataUsageSummaryPreference preference) { 117 DataUsageController.DataUsageInfo info = mDataUsageController.getDataUsageInfo( 118 mDefaultTemplate); 119 120 if (mSubscriptionManager != null) { 121 refreshDataplanInfo(info); 122 } 123 124 preference.setTitle(getUsageText()); 125 preference.setManageSubscriptionIntent(mManageSubscriptionIntent); 126 127 preference.setDataLimitText(getLimitText(info)); 128 preference.setRemainingBillingCycleText(getRemainingBillingCycleTimeText()); 129 130 // Carrier Info has special styling based on when it was last updated. 131 preference.setCarrierInfoText(getCarrierInfoText()); 132 long updateAgeMillis = calculateTruncatedUpdateAge(mSnapshotTime); 133 if (updateAgeMillis <= WARNING_AGE) { 134 preference.setCarrierInfoTextStyle(R.style.DataUsageSummaryCarrierInfoTextAppearance); 135 } else { 136 preference.setCarrierInfoTextStyle( 137 R.style.DataUsageSummaryCarrierInfoWarningTextAppearance); 138 } 139 140 // Set the progress bar values. 141 preference.setMinLabel(DataUsageUtils.bytesToIecUnits(getContext(), /* byteValue= */ 0)); 142 preference.setMaxLabel( 143 DataUsageUtils.bytesToIecUnits(getContext(), mDataplanTrackingThreshold)); 144 preference.setProgress(scaleUsage(mDataplanUse, mDataplanTrackingThreshold)); 145 } 146 getUsageText()147 private CharSequence getUsageText() { 148 Formatter.BytesResult usedResult = Formatter.formatBytes(getContext().getResources(), 149 mDataplanUse, Formatter.FLAG_CALCULATE_ROUNDED | Formatter.FLAG_IEC_UNITS); 150 SpannableString usageNumberText = new SpannableString(usedResult.value); 151 int textSize = getContext().getResources().getDimensionPixelSize( 152 R.dimen.usage_number_text_size); 153 154 // Set the usage text (only the number) to the size defined by usage_number_text_size. 155 usageNumberText.setSpan(new AbsoluteSizeSpan(textSize), /* start= */ 0, 156 usageNumberText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 157 158 CharSequence template = getContext().getText(R.string.data_used_formatted); 159 CharSequence usageText = TextUtils.expandTemplate(template, usageNumberText, 160 usedResult.units); 161 return usageText; 162 } 163 164 getLimitText(DataUsageController.DataUsageInfo info)165 private CharSequence getLimitText(DataUsageController.DataUsageInfo info) { 166 if (info.warningLevel > 0 && info.limitLevel > 0) { 167 return TextUtils.expandTemplate( 168 getContext().getText(R.string.cell_data_warning_and_limit), 169 DataUsageUtils.bytesToIecUnits(getContext(), info.warningLevel), 170 DataUsageUtils.bytesToIecUnits(getContext(), info.limitLevel)); 171 } else if (info.warningLevel > 0) { 172 return TextUtils.expandTemplate(getContext().getText(R.string.cell_data_warning), 173 DataUsageUtils.bytesToIecUnits(getContext(), info.warningLevel)); 174 } else if (info.limitLevel > 0) { 175 return TextUtils.expandTemplate(getContext().getText(R.string.cell_data_limit), 176 DataUsageUtils.bytesToIecUnits(getContext(), info.limitLevel)); 177 } 178 179 return null; 180 } 181 getRemainingBillingCycleTimeText()182 private CharSequence getRemainingBillingCycleTimeText() { 183 long millisLeft = mCycleEnd - System.currentTimeMillis(); 184 if (millisLeft <= 0) { 185 return getContext().getString(R.string.billing_cycle_none_left); 186 } else { 187 int daysLeft = (int) (millisLeft / MILLIS_IN_A_DAY); 188 return daysLeft < 1 189 ? getContext().getString(R.string.billing_cycle_less_than_one_day_left) 190 : getContext().getResources().getQuantityString( 191 R.plurals.billing_cycle_days_left, daysLeft, daysLeft); 192 } 193 } 194 getCarrierInfoText()195 private CharSequence getCarrierInfoText() { 196 if (mDataplanCount > 0 && mSnapshotTime >= 0L) { 197 long updateAgeMillis = calculateTruncatedUpdateAge(mSnapshotTime); 198 199 int textResourceId; 200 CharSequence updateTime = null; 201 if (updateAgeMillis == 0) { 202 if (mCarrierName != null) { 203 textResourceId = R.string.carrier_and_update_now_text; 204 } else { 205 textResourceId = R.string.no_carrier_update_now_text; 206 } 207 } else { 208 if (mCarrierName != null) { 209 textResourceId = R.string.carrier_and_update_text; 210 } else { 211 textResourceId = R.string.no_carrier_update_text; 212 } 213 updateTime = StringUtil.formatElapsedTime(getContext(), 214 updateAgeMillis, /* withSeconds= */ false); 215 } 216 return TextUtils.expandTemplate(getContext().getText(textResourceId), mCarrierName, 217 updateTime); 218 } 219 220 return null; 221 } 222 refreshDataplanInfo(DataUsageController.DataUsageInfo info)223 private void refreshDataplanInfo(DataUsageController.DataUsageInfo info) { 224 // Reset data before overwriting. 225 mCarrierName = null; 226 mDataplanCount = 0; 227 mSnapshotTime = -1L; 228 mDataplanSize = -1L; 229 mDataplanTrackingThreshold = getSummaryLimit(info); 230 mDataplanUse = info.usageLevel; 231 mCycleEnd = info.cycleEnd; 232 233 int defaultSubId = SubscriptionManager.getDefaultSubscriptionId(); 234 SubscriptionInfo subInfo = mSubscriptionManager.getDefaultDataSubscriptionInfo(); 235 if (subInfo != null) { 236 mCarrierName = subInfo.getCarrierName(); 237 List<SubscriptionPlan> plans = mSubscriptionManager.getSubscriptionPlans(defaultSubId); 238 SubscriptionPlan primaryPlan = DataUsageUtils.getPrimaryPlan(mSubscriptionManager, 239 defaultSubId); 240 if (primaryPlan != null) { 241 mDataplanCount = plans.size(); 242 mDataplanSize = primaryPlan.getDataLimitBytes(); 243 if (mDataplanSize == SubscriptionPlan.BYTES_UNLIMITED) { 244 mDataplanSize = -1L; 245 } 246 mDataplanTrackingThreshold = mDataplanSize; 247 mDataplanUse = primaryPlan.getDataUsageBytes(); 248 249 RecurrenceRule rule = primaryPlan.getCycleRule(); 250 if (rule != null && rule.start != null && rule.end != null) { 251 mCycleEnd = rule.end.toEpochSecond() * MILLIS_IN_A_SECOND; 252 } 253 mSnapshotTime = primaryPlan.getDataUsageTime(); 254 } 255 } 256 mManageSubscriptionIntent = mSubscriptionManager.createManageSubscriptionIntent( 257 defaultSubId); 258 } 259 260 /** Scales the current usage to be an integer between 0 and {@link #MAX_PROGRESS_BAR_VALUE}. */ scaleUsage(long usage, long maxUsage)261 private int scaleUsage(long usage, long maxUsage) { 262 if (maxUsage == 0) { 263 return 0; 264 } 265 return (int) ((usage / (float) maxUsage) * MAX_PROGRESS_BAR_VALUE); 266 } 267 268 /** 269 * Gets the max displayed limit based on {@link DataUsageController.DataUsageInfo}. 270 * 271 * @return the most appropriate limit for the data usage summary. Use the total usage when it 272 * is higher than the limit and warning level. Use the limit when it is set and less than usage. 273 * Otherwise use warning level. 274 */ getSummaryLimit(DataUsageController.DataUsageInfo info)275 private static long getSummaryLimit(DataUsageController.DataUsageInfo info) { 276 long limit = info.limitLevel; 277 if (limit <= 0) { 278 limit = info.warningLevel; 279 } 280 if (info.usageLevel > limit) { 281 limit = info.usageLevel; 282 } 283 return limit; 284 } 285 286 /** 287 * Returns the time since the last carrier update, as defined by {@link #mSnapshotTime}, 288 * truncated to the nearest day / hour / minute in milliseconds, or 0 if less than 1 min. 289 */ calculateTruncatedUpdateAge(long snapshotTime)290 private long calculateTruncatedUpdateAge(long snapshotTime) { 291 long updateAgeMillis = System.currentTimeMillis() - snapshotTime; 292 293 // Round to nearest whole unit 294 if (updateAgeMillis >= MILLIS_IN_A_DAY) { 295 return (updateAgeMillis / MILLIS_IN_A_DAY) * MILLIS_IN_A_DAY; 296 } else if (updateAgeMillis >= MILLIS_IN_AN_HOUR) { 297 return (updateAgeMillis / MILLIS_IN_AN_HOUR) * MILLIS_IN_AN_HOUR; 298 } else if (updateAgeMillis >= MILLIS_IN_A_MINUTE) { 299 return (updateAgeMillis / MILLIS_IN_A_MINUTE) * MILLIS_IN_A_MINUTE; 300 } else { 301 return 0; 302 } 303 } 304 } 305