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