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 protected 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 = createDataUsageController(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 = new NetworkTemplate.Builder(NetworkTemplate.MATCH_WIFI).build(); 142 } else { 143 mDataUsageTemplate = R.string.ethernet_data_template; 144 mDefaultTemplate = DataUsageUtils.getDefaultTemplate(context, subscriptionId); 145 } 146 } 147 148 @VisibleForTesting createDataUsageController(Context context)149 DataUsageController createDataUsageController(Context context) { 150 return new DataUsageController(context); 151 } 152 153 @VisibleForTesting DataUsageSummaryPreferenceController( DataUsageController dataUsageController, DataUsageInfoController dataInfoController, NetworkTemplate defaultTemplate, NetworkPolicyEditor policyEditor, int dataUsageTemplate, Activity activity, Lifecycle lifecycle, EntityHeaderController entityHeaderController, PreferenceFragmentCompat fragment, int subscriptionId)154 DataUsageSummaryPreferenceController( 155 DataUsageController dataUsageController, 156 DataUsageInfoController dataInfoController, 157 NetworkTemplate defaultTemplate, 158 NetworkPolicyEditor policyEditor, 159 int dataUsageTemplate, 160 Activity activity, 161 Lifecycle lifecycle, 162 EntityHeaderController entityHeaderController, 163 PreferenceFragmentCompat fragment, 164 int subscriptionId) { 165 super(activity, KEY); 166 mDataUsageController = dataUsageController; 167 mDataInfoController = dataInfoController; 168 mDefaultTemplate = defaultTemplate; 169 mPolicyEditor = policyEditor; 170 mDataUsageTemplate = dataUsageTemplate; 171 mHasMobileData = true; 172 mLifecycle = lifecycle; 173 mEntityHeaderController = entityHeaderController; 174 mFragment = fragment; 175 mSubId = subscriptionId; 176 } 177 178 @Override onStart()179 public void onStart() { 180 if (mEntityHeaderController == null) { 181 mEntityHeaderController = 182 EntityHeaderController.newInstance((Activity) mContext, mFragment, null); 183 } 184 RecyclerView view = mFragment.getListView(); 185 mEntityHeaderController.setRecyclerView(view, mLifecycle); 186 } 187 188 @VisibleForTesting getSubscriptionPlans(int subscriptionId)189 List<SubscriptionPlan> getSubscriptionPlans(int subscriptionId) { 190 return ProxySubscriptionManager.getInstance(mContext).get() 191 .getSubscriptionPlans(subscriptionId); 192 } 193 getSubscriptionInfo(int subscriptionId)194 protected SubscriptionInfo getSubscriptionInfo(int subscriptionId) { 195 if (!mHasMobileData) { 196 return null; 197 } 198 return ProxySubscriptionManager.getInstance(mContext) 199 .getAccessibleSubscriptionInfo(subscriptionId); 200 } 201 202 @Override getAvailabilityStatus(int subId)203 public int getAvailabilityStatus(int subId) { 204 return (getSubscriptionInfo(subId) != null) 205 || DataUsageUtils.hasWifiRadio(mContext) ? AVAILABLE : CONDITIONALLY_UNAVAILABLE; 206 } 207 208 @Override updateState(Preference preference)209 public void updateState(Preference preference) { 210 DataUsageSummaryPreference summaryPreference = (DataUsageSummaryPreference) preference; 211 212 final SubscriptionInfo subInfo = getSubscriptionInfo(mSubId); 213 if (mDataUsageController == null) { 214 updateConfiguration(mContext, mSubId, subInfo); 215 } 216 217 mHistoricalUsageLevel = ThreadUtils.postOnBackgroundThread(() -> 218 mDataUsageController.getHistoricalUsageLevel(mDefaultTemplate)); 219 220 final DataUsageController.DataUsageInfo info = 221 mDataUsageController.getDataUsageInfo(mDefaultTemplate); 222 223 long usageLevel = info.usageLevel; 224 225 if (subInfo != null) { 226 mDataInfoController.updateDataLimit(info, mPolicyEditor.getPolicy(mDefaultTemplate)); 227 summaryPreference.setWifiMode(/* isWifiMode */ false, 228 /* usagePeriod */ null, /* isSingleWifi */ false); 229 } else { 230 summaryPreference.setWifiMode(/* isWifiMode */ true, /* usagePeriod */ 231 info.period, /* isSingleWifi */ false); 232 summaryPreference.setLimitInfo(null); 233 summaryPreference.setUsageNumbers(displayUsageLevel(usageLevel), 234 /* dataPlanSize */ -1L, 235 /* hasMobileData */ true); 236 summaryPreference.setChartEnabled(false); 237 summaryPreference.setUsageInfo(info.cycleEnd, 238 /* snapshotTime */ -1L, 239 /* carrierName */ null, 240 /* numPlans */ 0, 241 /* launchIntent */ null); 242 return; 243 } 244 245 refreshDataplanInfo(info, subInfo); 246 247 if (info.warningLevel > 0 && info.limitLevel > 0) { 248 summaryPreference.setLimitInfo(TextUtils.expandTemplate( 249 mContext.getText(R.string.cell_data_warning_and_limit), 250 DataUsageUtils.formatDataUsage(mContext, info.warningLevel), 251 DataUsageUtils.formatDataUsage(mContext, info.limitLevel))); 252 } else if (info.warningLevel > 0) { 253 summaryPreference.setLimitInfo(TextUtils.expandTemplate( 254 mContext.getText(R.string.cell_data_warning), 255 DataUsageUtils.formatDataUsage(mContext, info.warningLevel))); 256 } else if (info.limitLevel > 0) { 257 summaryPreference.setLimitInfo(TextUtils.expandTemplate( 258 mContext.getText(R.string.cell_data_limit), 259 DataUsageUtils.formatDataUsage(mContext, info.limitLevel))); 260 } else { 261 summaryPreference.setLimitInfo(null); 262 } 263 264 if ((mDataplanUse <= 0L) && (mSnapshotTime < 0)) { 265 Log.d(TAG, "Display data usage from history"); 266 mDataplanUse = displayUsageLevel(usageLevel); 267 mSnapshotTime = -1L; 268 } 269 270 summaryPreference.setUsageNumbers(mDataplanUse, mDataplanSize, mHasMobileData); 271 272 if (mDataBarSize <= 0) { 273 summaryPreference.setChartEnabled(false); 274 } else { 275 summaryPreference.setChartEnabled(true); 276 summaryPreference.setLabels(DataUsageUtils.formatDataUsage(mContext, 0 /* sizeBytes */), 277 DataUsageUtils.formatDataUsage(mContext, mDataBarSize)); 278 summaryPreference.setProgress(mDataplanUse / (float) mDataBarSize); 279 } 280 summaryPreference.setUsageInfo(mCycleEnd, mSnapshotTime, mCarrierName, 281 mDataplanCount, mManageSubscriptionIntent); 282 } 283 displayUsageLevel(long usageLevel)284 private long displayUsageLevel(long usageLevel) { 285 if (usageLevel > 0) { 286 return usageLevel; 287 } 288 try { 289 usageLevel = mHistoricalUsageLevel.get(); 290 } catch (Exception ex) { 291 } 292 return usageLevel; 293 } 294 295 // TODO(b/70950124) add test for this method once the robolectric shadow run script is 296 // completed (b/3526807) refreshDataplanInfo(DataUsageController.DataUsageInfo info, SubscriptionInfo subInfo)297 private void refreshDataplanInfo(DataUsageController.DataUsageInfo info, 298 SubscriptionInfo subInfo) { 299 // reset data before overwriting 300 mCarrierName = null; 301 mDataplanCount = 0; 302 mDataplanSize = -1L; 303 mDataBarSize = mDataInfoController.getSummaryLimit(info); 304 mDataplanUse = info.usageLevel; 305 mCycleStart = info.cycleStart; 306 mCycleEnd = info.cycleEnd; 307 mSnapshotTime = -1L; 308 309 if (subInfo != null && mHasMobileData) { 310 mCarrierName = subInfo.getCarrierName(); 311 final List<SubscriptionPlan> plans = getSubscriptionPlans(mSubId); 312 final SubscriptionPlan primaryPlan = getPrimaryPlan(plans); 313 314 if (primaryPlan != null) { 315 mDataplanCount = plans.size(); 316 mDataplanSize = primaryPlan.getDataLimitBytes(); 317 if (unlimited(mDataplanSize)) { 318 mDataplanSize = -1L; 319 } 320 mDataBarSize = mDataplanSize; 321 mDataplanUse = primaryPlan.getDataUsageBytes(); 322 323 RecurrenceRule rule = primaryPlan.getCycleRule(); 324 if (rule != null && rule.start != null && rule.end != null) { 325 mCycleStart = rule.start.toEpochSecond() * 1000L; 326 mCycleEnd = rule.end.toEpochSecond() * 1000L; 327 } 328 mSnapshotTime = primaryPlan.getDataUsageTime(); 329 } 330 } 331 // Temporarily return null, since no current users of SubscriptionPlan have this intent set. 332 // TODO (b/170330084): Remove after refactoring 5G SubscriptionPlan logic. 333 // mManageSubscriptionIntent = createManageSubscriptionIntent(mSubId); 334 mManageSubscriptionIntent = null; 335 Log.i(TAG, "Have " + mDataplanCount + " plans, dflt sub-id " + mSubId 336 + ", intent " + mManageSubscriptionIntent); 337 } 338 339 /** 340 * Create an {@link Intent} that can be launched towards the carrier app 341 * that is currently defining the billing relationship plan through 342 * {@link INetworkPolicyManager#setSubscriptionPlans(int, SubscriptionPlan [], String)}. 343 * 344 * @return ready to launch Intent targeted towards the carrier app, or 345 * {@code null} if no carrier app is defined, or if the defined 346 * carrier app provides no management activity. 347 */ 348 @VisibleForTesting createManageSubscriptionIntent(int subId)349 Intent createManageSubscriptionIntent(int subId) { 350 final INetworkPolicyManager iNetPolicyManager = INetworkPolicyManager.Stub.asInterface( 351 ServiceManager.getService(Context.NETWORK_POLICY_SERVICE)); 352 String owner = ""; 353 try { 354 owner = iNetPolicyManager.getSubscriptionPlansOwner(subId); 355 } catch (Exception ex) { 356 Log.w(TAG, "Fail to get subscription plan owner for subId " + subId, ex); 357 } 358 359 if (TextUtils.isEmpty(owner)) { 360 return null; 361 } 362 363 final List<SubscriptionPlan> plans = getSubscriptionPlans(subId); 364 if (plans.isEmpty()) { 365 return null; 366 } 367 368 final Intent intent = new Intent(SubscriptionManager.ACTION_MANAGE_SUBSCRIPTION_PLANS); 369 intent.setPackage(owner); 370 intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId); 371 372 if (mContext.getPackageManager().queryIntentActivities(intent, 373 PackageManager.MATCH_DEFAULT_ONLY).isEmpty()) { 374 return null; 375 } 376 377 return intent; 378 } 379 getPrimaryPlan(List<SubscriptionPlan> plans)380 private static SubscriptionPlan getPrimaryPlan(List<SubscriptionPlan> plans) { 381 if (CollectionUtils.isEmpty(plans)) { 382 return null; 383 } 384 // First plan in the list is the primary plan 385 SubscriptionPlan plan = plans.get(0); 386 return plan.getDataLimitBytes() > 0 387 && validSize(plan.getDataUsageBytes()) 388 && plan.getCycleRule() != null ? plan : null; 389 } 390 validSize(long value)391 private static boolean validSize(long value) { 392 return value >= 0L && value < PETA; 393 } 394 unlimited(long size)395 public static boolean unlimited(long size) { 396 return size == SubscriptionPlan.BYTES_UNLIMITED; 397 } 398 } 399