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