1 /* 2 * Copyright (C) 2018 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.settings.datausage; 18 19 import android.app.Activity; 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.NetworkPolicyManager; 25 import android.net.NetworkTemplate; 26 import android.os.ServiceManager; 27 import android.telephony.SubscriptionInfo; 28 import android.telephony.SubscriptionManager; 29 import android.telephony.SubscriptionPlan; 30 import android.text.TextUtils; 31 import android.util.Log; 32 import android.util.RecurrenceRule; 33 34 import androidx.annotation.VisibleForTesting; 35 import androidx.preference.Preference; 36 import androidx.preference.PreferenceFragmentCompat; 37 import androidx.recyclerview.widget.RecyclerView; 38 39 import com.android.internal.util.CollectionUtils; 40 import com.android.settings.R; 41 import com.android.settings.core.PreferenceControllerMixin; 42 import com.android.settings.datausage.lib.DataUsageLib; 43 import com.android.settings.network.ProxySubscriptionManager; 44 import com.android.settings.network.telephony.TelephonyBasePreferenceController; 45 import com.android.settings.widget.EntityHeaderController; 46 import com.android.settingslib.NetworkPolicyEditor; 47 import com.android.settingslib.core.lifecycle.Lifecycle; 48 import com.android.settingslib.core.lifecycle.LifecycleObserver; 49 import com.android.settingslib.core.lifecycle.events.OnStart; 50 import com.android.settingslib.net.DataUsageController; 51 import com.android.settingslib.utils.ThreadUtils; 52 53 import java.util.List; 54 import java.util.concurrent.Future; 55 56 /** 57 * This is the controller for a data usage header that retrieves carrier data from the new 58 * subscriptions framework API if available. The controller reads subscription information from the 59 * framework and falls back to legacy usage data if none are available. 60 */ 61 public class DataUsageSummaryPreferenceController extends TelephonyBasePreferenceController 62 implements PreferenceControllerMixin, LifecycleObserver, OnStart { 63 64 private static final String TAG = "DataUsageController"; 65 private static final String KEY = "status_header"; 66 private static final long PETA = 1000000000000000L; 67 private static final float RELATIVE_SIZE_LARGE = 1.25f * 1.25f; // (1/0.8)^2 68 private static final float RELATIVE_SIZE_SMALL = 1.0f / RELATIVE_SIZE_LARGE; // 0.8^2 69 70 private EntityHeaderController mEntityHeaderController; 71 private final Lifecycle mLifecycle; 72 private final PreferenceFragmentCompat mFragment; 73 protected DataUsageController mDataUsageController; 74 protected DataUsageInfoController mDataInfoController; 75 private NetworkTemplate mDefaultTemplate; 76 protected NetworkPolicyEditor mPolicyEditor; 77 private int mDataUsageTemplate; 78 private boolean mHasMobileData; 79 80 /** Name of the carrier, or null if not available */ 81 private CharSequence mCarrierName; 82 83 /** The number of registered plans, [0,N] */ 84 private int mDataplanCount; 85 86 /** The time of the last update in milliseconds since the epoch, or -1 if unknown */ 87 private long mSnapshotTime; 88 89 /** 90 * The size of the first registered plan if one exists or the size of the warning if it is set. 91 * -1 if no information is available. 92 */ 93 private long mDataplanSize; 94 /** The "size" of the data usage bar, i.e. the amount of data its rhs end represents */ 95 private long mDataBarSize; 96 /** The number of bytes used since the start of the cycle. */ 97 private long mDataplanUse; 98 /** The starting time of the billing cycle in ms since the epoch */ 99 private long mCycleStart; 100 /** The ending time of the billing cycle in ms since the epoch */ 101 private long mCycleEnd; 102 103 private Intent mManageSubscriptionIntent; 104 105 private Future<Long> mHistoricalUsageLevel; 106 DataUsageSummaryPreferenceController(Activity activity, Lifecycle lifecycle, PreferenceFragmentCompat fragment, int subscriptionId)107 public DataUsageSummaryPreferenceController(Activity activity, 108 Lifecycle lifecycle, PreferenceFragmentCompat fragment, int subscriptionId) { 109 super(activity, KEY); 110 111 mLifecycle = lifecycle; 112 mFragment = fragment; 113 init(subscriptionId); 114 } 115 116 /** 117 * Initialize based on subscription ID provided 118 * @param subscriptionId is the target subscriptionId 119 */ init(int subscriptionId)120 public void init(int subscriptionId) { 121 mSubId = subscriptionId; 122 mHasMobileData = DataUsageUtils.hasMobileData(mContext); 123 mDataUsageController = null; 124 } 125 updateConfiguration(Context context, int subscriptionId, SubscriptionInfo subInfo)126 private void updateConfiguration(Context context, 127 int subscriptionId, SubscriptionInfo subInfo) { 128 final NetworkPolicyManager policyManager = 129 context.getSystemService(NetworkPolicyManager.class); 130 mPolicyEditor = new NetworkPolicyEditor(policyManager); 131 132 mDataUsageController = new DataUsageController(context); 133 mDataUsageController.setSubscriptionId(subscriptionId); 134 mDataInfoController = new DataUsageInfoController(); 135 136 if (subInfo != null) { 137 mDataUsageTemplate = R.string.cell_data_template; 138 mDefaultTemplate = DataUsageLib.getMobileTemplate(context, subscriptionId); 139 } else if (DataUsageUtils.hasWifiRadio(context)) { 140 mDataUsageTemplate = R.string.wifi_data_template; 141 mDefaultTemplate = NetworkTemplate.buildTemplateWifiWildcard(); 142 } else { 143 mDataUsageTemplate = R.string.ethernet_data_template; 144 mDefaultTemplate = DataUsageUtils.getDefaultTemplate(context, subscriptionId); 145 } 146 } 147 148 @VisibleForTesting DataUsageSummaryPreferenceController( DataUsageController dataUsageController, DataUsageInfoController dataInfoController, NetworkTemplate defaultTemplate, NetworkPolicyEditor policyEditor, int dataUsageTemplate, Activity activity, Lifecycle lifecycle, EntityHeaderController entityHeaderController, PreferenceFragmentCompat fragment, int subscriptionId)149 DataUsageSummaryPreferenceController( 150 DataUsageController dataUsageController, 151 DataUsageInfoController dataInfoController, 152 NetworkTemplate defaultTemplate, 153 NetworkPolicyEditor policyEditor, 154 int dataUsageTemplate, 155 Activity activity, 156 Lifecycle lifecycle, 157 EntityHeaderController entityHeaderController, 158 PreferenceFragmentCompat fragment, 159 int subscriptionId) { 160 super(activity, KEY); 161 mDataUsageController = dataUsageController; 162 mDataInfoController = dataInfoController; 163 mDefaultTemplate = defaultTemplate; 164 mPolicyEditor = policyEditor; 165 mDataUsageTemplate = dataUsageTemplate; 166 mHasMobileData = true; 167 mLifecycle = lifecycle; 168 mEntityHeaderController = entityHeaderController; 169 mFragment = fragment; 170 mSubId = subscriptionId; 171 } 172 173 @Override onStart()174 public void onStart() { 175 if (mEntityHeaderController == null) { 176 mEntityHeaderController = 177 EntityHeaderController.newInstance((Activity) mContext, mFragment, null); 178 } 179 RecyclerView view = mFragment.getListView(); 180 mEntityHeaderController.setRecyclerView(view, mLifecycle); 181 mEntityHeaderController.styleActionBar((Activity) mContext); 182 } 183 184 @VisibleForTesting getSubscriptionPlans(int subscriptionId)185 List<SubscriptionPlan> getSubscriptionPlans(int subscriptionId) { 186 return ProxySubscriptionManager.getInstance(mContext).get() 187 .getSubscriptionPlans(subscriptionId); 188 } 189 190 @VisibleForTesting getSubscriptionInfo(int subscriptionId)191 SubscriptionInfo getSubscriptionInfo(int subscriptionId) { 192 if (!mHasMobileData) { 193 return null; 194 } 195 return ProxySubscriptionManager.getInstance(mContext) 196 .getAccessibleSubscriptionInfo(subscriptionId); 197 } 198 199 @Override getAvailabilityStatus(int subId)200 public int getAvailabilityStatus(int subId) { 201 return (getSubscriptionInfo(subId) != null) 202 || DataUsageUtils.hasWifiRadio(mContext) ? AVAILABLE : CONDITIONALLY_UNAVAILABLE; 203 } 204 205 @Override updateState(Preference preference)206 public void updateState(Preference preference) { 207 DataUsageSummaryPreference summaryPreference = (DataUsageSummaryPreference) preference; 208 209 final SubscriptionInfo subInfo = getSubscriptionInfo(mSubId); 210 if (mDataUsageController == null) { 211 updateConfiguration(mContext, mSubId, subInfo); 212 } 213 214 mHistoricalUsageLevel = ThreadUtils.postOnBackgroundThread(() -> 215 mDataUsageController.getHistoricalUsageLevel(mDefaultTemplate)); 216 217 final DataUsageController.DataUsageInfo info = 218 mDataUsageController.getDataUsageInfo(mDefaultTemplate); 219 220 long usageLevel = info.usageLevel; 221 222 if (subInfo != null) { 223 mDataInfoController.updateDataLimit(info, mPolicyEditor.getPolicy(mDefaultTemplate)); 224 summaryPreference.setWifiMode(/* isWifiMode */ false, 225 /* usagePeriod */ null, /* isSingleWifi */ false); 226 } else { 227 summaryPreference.setWifiMode(/* isWifiMode */ true, /* usagePeriod */ 228 info.period, /* isSingleWifi */ false); 229 summaryPreference.setLimitInfo(null); 230 summaryPreference.setUsageNumbers(displayUsageLevel(usageLevel), 231 /* dataPlanSize */ -1L, 232 /* hasMobileData */ true); 233 summaryPreference.setChartEnabled(false); 234 summaryPreference.setUsageInfo(info.cycleEnd, 235 /* snapshotTime */ -1L, 236 /* carrierName */ null, 237 /* numPlans */ 0, 238 /* launchIntent */ null); 239 return; 240 } 241 242 refreshDataplanInfo(info, subInfo); 243 244 if (info.warningLevel > 0 && info.limitLevel > 0) { 245 summaryPreference.setLimitInfo(TextUtils.expandTemplate( 246 mContext.getText(R.string.cell_data_warning_and_limit), 247 DataUsageUtils.formatDataUsage(mContext, info.warningLevel), 248 DataUsageUtils.formatDataUsage(mContext, info.limitLevel))); 249 } else if (info.warningLevel > 0) { 250 summaryPreference.setLimitInfo(TextUtils.expandTemplate( 251 mContext.getText(R.string.cell_data_warning), 252 DataUsageUtils.formatDataUsage(mContext, info.warningLevel))); 253 } else if (info.limitLevel > 0) { 254 summaryPreference.setLimitInfo(TextUtils.expandTemplate( 255 mContext.getText(R.string.cell_data_limit), 256 DataUsageUtils.formatDataUsage(mContext, info.limitLevel))); 257 } else { 258 summaryPreference.setLimitInfo(null); 259 } 260 261 if ((mDataplanUse <= 0L) && (mSnapshotTime < 0)) { 262 Log.d(TAG, "Display data usage from history"); 263 mDataplanUse = displayUsageLevel(usageLevel); 264 mSnapshotTime = -1L; 265 } 266 267 summaryPreference.setUsageNumbers(mDataplanUse, mDataplanSize, mHasMobileData); 268 269 if (mDataBarSize <= 0) { 270 summaryPreference.setChartEnabled(false); 271 } else { 272 summaryPreference.setChartEnabled(true); 273 summaryPreference.setLabels(DataUsageUtils.formatDataUsage(mContext, 0 /* sizeBytes */), 274 DataUsageUtils.formatDataUsage(mContext, mDataBarSize)); 275 summaryPreference.setProgress(mDataplanUse / (float) mDataBarSize); 276 } 277 summaryPreference.setUsageInfo(mCycleEnd, mSnapshotTime, mCarrierName, 278 mDataplanCount, mManageSubscriptionIntent); 279 } 280 displayUsageLevel(long usageLevel)281 private long displayUsageLevel(long usageLevel) { 282 if (usageLevel > 0) { 283 return usageLevel; 284 } 285 try { 286 usageLevel = mHistoricalUsageLevel.get(); 287 } catch (Exception ex) { 288 } 289 return usageLevel; 290 } 291 292 // TODO(b/70950124) add test for this method once the robolectric shadow run script is 293 // completed (b/3526807) refreshDataplanInfo(DataUsageController.DataUsageInfo info, SubscriptionInfo subInfo)294 private void refreshDataplanInfo(DataUsageController.DataUsageInfo info, 295 SubscriptionInfo subInfo) { 296 // reset data before overwriting 297 mCarrierName = null; 298 mDataplanCount = 0; 299 mDataplanSize = -1L; 300 mDataBarSize = mDataInfoController.getSummaryLimit(info); 301 mDataplanUse = info.usageLevel; 302 mCycleStart = info.cycleStart; 303 mCycleEnd = info.cycleEnd; 304 mSnapshotTime = -1L; 305 306 if (subInfo != null && mHasMobileData) { 307 mCarrierName = subInfo.getCarrierName(); 308 final List<SubscriptionPlan> plans = getSubscriptionPlans(mSubId); 309 final SubscriptionPlan primaryPlan = getPrimaryPlan(plans); 310 311 if (primaryPlan != null) { 312 mDataplanCount = plans.size(); 313 mDataplanSize = primaryPlan.getDataLimitBytes(); 314 if (unlimited(mDataplanSize)) { 315 mDataplanSize = -1L; 316 } 317 mDataBarSize = mDataplanSize; 318 mDataplanUse = primaryPlan.getDataUsageBytes(); 319 320 RecurrenceRule rule = primaryPlan.getCycleRule(); 321 if (rule != null && rule.start != null && rule.end != null) { 322 mCycleStart = rule.start.toEpochSecond() * 1000L; 323 mCycleEnd = rule.end.toEpochSecond() * 1000L; 324 } 325 mSnapshotTime = primaryPlan.getDataUsageTime(); 326 } 327 } 328 mManageSubscriptionIntent = createManageSubscriptionIntent(mSubId); 329 Log.i(TAG, "Have " + mDataplanCount + " plans, dflt sub-id " + mSubId 330 + ", intent " + mManageSubscriptionIntent); 331 } 332 333 /** 334 * Create an {@link Intent} that can be launched towards the carrier app 335 * that is currently defining the billing relationship plan through 336 * {@link INetworkPolicyManager#setSubscriptionPlans(int, SubscriptionPlan [], String)}. 337 * 338 * @return ready to launch Intent targeted towards the carrier app, or 339 * {@code null} if no carrier app is defined, or if the defined 340 * carrier app provides no management activity. 341 */ 342 @VisibleForTesting createManageSubscriptionIntent(int subId)343 Intent createManageSubscriptionIntent(int subId) { 344 final INetworkPolicyManager iNetPolicyManager = INetworkPolicyManager.Stub.asInterface( 345 ServiceManager.getService(Context.NETWORK_POLICY_SERVICE)); 346 String owner = ""; 347 try { 348 owner = iNetPolicyManager.getSubscriptionPlansOwner(subId); 349 } catch (Exception ex) { 350 Log.w(TAG, "Fail to get subscription plan owner for subId " + subId, ex); 351 } 352 353 if (TextUtils.isEmpty(owner)) { 354 return null; 355 } 356 357 final List<SubscriptionPlan> plans = getSubscriptionPlans(subId); 358 if (plans.isEmpty()) { 359 return null; 360 } 361 362 final Intent intent = new Intent(SubscriptionManager.ACTION_MANAGE_SUBSCRIPTION_PLANS); 363 intent.setPackage(owner); 364 intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId); 365 366 if (mContext.getPackageManager().queryIntentActivities(intent, 367 PackageManager.MATCH_DEFAULT_ONLY).isEmpty()) { 368 return null; 369 } 370 371 return intent; 372 } 373 getPrimaryPlan(List<SubscriptionPlan> plans)374 private static SubscriptionPlan getPrimaryPlan(List<SubscriptionPlan> plans) { 375 if (CollectionUtils.isEmpty(plans)) { 376 return null; 377 } 378 // First plan in the list is the primary plan 379 SubscriptionPlan plan = plans.get(0); 380 return plan.getDataLimitBytes() > 0 381 && saneSize(plan.getDataUsageBytes()) 382 && plan.getCycleRule() != null ? plan : null; 383 } 384 saneSize(long value)385 private static boolean saneSize(long value) { 386 return value >= 0L && value < PETA; 387 } 388 unlimited(long size)389 public static boolean unlimited(long size) { 390 return size == SubscriptionPlan.BYTES_UNLIMITED; 391 } 392 } 393