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.ConnectivityManager; 22 import android.net.INetworkStatsSession; 23 import android.net.NetworkPolicy; 24 import android.net.NetworkPolicyManager; 25 import android.net.NetworkTemplate; 26 import android.net.TrafficStats; 27 import android.net.wifi.WifiConfiguration; 28 import android.net.wifi.WifiManager; 29 import android.os.Bundle; 30 import android.os.RemoteException; 31 import android.os.SystemProperties; 32 import android.os.UserManager; 33 import android.provider.SearchIndexableResource; 34 import android.support.annotation.VisibleForTesting; 35 import android.support.v7.preference.Preference; 36 import android.support.v7.preference.PreferenceScreen; 37 import android.telephony.SubscriptionInfo; 38 import android.telephony.SubscriptionManager; 39 import android.telephony.TelephonyManager; 40 import android.text.BidiFormatter; 41 import android.text.Spannable; 42 import android.text.SpannableString; 43 import android.text.TextUtils; 44 import android.text.format.Formatter; 45 import android.text.style.RelativeSizeSpan; 46 import android.view.Menu; 47 import android.view.MenuInflater; 48 import android.view.MenuItem; 49 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 50 import com.android.settings.R; 51 import com.android.settings.SummaryPreference; 52 import com.android.settings.Utils; 53 import com.android.settings.dashboard.SummaryLoader; 54 import com.android.settings.search.BaseSearchIndexProvider; 55 import com.android.settings.search.Indexable; 56 import com.android.settingslib.NetworkPolicyEditor; 57 import com.android.settingslib.net.DataUsageController; 58 59 import java.util.ArrayList; 60 import java.util.List; 61 62 import static android.net.ConnectivityManager.TYPE_ETHERNET; 63 import static android.net.ConnectivityManager.TYPE_WIFI; 64 import static android.net.NetworkPolicy.LIMIT_DISABLED; 65 66 public class DataUsageSummary extends DataUsageBase implements Indexable, DataUsageEditController { 67 68 private static final String TAG = "DataUsageSummary"; 69 static final boolean LOGD = false; 70 71 public static final boolean TEST_RADIOS = false; 72 public static final String TEST_RADIOS_PROP = "test.radios"; 73 74 public static final String KEY_RESTRICT_BACKGROUND = "restrict_background"; 75 public static final String KEY_NETWORK_RESTRICTIONS = "network_restrictions"; 76 77 private static final String KEY_STATUS_HEADER = "status_header"; 78 private static final String KEY_LIMIT_SUMMARY = "limit_summary"; 79 private static final String KEY_WIFI_USAGE_TITLE = "wifi_category"; 80 81 private DataUsageController mDataUsageController; 82 private DataUsageInfoController mDataInfoController; 83 private SummaryPreference mSummaryPreference; 84 private Preference mLimitPreference; 85 private NetworkTemplate mDefaultTemplate; 86 private int mDataUsageTemplate; 87 private NetworkRestrictionsPreference mNetworkRestrcitionPreference; 88 private WifiManager mWifiManager; 89 private NetworkPolicyManager mPolicyManager; 90 private NetworkPolicyEditor mPolicyEditor; 91 92 @Override getHelpResource()93 protected int getHelpResource() { 94 return R.string.help_url_data_usage; 95 } 96 97 @Override onCreate(Bundle icicle)98 public void onCreate(Bundle icicle) { 99 super.onCreate(icicle); 100 101 final Context context = getContext(); 102 mPolicyManager = NetworkPolicyManager.from(context); 103 mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); 104 mPolicyEditor = new NetworkPolicyEditor(mPolicyManager); 105 106 boolean hasMobileData = hasMobileData(context); 107 mDataUsageController = new DataUsageController(context); 108 mDataInfoController = new DataUsageInfoController(); 109 addPreferencesFromResource(R.xml.data_usage); 110 111 int defaultSubId = getDefaultSubscriptionId(context); 112 if (defaultSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 113 hasMobileData = false; 114 } 115 mDefaultTemplate = getDefaultTemplate(context, defaultSubId); 116 mSummaryPreference = (SummaryPreference) findPreference(KEY_STATUS_HEADER); 117 118 if (!hasMobileData || !isAdmin()) { 119 removePreference(KEY_RESTRICT_BACKGROUND); 120 } 121 if (hasMobileData) { 122 mLimitPreference = findPreference(KEY_LIMIT_SUMMARY); 123 List<SubscriptionInfo> subscriptions = 124 services.mSubscriptionManager.getActiveSubscriptionInfoList(); 125 if (subscriptions == null || subscriptions.size() == 0) { 126 addMobileSection(defaultSubId); 127 } 128 for (int i = 0; subscriptions != null && i < subscriptions.size(); i++) { 129 addMobileSection(subscriptions.get(i).getSubscriptionId()); 130 } 131 mSummaryPreference.setSelectable(true); 132 } else { 133 removePreference(KEY_LIMIT_SUMMARY); 134 mSummaryPreference.setSelectable(false); 135 } 136 boolean hasWifiRadio = hasWifiRadio(context); 137 if (hasWifiRadio) { 138 addWifiSection(); 139 } 140 if (hasEthernet(context)) { 141 addEthernetSection(); 142 } 143 mDataUsageTemplate = hasMobileData ? R.string.cell_data_template 144 : hasWifiRadio ? R.string.wifi_data_template 145 : R.string.ethernet_data_template; 146 147 setHasOptionsMenu(true); 148 } 149 150 @Override onCreateOptionsMenu(Menu menu, MenuInflater inflater)151 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 152 if (UserManager.get(getContext()).isAdminUser()) { 153 inflater.inflate(R.menu.data_usage, menu); 154 } 155 super.onCreateOptionsMenu(menu, inflater); 156 } 157 158 @Override onOptionsItemSelected(MenuItem item)159 public boolean onOptionsItemSelected(MenuItem item) { 160 switch (item.getItemId()) { 161 case R.id.data_usage_menu_cellular_networks: { 162 final Intent intent = new Intent(Intent.ACTION_MAIN); 163 intent.setComponent(new ComponentName("com.android.phone", 164 "com.android.phone.MobileNetworkSettings")); 165 startActivity(intent); 166 return true; 167 } 168 } 169 return false; 170 } 171 172 @Override onPreferenceTreeClick(Preference preference)173 public boolean onPreferenceTreeClick(Preference preference) { 174 if (preference == findPreference(KEY_STATUS_HEADER)) { 175 BillingCycleSettings.BytesEditorFragment.show(this, false); 176 return false; 177 } 178 return super.onPreferenceTreeClick(preference); 179 } 180 addMobileSection(int subId)181 private void addMobileSection(int subId) { 182 TemplatePreferenceCategory category = (TemplatePreferenceCategory) 183 inflatePreferences(R.xml.data_usage_cellular); 184 category.setTemplate(getNetworkTemplate(subId), subId, services); 185 category.pushTemplates(services); 186 } 187 addWifiSection()188 private void addWifiSection() { 189 TemplatePreferenceCategory category = (TemplatePreferenceCategory) 190 inflatePreferences(R.xml.data_usage_wifi); 191 category.setTemplate(NetworkTemplate.buildTemplateWifiWildcard(), 0, services); 192 mNetworkRestrcitionPreference = 193 (NetworkRestrictionsPreference) category.findPreference(KEY_NETWORK_RESTRICTIONS); 194 } 195 addEthernetSection()196 private void addEthernetSection() { 197 TemplatePreferenceCategory category = (TemplatePreferenceCategory) 198 inflatePreferences(R.xml.data_usage_ethernet); 199 category.setTemplate(NetworkTemplate.buildTemplateEthernet(), 0, services); 200 } 201 inflatePreferences(int resId)202 private Preference inflatePreferences(int resId) { 203 PreferenceScreen rootPreferences = getPreferenceManager().inflateFromResource( 204 getPrefContext(), resId, null); 205 Preference pref = rootPreferences.getPreference(0); 206 rootPreferences.removeAll(); 207 208 PreferenceScreen screen = getPreferenceScreen(); 209 pref.setOrder(screen.getPreferenceCount()); 210 screen.addPreference(pref); 211 212 return pref; 213 } 214 getNetworkTemplate(int subscriptionId)215 private NetworkTemplate getNetworkTemplate(int subscriptionId) { 216 NetworkTemplate mobileAll = NetworkTemplate.buildTemplateMobileAll( 217 services.mTelephonyManager.getSubscriberId(subscriptionId)); 218 return NetworkTemplate.normalize(mobileAll, 219 services.mTelephonyManager.getMergedSubscriberIds()); 220 } 221 222 @Override onResume()223 public void onResume() { 224 super.onResume(); 225 updateState(); 226 } 227 formatTitle(Context context, String template, long usageLevel)228 private static CharSequence formatTitle(Context context, String template, long usageLevel) { 229 final float LARGER_SIZE = 1.25f * 1.25f; // (1/0.8)^2 230 final float SMALLER_SIZE = 1.0f / LARGER_SIZE; // 0.8^2 231 final int FLAGS = Spannable.SPAN_INCLUSIVE_INCLUSIVE; 232 233 final Formatter.BytesResult usedResult = Formatter.formatBytes(context.getResources(), 234 usageLevel, Formatter.FLAG_SHORTER); 235 final SpannableString enlargedValue = new SpannableString(usedResult.value); 236 enlargedValue.setSpan(new RelativeSizeSpan(LARGER_SIZE), 0, enlargedValue.length(), FLAGS); 237 238 final SpannableString amountTemplate = new SpannableString( 239 context.getString(com.android.internal.R.string.fileSizeSuffix) 240 .replace("%1$s", "^1").replace("%2$s", "^2")); 241 final CharSequence formattedUsage = TextUtils.expandTemplate(amountTemplate, 242 enlargedValue, usedResult.units); 243 244 final SpannableString fullTemplate = new SpannableString(template); 245 fullTemplate.setSpan(new RelativeSizeSpan(SMALLER_SIZE), 0, fullTemplate.length(), FLAGS); 246 return TextUtils.expandTemplate(fullTemplate, 247 BidiFormatter.getInstance().unicodeWrap(formattedUsage)); 248 } 249 updateState()250 private void updateState() { 251 DataUsageController.DataUsageInfo info = mDataUsageController.getDataUsageInfo( 252 mDefaultTemplate); 253 Context context = getContext(); 254 mDataInfoController.updateDataLimit(info, 255 services.mPolicyEditor.getPolicy(mDefaultTemplate)); 256 257 if (mSummaryPreference != null) { 258 mSummaryPreference.setTitle( 259 formatTitle(context, getString(mDataUsageTemplate), info.usageLevel)); 260 long limit = mDataInfoController.getSummaryLimit(info); 261 mSummaryPreference.setSummary(info.period); 262 263 if (limit <= 0) { 264 mSummaryPreference.setChartEnabled(false); 265 } else { 266 mSummaryPreference.setChartEnabled(true); 267 mSummaryPreference.setLabels(Formatter.formatFileSize(context, 0), 268 Formatter.formatFileSize(context, limit)); 269 mSummaryPreference.setRatios(info.usageLevel / (float) limit, 0, 270 (limit - info.usageLevel) / (float) limit); 271 } 272 } 273 if (mLimitPreference != null && (info.warningLevel > 0 || info.limitLevel > 0)) { 274 String warning = Formatter.formatFileSize(context, info.warningLevel); 275 String limit = Formatter.formatFileSize(context, info.limitLevel); 276 mLimitPreference.setSummary(getString(info.limitLevel <= 0 ? R.string.cell_warning_only 277 : R.string.cell_warning_and_limit, warning, limit)); 278 } else if (mLimitPreference != null) { 279 mLimitPreference.setSummary(null); 280 } 281 282 updateNetworkRestrictionSummary(mNetworkRestrcitionPreference); 283 284 PreferenceScreen screen = getPreferenceScreen(); 285 for (int i = 1; i < screen.getPreferenceCount(); i++) { 286 ((TemplatePreferenceCategory) screen.getPreference(i)).pushTemplates(services); 287 } 288 } 289 290 @Override getMetricsCategory()291 public int getMetricsCategory() { 292 return MetricsEvent.DATA_USAGE_SUMMARY; 293 } 294 295 @Override getNetworkPolicyEditor()296 public NetworkPolicyEditor getNetworkPolicyEditor() { 297 return services.mPolicyEditor; 298 } 299 300 @Override getNetworkTemplate()301 public NetworkTemplate getNetworkTemplate() { 302 return mDefaultTemplate; 303 } 304 305 @Override updateDataUsage()306 public void updateDataUsage() { 307 updateState(); 308 } 309 310 /** 311 * Test if device has an ethernet network connection. 312 */ hasEthernet(Context context)313 public boolean hasEthernet(Context context) { 314 if (TEST_RADIOS) { 315 return SystemProperties.get(TEST_RADIOS_PROP).contains("ethernet"); 316 } 317 318 final ConnectivityManager conn = ConnectivityManager.from(context); 319 final boolean hasEthernet = conn.isNetworkSupported(TYPE_ETHERNET); 320 321 final long ethernetBytes; 322 try { 323 INetworkStatsSession statsSession = services.mStatsService.openSession(); 324 if (statsSession != null) { 325 ethernetBytes = statsSession.getSummaryForNetwork( 326 NetworkTemplate.buildTemplateEthernet(), Long.MIN_VALUE, Long.MAX_VALUE) 327 .getTotalBytes(); 328 TrafficStats.closeQuietly(statsSession); 329 } else { 330 ethernetBytes = 0; 331 } 332 } catch (RemoteException e) { 333 throw new RuntimeException(e); 334 } 335 336 // only show ethernet when both hardware present and traffic has occurred 337 return hasEthernet && ethernetBytes > 0; 338 } 339 hasMobileData(Context context)340 public static boolean hasMobileData(Context context) { 341 return ConnectivityManager.from(context).isNetworkSupported( 342 ConnectivityManager.TYPE_MOBILE); 343 } 344 345 /** 346 * Test if device has a Wi-Fi data radio. 347 */ hasWifiRadio(Context context)348 public static boolean hasWifiRadio(Context context) { 349 if (TEST_RADIOS) { 350 return SystemProperties.get(TEST_RADIOS_PROP).contains("wifi"); 351 } 352 353 final ConnectivityManager conn = ConnectivityManager.from(context); 354 return conn.isNetworkSupported(TYPE_WIFI); 355 } 356 getDefaultSubscriptionId(Context context)357 public static int getDefaultSubscriptionId(Context context) { 358 SubscriptionManager subManager = SubscriptionManager.from(context); 359 if (subManager == null) { 360 return SubscriptionManager.INVALID_SUBSCRIPTION_ID; 361 } 362 SubscriptionInfo subscriptionInfo = subManager.getDefaultDataSubscriptionInfo(); 363 if (subscriptionInfo == null) { 364 List<SubscriptionInfo> list = subManager.getAllSubscriptionInfoList(); 365 if (list.size() == 0) { 366 return SubscriptionManager.INVALID_SUBSCRIPTION_ID; 367 } 368 subscriptionInfo = list.get(0); 369 } 370 return subscriptionInfo.getSubscriptionId(); 371 } 372 getDefaultTemplate(Context context, int defaultSubId)373 public static NetworkTemplate getDefaultTemplate(Context context, int defaultSubId) { 374 if (hasMobileData(context) && defaultSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 375 TelephonyManager telephonyManager = TelephonyManager.from(context); 376 NetworkTemplate mobileAll = NetworkTemplate.buildTemplateMobileAll( 377 telephonyManager.getSubscriberId(defaultSubId)); 378 return NetworkTemplate.normalize(mobileAll, 379 telephonyManager.getMergedSubscriberIds()); 380 } else if (hasWifiRadio(context)) { 381 return NetworkTemplate.buildTemplateWifiWildcard(); 382 } else { 383 return NetworkTemplate.buildTemplateEthernet(); 384 } 385 } 386 387 @VisibleForTesting updateNetworkRestrictionSummary(NetworkRestrictionsPreference preference)388 void updateNetworkRestrictionSummary(NetworkRestrictionsPreference preference) { 389 if (preference == null) { 390 return; 391 } 392 mPolicyEditor.read(); 393 int count = 0; 394 for (WifiConfiguration config : mWifiManager.getConfiguredNetworks()) { 395 if (isMetered(config)) { 396 count++; 397 } 398 } 399 preference.setSummary(getResources().getQuantityString( 400 R.plurals.network_restrictions_summary, count, count)); 401 } 402 403 @VisibleForTesting isMetered(WifiConfiguration config)404 boolean isMetered(WifiConfiguration config) { 405 if (config.SSID == null) { 406 return false; 407 } 408 final String networkId = config.isPasspoint() ? config.providerFriendlyName : config.SSID; 409 final NetworkPolicy policy = 410 mPolicyEditor.getPolicyMaybeUnquoted(NetworkTemplate.buildTemplateWifi(networkId)); 411 if (policy == null) { 412 return false; 413 } 414 if (policy.limitBytes != LIMIT_DISABLED) { 415 return true; 416 } 417 return policy.metered; 418 } 419 420 private static class SummaryProvider 421 implements SummaryLoader.SummaryProvider { 422 423 private final Activity mActivity; 424 private final SummaryLoader mSummaryLoader; 425 private final DataUsageController mDataController; 426 SummaryProvider(Activity activity, SummaryLoader summaryLoader)427 public SummaryProvider(Activity activity, SummaryLoader summaryLoader) { 428 mActivity = activity; 429 mSummaryLoader = summaryLoader; 430 mDataController = new DataUsageController(activity); 431 } 432 433 @Override setListening(boolean listening)434 public void setListening(boolean listening) { 435 if (listening) { 436 DataUsageController.DataUsageInfo info = mDataController.getDataUsageInfo(); 437 String used; 438 if (info == null) { 439 used = Formatter.formatFileSize(mActivity, 0); 440 } else if (info.limitLevel <= 0) { 441 used = Formatter.formatFileSize(mActivity, info.usageLevel); 442 } else { 443 used = Utils.formatPercentage(info.usageLevel, info.limitLevel); 444 } 445 mSummaryLoader.setSummary(this, 446 mActivity.getString(R.string.data_usage_summary_format, used)); 447 } 448 } 449 } 450 451 public static final SummaryLoader.SummaryProviderFactory SUMMARY_PROVIDER_FACTORY 452 = new SummaryLoader.SummaryProviderFactory() { 453 @Override 454 public SummaryLoader.SummaryProvider createSummaryProvider(Activity activity, 455 SummaryLoader summaryLoader) { 456 return new SummaryProvider(activity, summaryLoader); 457 } 458 }; 459 460 /** 461 * For search 462 */ 463 public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = 464 new BaseSearchIndexProvider() { 465 466 @Override 467 public List<SearchIndexableResource> getXmlResourcesToIndex(Context context, 468 boolean enabled) { 469 List<SearchIndexableResource> resources = new ArrayList<>(); 470 SearchIndexableResource resource = new SearchIndexableResource(context); 471 resource.xmlResId = R.xml.data_usage; 472 resources.add(resource); 473 474 if (hasMobileData(context)) { 475 resource = new SearchIndexableResource(context); 476 resource.xmlResId = R.xml.data_usage_cellular; 477 resources.add(resource); 478 } 479 if (hasWifiRadio(context)) { 480 resource = new SearchIndexableResource(context); 481 resource.xmlResId = R.xml.data_usage_wifi; 482 resources.add(resource); 483 } 484 return resources; 485 } 486 487 @Override 488 public List<String> getNonIndexableKeys(Context context) { 489 List<String> keys = super.getNonIndexableKeys(context); 490 491 if (hasMobileData(context)) { 492 keys.add(KEY_RESTRICT_BACKGROUND); 493 } 494 if (hasWifiRadio(context)) { 495 keys.add(KEY_NETWORK_RESTRICTIONS); 496 } 497 keys.add(KEY_WIFI_USAGE_TITLE); 498 499 return keys; 500 } 501 }; 502 } 503