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.content.pm.PackageManager; 23 import android.net.INetworkPolicyManager; 24 import android.net.NetworkTemplate; 25 import android.os.ServiceManager; 26 import android.telephony.SubscriptionInfo; 27 import android.telephony.SubscriptionManager; 28 import android.telephony.SubscriptionPlan; 29 import android.telephony.TelephonyManager; 30 import android.text.Spannable; 31 import android.text.SpannableString; 32 import android.text.TextUtils; 33 import android.text.format.Formatter; 34 import android.text.style.AbsoluteSizeSpan; 35 import android.util.RecurrenceRule; 36 37 import androidx.annotation.Nullable; 38 import androidx.annotation.VisibleForTesting; 39 40 import com.android.car.settings.R; 41 import com.android.car.settings.common.FragmentController; 42 import com.android.car.settings.common.Logger; 43 import com.android.car.settings.common.PreferenceController; 44 import com.android.car.settings.network.NetworkUtils; 45 import com.android.settingslib.net.DataUsageController; 46 import com.android.settingslib.utils.StringUtil; 47 48 import java.util.List; 49 import java.util.concurrent.TimeUnit; 50 51 /** 52 * Business logic for setting the {@link DataUsageSummaryPreference} with the current data usage and 53 * the appropriate summary text. 54 */ 55 public class DataUsageSummaryPreferenceController extends 56 PreferenceController<DataUsageSummaryPreference> { 57 private static final Logger LOG = new Logger(DataUsageSummaryPreferenceController.class); 58 59 private static final long MILLIS_IN_A_DAY = TimeUnit.DAYS.toMillis(1); 60 private static final long MILLIS_IN_AN_HOUR = TimeUnit.HOURS.toMillis(1); 61 private static final long MILLIS_IN_A_MINUTE = TimeUnit.MINUTES.toMillis(1); 62 private static final long MILLIS_IN_A_SECOND = TimeUnit.SECONDS.toMillis(1); 63 private static final int MAX_PROGRESS_BAR_VALUE = 1000; 64 65 @VisibleForTesting 66 static final long WARNING_AGE = TimeUnit.HOURS.toMillis(6L); 67 68 private final SubscriptionManager mSubscriptionManager; 69 private final TelephonyManager mTelephonyManager; 70 private final DataUsageController mDataUsageController; 71 private final NetworkTemplate mDefaultTemplate; 72 73 /** Name of the carrier, or null if not available */ 74 @Nullable 75 private CharSequence mCarrierName; 76 /** The number of registered plans, [0,N] */ 77 private int mDataplanCount; 78 /** The time of the last update in milliseconds since the epoch, or -1 if unknown */ 79 private long mSnapshotTime; 80 /** The size of the first registered plan if one exists. -1 if no information is available. */ 81 private long mDataplanSize = -1; 82 /** 83 * Limit to track. Size of the first registered plan if one exists. Otherwise size of data limit 84 * or warning. 85 */ 86 private long mDataplanTrackingThreshold; 87 /** The number of bytes used since the start of the cycle. */ 88 private long mDataplanUse; 89 /** The ending time of the billing cycle in ms since the epoch */ 90 private long mCycleEnd; 91 private Intent mManageSubscriptionIntent; 92 DataUsageSummaryPreferenceController(Context context, String preferenceKey, FragmentController fragmentController, CarUxRestrictions uxRestrictions)93 public DataUsageSummaryPreferenceController(Context context, String preferenceKey, 94 FragmentController fragmentController, CarUxRestrictions uxRestrictions) { 95 super(context, preferenceKey, fragmentController, uxRestrictions); 96 mSubscriptionManager = context.getSystemService(SubscriptionManager.class); 97 mTelephonyManager = context.getSystemService(TelephonyManager.class); 98 mDataUsageController = new DataUsageController(context); 99 100 int defaultSubId = DataUsageUtils.getDefaultSubscriptionId(mSubscriptionManager); 101 mDefaultTemplate = DataUsageUtils.getMobileNetworkTemplate(mTelephonyManager, defaultSubId); 102 } 103 104 @Override getPreferenceType()105 protected Class<DataUsageSummaryPreference> getPreferenceType() { 106 return DataUsageSummaryPreference.class; 107 } 108 109 @Override getAvailabilityStatus()110 protected int getAvailabilityStatus() { 111 return NetworkUtils.hasSim(mTelephonyManager) ? AVAILABLE : CONDITIONALLY_UNAVAILABLE; 112 } 113 114 @Override onCreateInternal()115 protected void onCreateInternal() { 116 getPreference().setMin(0); 117 getPreference().setMax(MAX_PROGRESS_BAR_VALUE); 118 } 119 120 @Override updateState(DataUsageSummaryPreference preference)121 protected void updateState(DataUsageSummaryPreference preference) { 122 DataUsageController.DataUsageInfo info = mDataUsageController.getDataUsageInfo( 123 mDefaultTemplate); 124 125 if (mSubscriptionManager != null) { 126 refreshDataplanInfo(info); 127 } 128 129 preference.setTitle(getUsageText()); 130 preference.setManageSubscriptionIntent(mManageSubscriptionIntent); 131 132 preference.setDataLimitText(getLimitText(info)); 133 preference.setRemainingBillingCycleText(getRemainingBillingCycleTimeText()); 134 135 // Carrier Info has special styling based on when it was last updated. 136 preference.setCarrierInfoText(getCarrierInfoText()); 137 long updateAgeMillis = calculateTruncatedUpdateAge(mSnapshotTime); 138 if (updateAgeMillis <= WARNING_AGE) { 139 preference.setCarrierInfoTextStyle(R.style.DataUsageSummaryCarrierInfoTextAppearance); 140 } else { 141 preference.setCarrierInfoTextStyle( 142 R.style.DataUsageSummaryCarrierInfoWarningTextAppearance); 143 } 144 145 // Set the progress bar values. 146 preference.setMinLabel(DataUsageUtils.bytesToIecUnits(getContext(), /* byteValue= */ 0)); 147 preference.setMaxLabel( 148 DataUsageUtils.bytesToIecUnits(getContext(), mDataplanTrackingThreshold)); 149 preference.setProgress(scaleUsage(mDataplanUse, mDataplanTrackingThreshold)); 150 } 151 getUsageText()152 private CharSequence getUsageText() { 153 Formatter.BytesResult usedResult = Formatter.formatBytes(getContext().getResources(), 154 mDataplanUse, Formatter.FLAG_CALCULATE_ROUNDED | Formatter.FLAG_IEC_UNITS); 155 SpannableString usageNumberText = new SpannableString(usedResult.value); 156 int textSize = getContext().getResources().getDimensionPixelSize( 157 R.dimen.usage_number_text_size); 158 159 // Set the usage text (only the number) to the size defined by usage_number_text_size. 160 usageNumberText.setSpan(new AbsoluteSizeSpan(textSize), /* start= */ 0, 161 usageNumberText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 162 163 CharSequence template = getContext().getText(R.string.data_used_formatted); 164 CharSequence usageText = TextUtils.expandTemplate(template, usageNumberText, 165 usedResult.units); 166 return usageText; 167 } 168 169 getLimitText(DataUsageController.DataUsageInfo info)170 private CharSequence getLimitText(DataUsageController.DataUsageInfo info) { 171 if (info.warningLevel > 0 && info.limitLevel > 0) { 172 return TextUtils.expandTemplate( 173 getContext().getText(R.string.cell_data_warning_and_limit), 174 DataUsageUtils.bytesToIecUnits(getContext(), info.warningLevel), 175 DataUsageUtils.bytesToIecUnits(getContext(), info.limitLevel)); 176 } else if (info.warningLevel > 0) { 177 return TextUtils.expandTemplate(getContext().getText(R.string.cell_data_warning), 178 DataUsageUtils.bytesToIecUnits(getContext(), info.warningLevel)); 179 } else if (info.limitLevel > 0) { 180 return TextUtils.expandTemplate(getContext().getText(R.string.cell_data_limit), 181 DataUsageUtils.bytesToIecUnits(getContext(), info.limitLevel)); 182 } 183 184 return null; 185 } 186 getRemainingBillingCycleTimeText()187 private CharSequence getRemainingBillingCycleTimeText() { 188 long millisLeft = mCycleEnd - System.currentTimeMillis(); 189 if (millisLeft <= 0) { 190 return getContext().getString(R.string.billing_cycle_none_left); 191 } else { 192 int daysLeft = (int) (millisLeft / MILLIS_IN_A_DAY); 193 return daysLeft < 1 194 ? getContext().getString(R.string.billing_cycle_less_than_one_day_left) 195 : getContext().getResources().getQuantityString( 196 R.plurals.billing_cycle_days_left, daysLeft, daysLeft); 197 } 198 } 199 getCarrierInfoText()200 private CharSequence getCarrierInfoText() { 201 if (mDataplanCount > 0 && mSnapshotTime >= 0L) { 202 long updateAgeMillis = calculateTruncatedUpdateAge(mSnapshotTime); 203 204 int textResourceId; 205 CharSequence updateTime = null; 206 if (updateAgeMillis == 0) { 207 if (mCarrierName != null) { 208 textResourceId = R.string.carrier_and_update_now_text; 209 } else { 210 textResourceId = R.string.no_carrier_update_now_text; 211 } 212 } else { 213 if (mCarrierName != null) { 214 textResourceId = R.string.carrier_and_update_text; 215 } else { 216 textResourceId = R.string.no_carrier_update_text; 217 } 218 updateTime = StringUtil.formatElapsedTime(getContext(), 219 updateAgeMillis, /* withSeconds= */ false); 220 } 221 return TextUtils.expandTemplate(getContext().getText(textResourceId), mCarrierName, 222 updateTime); 223 } 224 225 return null; 226 } 227 refreshDataplanInfo(DataUsageController.DataUsageInfo info)228 private void refreshDataplanInfo(DataUsageController.DataUsageInfo info) { 229 // Reset data before overwriting. 230 mCarrierName = null; 231 mDataplanCount = 0; 232 mSnapshotTime = -1L; 233 mDataplanSize = -1L; 234 mDataplanTrackingThreshold = getSummaryLimit(info); 235 mDataplanUse = info.usageLevel; 236 mCycleEnd = info.cycleEnd; 237 238 int defaultSubId = SubscriptionManager.getDefaultSubscriptionId(); 239 SubscriptionInfo subInfo = mSubscriptionManager.getDefaultDataSubscriptionInfo(); 240 if (subInfo != null) { 241 mCarrierName = subInfo.getCarrierName(); 242 List<SubscriptionPlan> plans = mSubscriptionManager.getSubscriptionPlans(defaultSubId); 243 SubscriptionPlan primaryPlan = DataUsageUtils.getPrimaryPlan(mSubscriptionManager, 244 defaultSubId); 245 if (primaryPlan != null) { 246 mDataplanCount = plans.size(); 247 mDataplanSize = primaryPlan.getDataLimitBytes(); 248 if (mDataplanSize == SubscriptionPlan.BYTES_UNLIMITED) { 249 mDataplanSize = -1L; 250 } 251 mDataplanTrackingThreshold = mDataplanSize; 252 mDataplanUse = primaryPlan.getDataUsageBytes(); 253 254 RecurrenceRule rule = primaryPlan.getCycleRule(); 255 if (rule != null && rule.start != null && rule.end != null) { 256 mCycleEnd = rule.end.toEpochSecond() * MILLIS_IN_A_SECOND; 257 } 258 mSnapshotTime = primaryPlan.getDataUsageTime(); 259 } 260 } 261 mManageSubscriptionIntent = createManageSubscriptionIntent(defaultSubId); 262 } 263 264 /** 265 * Create an {@link Intent} that can be launched towards the carrier app 266 * that is currently defining the billing relationship plan through 267 * {@link INetworkPolicyManager#setSubscriptionPlans(int, SubscriptionPlan [], String)}. 268 * 269 * @return ready to launch Intent targeted towards the carrier app, or 270 * {@code null} if no carrier app is defined, or if the defined 271 * carrier app provides no management activity. 272 */ createManageSubscriptionIntent(int subId)273 private Intent createManageSubscriptionIntent(int subId) { 274 INetworkPolicyManager networkPolicyManager = INetworkPolicyManager.Stub.asInterface( 275 ServiceManager.getService(Context.NETWORK_POLICY_SERVICE)); 276 String owner = ""; 277 try { 278 owner = networkPolicyManager.getSubscriptionPlansOwner(subId); 279 } catch (Exception ex) { 280 LOG.w("Fail to get subscription plan owner for subId " + subId, ex); 281 } 282 283 if (TextUtils.isEmpty(owner)) { 284 return null; 285 } 286 287 List<SubscriptionPlan> plans = mSubscriptionManager.getSubscriptionPlans(subId); 288 if (plans.isEmpty()) { 289 return null; 290 } 291 292 Intent intent = new Intent(SubscriptionManager.ACTION_MANAGE_SUBSCRIPTION_PLANS); 293 intent.setPackage(owner); 294 intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId); 295 296 if (getContext().getPackageManager().queryIntentActivities(intent, 297 PackageManager.MATCH_DEFAULT_ONLY).isEmpty()) { 298 return null; 299 } 300 301 return intent; 302 } 303 304 /** Scales the current usage to be an integer between 0 and {@link #MAX_PROGRESS_BAR_VALUE}. */ scaleUsage(long usage, long maxUsage)305 private int scaleUsage(long usage, long maxUsage) { 306 if (maxUsage == 0) { 307 return 0; 308 } 309 return (int) ((usage / (float) maxUsage) * MAX_PROGRESS_BAR_VALUE); 310 } 311 312 /** 313 * Gets the max displayed limit based on {@link DataUsageController.DataUsageInfo}. 314 * 315 * @return the most appropriate limit for the data usage summary. Use the total usage when it 316 * is higher than the limit and warning level. Use the limit when it is set and less than usage. 317 * Otherwise use warning level. 318 */ getSummaryLimit(DataUsageController.DataUsageInfo info)319 private static long getSummaryLimit(DataUsageController.DataUsageInfo info) { 320 long limit = info.limitLevel; 321 if (limit <= 0) { 322 limit = info.warningLevel; 323 } 324 if (info.usageLevel > limit) { 325 limit = info.usageLevel; 326 } 327 return limit; 328 } 329 330 /** 331 * Returns the time since the last carrier update, as defined by {@link #mSnapshotTime}, 332 * truncated to the nearest day / hour / minute in milliseconds, or 0 if less than 1 min. 333 */ calculateTruncatedUpdateAge(long snapshotTime)334 private long calculateTruncatedUpdateAge(long snapshotTime) { 335 long updateAgeMillis = System.currentTimeMillis() - snapshotTime; 336 337 // Round to nearest whole unit 338 if (updateAgeMillis >= MILLIS_IN_A_DAY) { 339 return (updateAgeMillis / MILLIS_IN_A_DAY) * MILLIS_IN_A_DAY; 340 } else if (updateAgeMillis >= MILLIS_IN_AN_HOUR) { 341 return (updateAgeMillis / MILLIS_IN_AN_HOUR) * MILLIS_IN_AN_HOUR; 342 } else if (updateAgeMillis >= MILLIS_IN_A_MINUTE) { 343 return (updateAgeMillis / MILLIS_IN_A_MINUTE) * MILLIS_IN_A_MINUTE; 344 } else { 345 return 0; 346 } 347 } 348 } 349