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