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