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.net.NetworkPolicyManager; 23 import android.net.NetworkTemplate; 24 import android.telephony.SubscriptionInfo; 25 import android.telephony.SubscriptionManager; 26 import android.telephony.SubscriptionPlan; 27 import android.text.TextUtils; 28 import android.util.Log; 29 import android.util.RecurrenceRule; 30 31 import androidx.annotation.VisibleForTesting; 32 import androidx.preference.Preference; 33 import androidx.preference.PreferenceFragmentCompat; 34 import androidx.recyclerview.widget.RecyclerView; 35 36 import com.android.internal.util.CollectionUtils; 37 import com.android.settings.R; 38 import com.android.settings.core.BasePreferenceController; 39 import com.android.settings.core.PreferenceControllerMixin; 40 import com.android.settings.widget.EntityHeaderController; 41 import com.android.settingslib.NetworkPolicyEditor; 42 import com.android.settingslib.core.lifecycle.Lifecycle; 43 import com.android.settingslib.core.lifecycle.LifecycleObserver; 44 import com.android.settingslib.core.lifecycle.events.OnStart; 45 import com.android.settingslib.net.DataUsageController; 46 47 import java.util.List; 48 49 /** 50 * This is the controller for a data usage header that retrieves carrier data from the new 51 * subscriptions framework API if available. The controller reads subscription information from the 52 * framework and falls back to legacy usage data if none are available. 53 */ 54 public class DataUsageSummaryPreferenceController extends BasePreferenceController 55 implements PreferenceControllerMixin, LifecycleObserver, OnStart { 56 57 private static final String TAG = "DataUsageController"; 58 private static final String KEY = "status_header"; 59 private static final long PETA = 1000000000000000L; 60 private static final float RELATIVE_SIZE_LARGE = 1.25f * 1.25f; // (1/0.8)^2 61 private static final float RELATIVE_SIZE_SMALL = 1.0f / RELATIVE_SIZE_LARGE; // 0.8^2 62 63 private final Activity mActivity; 64 private final EntityHeaderController mEntityHeaderController; 65 private final Lifecycle mLifecycle; 66 private final PreferenceFragmentCompat mFragment; 67 protected final DataUsageController mDataUsageController; 68 protected final DataUsageInfoController mDataInfoController; 69 private final NetworkTemplate mDefaultTemplate; 70 protected final NetworkPolicyEditor mPolicyEditor; 71 private final int mDataUsageTemplate; 72 private final boolean mHasMobileData; 73 private final SubscriptionManager mSubscriptionManager; 74 75 /** Name of the carrier, or null if not available */ 76 private CharSequence mCarrierName; 77 78 /** The number of registered plans, [0,N] */ 79 private int mDataplanCount; 80 81 /** The time of the last update in milliseconds since the epoch, or -1 if unknown */ 82 private long mSnapshotTime; 83 84 /** 85 * The size of the first registered plan if one exists or the size of the warning if it is set. 86 * -1 if no information is available. 87 */ 88 private long mDataplanSize; 89 /** The "size" of the data usage bar, i.e. the amount of data its rhs end represents */ 90 private long mDataBarSize; 91 /** The number of bytes used since the start of the cycle. */ 92 private long mDataplanUse; 93 /** The starting time of the billing cycle in ms since the epoch */ 94 private long mCycleStart; 95 /** The ending time of the billing cycle in ms since the epoch */ 96 private long mCycleEnd; 97 /** The subscription that we should show usage for. */ 98 private int mSubscriptionId; 99 100 private Intent mManageSubscriptionIntent; 101 DataUsageSummaryPreferenceController(Activity activity, Lifecycle lifecycle, PreferenceFragmentCompat fragment, int subscriptionId)102 public DataUsageSummaryPreferenceController(Activity activity, 103 Lifecycle lifecycle, PreferenceFragmentCompat fragment, int subscriptionId) { 104 super(activity, KEY); 105 106 mActivity = activity; 107 mEntityHeaderController = EntityHeaderController.newInstance(activity, 108 fragment, null); 109 mLifecycle = lifecycle; 110 mFragment = fragment; 111 mSubscriptionId = subscriptionId; 112 113 mDefaultTemplate = DataUsageUtils.getDefaultTemplate(activity, mSubscriptionId); 114 NetworkPolicyManager policyManager = activity.getSystemService(NetworkPolicyManager.class); 115 mPolicyEditor = new NetworkPolicyEditor(policyManager); 116 117 mHasMobileData = DataUsageUtils.hasMobileData(activity) 118 && mSubscriptionId != SubscriptionManager.INVALID_SUBSCRIPTION_ID; 119 120 mDataUsageController = new DataUsageController(activity); 121 mDataUsageController.setSubscriptionId(mSubscriptionId); 122 mDataInfoController = new DataUsageInfoController(); 123 124 if (mHasMobileData) { 125 mDataUsageTemplate = R.string.cell_data_template; 126 } else if (DataUsageUtils.hasWifiRadio(activity)) { 127 mDataUsageTemplate = R.string.wifi_data_template; 128 } else { 129 mDataUsageTemplate = R.string.ethernet_data_template; 130 } 131 132 mSubscriptionManager = (SubscriptionManager) 133 mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); 134 } 135 136 @VisibleForTesting DataUsageSummaryPreferenceController( DataUsageController dataUsageController, DataUsageInfoController dataInfoController, NetworkTemplate defaultTemplate, NetworkPolicyEditor policyEditor, int dataUsageTemplate, boolean hasMobileData, SubscriptionManager subscriptionManager, Activity activity, Lifecycle lifecycle, EntityHeaderController entityHeaderController, PreferenceFragmentCompat fragment, int subscriptionId)137 DataUsageSummaryPreferenceController( 138 DataUsageController dataUsageController, 139 DataUsageInfoController dataInfoController, 140 NetworkTemplate defaultTemplate, 141 NetworkPolicyEditor policyEditor, 142 int dataUsageTemplate, 143 boolean hasMobileData, 144 SubscriptionManager subscriptionManager, 145 Activity activity, 146 Lifecycle lifecycle, 147 EntityHeaderController entityHeaderController, 148 PreferenceFragmentCompat fragment, 149 int subscriptionId) { 150 super(activity, KEY); 151 mDataUsageController = dataUsageController; 152 mDataInfoController = dataInfoController; 153 mDefaultTemplate = defaultTemplate; 154 mPolicyEditor = policyEditor; 155 mDataUsageTemplate = dataUsageTemplate; 156 mHasMobileData = hasMobileData; 157 mSubscriptionManager = subscriptionManager; 158 mActivity = activity; 159 mLifecycle = lifecycle; 160 mEntityHeaderController = entityHeaderController; 161 mFragment = fragment; 162 mSubscriptionId = subscriptionId; 163 } 164 165 @Override onStart()166 public void onStart() { 167 RecyclerView view = mFragment.getListView(); 168 mEntityHeaderController.setRecyclerView(view, mLifecycle); 169 mEntityHeaderController.styleActionBar(mActivity); 170 } 171 172 @VisibleForTesting setPlanValues(int dataPlanCount, long dataPlanSize, long dataPlanUse)173 void setPlanValues(int dataPlanCount, long dataPlanSize, long dataPlanUse) { 174 mDataplanCount = dataPlanCount; 175 mDataplanSize = dataPlanSize; 176 mDataBarSize = dataPlanSize; 177 mDataplanUse = dataPlanUse; 178 } 179 180 @VisibleForTesting setCarrierValues(String carrierName, long snapshotTime, long cycleEnd, Intent intent)181 void setCarrierValues(String carrierName, long snapshotTime, long cycleEnd, Intent intent) { 182 mCarrierName = carrierName; 183 mSnapshotTime = snapshotTime; 184 mCycleEnd = cycleEnd; 185 mManageSubscriptionIntent = intent; 186 } 187 188 @Override getAvailabilityStatus()189 public int getAvailabilityStatus() { 190 return DataUsageUtils.hasSim(mActivity) 191 || DataUsageUtils.hasWifiRadio(mContext) ? AVAILABLE : CONDITIONALLY_UNAVAILABLE; 192 } 193 194 @Override updateState(Preference preference)195 public void updateState(Preference preference) { 196 DataUsageSummaryPreference summaryPreference = (DataUsageSummaryPreference) preference; 197 198 final DataUsageController.DataUsageInfo info; 199 if (DataUsageUtils.hasSim(mActivity)) { 200 info = mDataUsageController.getDataUsageInfo(mDefaultTemplate); 201 mDataInfoController.updateDataLimit(info, mPolicyEditor.getPolicy(mDefaultTemplate)); 202 summaryPreference.setWifiMode(/* isWifiMode */ false, 203 /* usagePeriod */ null, /* isSingleWifi */ false); 204 } else { 205 info = mDataUsageController.getDataUsageInfo( 206 NetworkTemplate.buildTemplateWifiWildcard()); 207 summaryPreference.setWifiMode(/* isWifiMode */ true, /* usagePeriod */ 208 info.period, /* isSingleWifi */ false); 209 summaryPreference.setLimitInfo(null); 210 summaryPreference.setUsageNumbers(info.usageLevel, 211 /* dataPlanSize */ -1L, 212 /* hasMobileData */ true); 213 summaryPreference.setChartEnabled(false); 214 summaryPreference.setUsageInfo(info.cycleEnd, 215 /* snapshotTime */ -1L, 216 /* carrierName */ null, 217 /* numPlans */ 0, 218 /* launchIntent */ null); 219 return; 220 } 221 222 if (mSubscriptionManager != null) { 223 refreshDataplanInfo(info); 224 } 225 226 if (info.warningLevel > 0 && info.limitLevel > 0) { 227 summaryPreference.setLimitInfo(TextUtils.expandTemplate( 228 mContext.getText(R.string.cell_data_warning_and_limit), 229 DataUsageUtils.formatDataUsage(mContext, info.warningLevel), 230 DataUsageUtils.formatDataUsage(mContext, info.limitLevel))); 231 } else if (info.warningLevel > 0) { 232 summaryPreference.setLimitInfo(TextUtils.expandTemplate( 233 mContext.getText(R.string.cell_data_warning), 234 DataUsageUtils.formatDataUsage(mContext, info.warningLevel))); 235 } else if (info.limitLevel > 0) { 236 summaryPreference.setLimitInfo(TextUtils.expandTemplate( 237 mContext.getText(R.string.cell_data_limit), 238 DataUsageUtils.formatDataUsage(mContext, info.limitLevel))); 239 } else { 240 summaryPreference.setLimitInfo(null); 241 } 242 243 summaryPreference.setUsageNumbers(mDataplanUse, mDataplanSize, mHasMobileData); 244 245 if (mDataBarSize <= 0) { 246 summaryPreference.setChartEnabled(false); 247 } else { 248 summaryPreference.setChartEnabled(true); 249 summaryPreference.setLabels(DataUsageUtils.formatDataUsage(mContext, 0 /* sizeBytes */), 250 DataUsageUtils.formatDataUsage(mContext, mDataBarSize)); 251 summaryPreference.setProgress(mDataplanUse / (float) mDataBarSize); 252 } 253 summaryPreference.setUsageInfo(mCycleEnd, mSnapshotTime, mCarrierName, 254 mDataplanCount, mManageSubscriptionIntent); 255 } 256 257 // TODO(b/70950124) add test for this method once the robolectric shadow run script is 258 // completed (b/3526807) refreshDataplanInfo(DataUsageController.DataUsageInfo info)259 private void refreshDataplanInfo(DataUsageController.DataUsageInfo info) { 260 // reset data before overwriting 261 mCarrierName = null; 262 mDataplanCount = 0; 263 mDataplanSize = -1L; 264 mDataBarSize = mDataInfoController.getSummaryLimit(info); 265 mDataplanUse = info.usageLevel; 266 mCycleStart = info.cycleStart; 267 mCycleEnd = info.cycleEnd; 268 mSnapshotTime = -1L; 269 270 SubscriptionInfo subInfo = mSubscriptionManager.getActiveSubscriptionInfo(mSubscriptionId); 271 if (subInfo == null) { 272 subInfo = mSubscriptionManager.getAllSubscriptionInfoList().stream().filter( 273 i -> i.getSubscriptionId() == mSubscriptionId).findFirst().orElse(null); 274 } 275 if (subInfo != null && mHasMobileData) { 276 mCarrierName = subInfo.getCarrierName(); 277 List<SubscriptionPlan> plans = mSubscriptionManager.getSubscriptionPlans( 278 mSubscriptionId); 279 final SubscriptionPlan primaryPlan = getPrimaryPlan(mSubscriptionManager, 280 mSubscriptionId); 281 282 if (primaryPlan != null) { 283 mDataplanCount = plans.size(); 284 mDataplanSize = primaryPlan.getDataLimitBytes(); 285 if (unlimited(mDataplanSize)) { 286 mDataplanSize = -1L; 287 } 288 mDataBarSize = mDataplanSize; 289 mDataplanUse = primaryPlan.getDataUsageBytes(); 290 291 RecurrenceRule rule = primaryPlan.getCycleRule(); 292 if (rule != null && rule.start != null && rule.end != null) { 293 mCycleStart = rule.start.toEpochSecond() * 1000L; 294 mCycleEnd = rule.end.toEpochSecond() * 1000L; 295 } 296 mSnapshotTime = primaryPlan.getDataUsageTime(); 297 } 298 } 299 mManageSubscriptionIntent = 300 mSubscriptionManager.createManageSubscriptionIntent(mSubscriptionId); 301 Log.i(TAG, "Have " + mDataplanCount + " plans, dflt sub-id " + mSubscriptionId 302 + ", intent " + mManageSubscriptionIntent); 303 } 304 getPrimaryPlan(SubscriptionManager subManager, int primaryId)305 public static SubscriptionPlan getPrimaryPlan(SubscriptionManager subManager, int primaryId) { 306 List<SubscriptionPlan> plans = subManager.getSubscriptionPlans(primaryId); 307 if (CollectionUtils.isEmpty(plans)) { 308 return null; 309 } 310 // First plan in the list is the primary plan 311 SubscriptionPlan plan = plans.get(0); 312 return plan.getDataLimitBytes() > 0 313 && saneSize(plan.getDataUsageBytes()) 314 && plan.getCycleRule() != null ? plan : null; 315 } 316 saneSize(long value)317 private static boolean saneSize(long value) { 318 return value >= 0L && value < PETA; 319 } 320 unlimited(long size)321 public static boolean unlimited(long size) { 322 return size == SubscriptionPlan.BYTES_UNLIMITED; 323 } 324 } 325