1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 * except in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the 10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 11 * KIND, either express or implied. See the License for the specific language governing 12 * permissions and limitations under the License. 13 */ 14 15 package com.android.settings.datausage; 16 17 import android.app.Activity; 18 import android.content.ComponentName; 19 import android.content.Context; 20 import android.content.Intent; 21 import android.net.NetworkTemplate; 22 import android.os.Bundle; 23 import android.provider.SearchIndexableResource; 24 import android.support.annotation.VisibleForTesting; 25 import android.support.v7.preference.Preference; 26 import android.support.v7.preference.PreferenceScreen; 27 import android.telephony.SubscriptionInfo; 28 import android.telephony.SubscriptionManager; 29 import android.telephony.SubscriptionPlan; 30 import android.text.BidiFormatter; 31 import android.text.Spannable; 32 import android.text.SpannableString; 33 import android.text.TextUtils; 34 import android.text.format.Formatter; 35 import android.text.style.RelativeSizeSpan; 36 import android.view.MenuItem; 37 38 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 39 import com.android.settings.R; 40 import com.android.settings.Utils; 41 import com.android.settings.dashboard.SummaryLoader; 42 import com.android.settings.search.BaseSearchIndexProvider; 43 import com.android.settings.search.Indexable; 44 import com.android.settingslib.NetworkPolicyEditor; 45 import com.android.settingslib.core.AbstractPreferenceController; 46 import com.android.settingslib.net.DataUsageController; 47 48 import java.util.ArrayList; 49 import java.util.List; 50 51 /** 52 * Settings preference fragment that displays data usage summary. 53 */ 54 public class DataUsageSummary extends DataUsageBaseFragment implements Indexable, 55 DataUsageEditController { 56 57 private static final String TAG = "DataUsageSummary"; 58 59 static final boolean LOGD = false; 60 61 public static final String KEY_RESTRICT_BACKGROUND = "restrict_background"; 62 63 private static final String KEY_STATUS_HEADER = "status_header"; 64 65 // Mobile data keys 66 public static final String KEY_MOBILE_USAGE_TITLE = "mobile_category"; 67 public static final String KEY_MOBILE_DATA_USAGE_TOGGLE = "data_usage_enable"; 68 public static final String KEY_MOBILE_DATA_USAGE = "cellular_data_usage"; 69 public static final String KEY_MOBILE_BILLING_CYCLE = "billing_preference"; 70 71 // Wifi keys 72 public static final String KEY_WIFI_USAGE_TITLE = "wifi_category"; 73 public static final String KEY_WIFI_DATA_USAGE = "wifi_data_usage"; 74 75 private DataUsageSummaryPreference mSummaryPreference; 76 private DataUsageSummaryPreferenceController mSummaryController; 77 private NetworkTemplate mDefaultTemplate; 78 79 @Override 80 public int getHelpResource() { 81 return R.string.help_url_data_usage; 82 } 83 84 @Override 85 public void onCreate(Bundle icicle) { 86 super.onCreate(icicle); 87 Context context = getContext(); 88 89 boolean hasMobileData = DataUsageUtils.hasMobileData(context); 90 91 int defaultSubId = DataUsageUtils.getDefaultSubscriptionId(context); 92 if (defaultSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 93 hasMobileData = false; 94 } 95 mDefaultTemplate = DataUsageUtils.getDefaultTemplate(context, defaultSubId); 96 mSummaryPreference = (DataUsageSummaryPreference) findPreference(KEY_STATUS_HEADER); 97 98 if (!hasMobileData || !isAdmin()) { 99 removePreference(KEY_RESTRICT_BACKGROUND); 100 } 101 boolean hasWifiRadio = DataUsageUtils.hasWifiRadio(context); 102 if (hasMobileData) { 103 addMobileSection(defaultSubId); 104 if (DataUsageUtils.hasSim(context) && hasWifiRadio) { 105 // If the device has a SIM installed, the data usage section shows usage for mobile, 106 // and the WiFi section is added if there is a WiFi radio - legacy behavior. 107 addWifiSection(); 108 } 109 // Do not add the WiFi section if either there is no WiFi radio (obviously) or if no 110 // SIM is installed. In the latter case the data usage section will show WiFi usage and 111 // there should be no explicit WiFi section added. 112 } else if (hasWifiRadio) { 113 addWifiSection(); 114 } 115 if (DataUsageUtils.hasEthernet(context)) { 116 addEthernetSection(); 117 } 118 setHasOptionsMenu(true); 119 } 120 121 @Override 122 public boolean onOptionsItemSelected(MenuItem item) { 123 switch (item.getItemId()) { 124 case R.id.data_usage_menu_cellular_networks: { 125 final Intent intent = new Intent(Intent.ACTION_MAIN); 126 intent.setComponent(new ComponentName("com.android.phone", 127 "com.android.phone.MobileNetworkSettings")); 128 startActivity(intent); 129 return true; 130 } 131 } 132 return false; 133 } 134 135 @Override 136 public boolean onPreferenceTreeClick(Preference preference) { 137 if (preference == findPreference(KEY_STATUS_HEADER)) { 138 BillingCycleSettings.BytesEditorFragment.show(this, false); 139 return false; 140 } 141 return super.onPreferenceTreeClick(preference); 142 } 143 144 @Override 145 protected int getPreferenceScreenResId() { 146 return R.xml.data_usage; 147 } 148 149 @Override 150 protected String getLogTag() { 151 return TAG; 152 } 153 154 @Override 155 protected List<AbstractPreferenceController> createPreferenceControllers(Context context) { 156 final Activity activity = getActivity(); 157 final ArrayList<AbstractPreferenceController> controllers = new ArrayList<>(); 158 mSummaryController = 159 new DataUsageSummaryPreferenceController(activity, getLifecycle(), this); 160 controllers.add(mSummaryController); 161 getLifecycle().addObserver(mSummaryController); 162 return controllers; 163 } 164 165 @VisibleForTesting 166 void addMobileSection(int subId) { 167 addMobileSection(subId, null); 168 } 169 170 private void addMobileSection(int subId, SubscriptionInfo subInfo) { 171 TemplatePreferenceCategory category = (TemplatePreferenceCategory) 172 inflatePreferences(R.xml.data_usage_cellular); 173 category.setTemplate(getNetworkTemplate(subId), subId, services); 174 category.pushTemplates(services); 175 if (subInfo != null && !TextUtils.isEmpty(subInfo.getDisplayName())) { 176 Preference title = category.findPreference(KEY_MOBILE_USAGE_TITLE); 177 title.setTitle(subInfo.getDisplayName()); 178 } 179 } 180 181 @VisibleForTesting 182 void addWifiSection() { 183 TemplatePreferenceCategory category = (TemplatePreferenceCategory) 184 inflatePreferences(R.xml.data_usage_wifi); 185 category.setTemplate(NetworkTemplate.buildTemplateWifiWildcard(), 0, services); 186 } 187 188 private void addEthernetSection() { 189 TemplatePreferenceCategory category = (TemplatePreferenceCategory) 190 inflatePreferences(R.xml.data_usage_ethernet); 191 category.setTemplate(NetworkTemplate.buildTemplateEthernet(), 0, services); 192 } 193 194 private Preference inflatePreferences(int resId) { 195 PreferenceScreen rootPreferences = getPreferenceManager().inflateFromResource( 196 getPrefContext(), resId, null); 197 Preference pref = rootPreferences.getPreference(0); 198 rootPreferences.removeAll(); 199 200 PreferenceScreen screen = getPreferenceScreen(); 201 pref.setOrder(screen.getPreferenceCount()); 202 screen.addPreference(pref); 203 204 return pref; 205 } 206 207 private NetworkTemplate getNetworkTemplate(int subscriptionId) { 208 NetworkTemplate mobileAll = NetworkTemplate.buildTemplateMobileAll( 209 services.mTelephonyManager.getSubscriberId(subscriptionId)); 210 return NetworkTemplate.normalize(mobileAll, 211 services.mTelephonyManager.getMergedSubscriberIds()); 212 } 213 214 @Override 215 public void onResume() { 216 super.onResume(); 217 updateState(); 218 } 219 220 @VisibleForTesting 221 static CharSequence formatUsage(Context context, String template, long usageLevel) { 222 final float LARGER_SIZE = 1.25f * 1.25f; // (1/0.8)^2 223 final float SMALLER_SIZE = 1.0f / LARGER_SIZE; // 0.8^2 224 return formatUsage(context, template, usageLevel, LARGER_SIZE, SMALLER_SIZE); 225 } 226 227 static CharSequence formatUsage(Context context, String template, long usageLevel, 228 float larger, float smaller) { 229 final int FLAGS = Spannable.SPAN_INCLUSIVE_INCLUSIVE; 230 231 final Formatter.BytesResult usedResult = Formatter.formatBytes(context.getResources(), 232 usageLevel, Formatter.FLAG_CALCULATE_ROUNDED | Formatter.FLAG_IEC_UNITS); 233 final SpannableString enlargedValue = new SpannableString(usedResult.value); 234 enlargedValue.setSpan(new RelativeSizeSpan(larger), 0, enlargedValue.length(), FLAGS); 235 236 final SpannableString amountTemplate = new SpannableString( 237 context.getString(com.android.internal.R.string.fileSizeSuffix) 238 .replace("%1$s", "^1").replace("%2$s", "^2")); 239 final CharSequence formattedUsage = TextUtils.expandTemplate(amountTemplate, 240 enlargedValue, usedResult.units); 241 242 final SpannableString fullTemplate = new SpannableString(template); 243 fullTemplate.setSpan(new RelativeSizeSpan(smaller), 0, fullTemplate.length(), FLAGS); 244 return TextUtils.expandTemplate(fullTemplate, 245 BidiFormatter.getInstance().unicodeWrap(formattedUsage.toString())); 246 } 247 248 private void updateState() { 249 PreferenceScreen screen = getPreferenceScreen(); 250 for (int i = 1; i < screen.getPreferenceCount(); i++) { 251 Preference currentPreference = screen.getPreference(i); 252 if (currentPreference instanceof TemplatePreferenceCategory) { 253 ((TemplatePreferenceCategory) currentPreference).pushTemplates(services); 254 } 255 } 256 } 257 258 @Override 259 public int getMetricsCategory() { 260 return MetricsEvent.DATA_USAGE_SUMMARY; 261 } 262 263 @Override 264 public NetworkPolicyEditor getNetworkPolicyEditor() { 265 return services.mPolicyEditor; 266 } 267 268 @Override 269 public NetworkTemplate getNetworkTemplate() { 270 return mDefaultTemplate; 271 } 272 273 @Override 274 public void updateDataUsage() { 275 updateState(); 276 mSummaryController.updateState(mSummaryPreference); 277 } 278 279 private static class SummaryProvider 280 implements SummaryLoader.SummaryProvider { 281 282 private final Activity mActivity; 283 private final SummaryLoader mSummaryLoader; 284 private final DataUsageController mDataController; 285 286 public SummaryProvider(Activity activity, SummaryLoader summaryLoader) { 287 mActivity = activity; 288 mSummaryLoader = summaryLoader; 289 mDataController = new DataUsageController(activity); 290 } 291 292 @Override 293 public void setListening(boolean listening) { 294 if (listening) { 295 if (DataUsageUtils.hasSim(mActivity)) { 296 mSummaryLoader.setSummary(this, 297 mActivity.getString(R.string.data_usage_summary_format, 298 formatUsedData())); 299 } else { 300 final DataUsageController.DataUsageInfo info = 301 mDataController 302 .getDataUsageInfo(NetworkTemplate.buildTemplateWifiWildcard()); 303 304 if (info == null) { 305 mSummaryLoader.setSummary(this, null); 306 } else { 307 final CharSequence wifiFormat = mActivity 308 .getText(R.string.data_usage_wifi_format); 309 final CharSequence sizeText = 310 DataUsageUtils.formatDataUsage(mActivity, info.usageLevel); 311 mSummaryLoader.setSummary(this, 312 TextUtils.expandTemplate(wifiFormat, sizeText)); 313 } 314 } 315 } 316 } 317 318 private CharSequence formatUsedData() { 319 SubscriptionManager subscriptionManager = (SubscriptionManager) mActivity 320 .getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); 321 int defaultSubId = subscriptionManager.getDefaultSubscriptionId(); 322 if (defaultSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 323 return formatFallbackData(); 324 } 325 SubscriptionPlan dfltPlan = DataUsageSummaryPreferenceController 326 .getPrimaryPlan(subscriptionManager, defaultSubId); 327 if (dfltPlan == null) { 328 return formatFallbackData(); 329 } 330 if (DataUsageSummaryPreferenceController.unlimited(dfltPlan.getDataLimitBytes())) { 331 return DataUsageUtils.formatDataUsage(mActivity, dfltPlan.getDataUsageBytes()); 332 } else { 333 return Utils.formatPercentage(dfltPlan.getDataUsageBytes(), 334 dfltPlan.getDataLimitBytes()); 335 } 336 } 337 338 private CharSequence formatFallbackData() { 339 DataUsageController.DataUsageInfo info = mDataController.getDataUsageInfo(); 340 if (info == null) { 341 return DataUsageUtils.formatDataUsage(mActivity, 0); 342 } else if (info.limitLevel <= 0) { 343 return DataUsageUtils.formatDataUsage(mActivity, info.usageLevel); 344 } else { 345 return Utils.formatPercentage(info.usageLevel, info.limitLevel); 346 } 347 } 348 } 349 350 public static final SummaryLoader.SummaryProviderFactory SUMMARY_PROVIDER_FACTORY 351 = SummaryProvider::new; 352 353 /** 354 * For search 355 */ 356 public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = 357 new BaseSearchIndexProvider() { 358 359 @Override 360 public List<SearchIndexableResource> getXmlResourcesToIndex(Context context, 361 boolean enabled) { 362 List<SearchIndexableResource> resources = new ArrayList<>(); 363 SearchIndexableResource resource = new SearchIndexableResource(context); 364 resource.xmlResId = R.xml.data_usage; 365 resources.add(resource); 366 367 resource = new SearchIndexableResource(context); 368 resource.xmlResId = R.xml.data_usage_cellular; 369 resources.add(resource); 370 371 resource = new SearchIndexableResource(context); 372 resource.xmlResId = R.xml.data_usage_wifi; 373 resources.add(resource); 374 375 return resources; 376 } 377 378 @Override 379 public List<String> getNonIndexableKeys(Context context) { 380 List<String> keys = super.getNonIndexableKeys(context); 381 382 if (!DataUsageUtils.hasMobileData(context)) { 383 keys.add(KEY_MOBILE_USAGE_TITLE); 384 keys.add(KEY_MOBILE_DATA_USAGE_TOGGLE); 385 keys.add(KEY_MOBILE_DATA_USAGE); 386 keys.add(KEY_MOBILE_BILLING_CYCLE); 387 } 388 389 if (!DataUsageUtils.hasWifiRadio(context)) { 390 keys.add(KEY_WIFI_DATA_USAGE); 391 } 392 393 // This title is named Wifi, and will confuse users. 394 keys.add(KEY_WIFI_USAGE_TITLE); 395 396 return keys; 397 } 398 }; 399 } 400