| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file |
| * except in compliance with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software distributed under the |
| * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the specific language governing |
| * permissions and limitations under the License. |
| */ |
| |
| package com.android.settings.datausage; |
| |
| import android.app.Activity; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.net.ConnectivityManager; |
| import android.net.INetworkStatsSession; |
| import android.net.NetworkTemplate; |
| import android.net.TrafficStats; |
| import android.os.Bundle; |
| import android.os.RemoteException; |
| import android.os.SystemProperties; |
| import android.os.UserManager; |
| import android.provider.SearchIndexableResource; |
| import android.support.v7.preference.Preference; |
| import android.support.v7.preference.PreferenceScreen; |
| import android.telephony.SubscriptionInfo; |
| import android.telephony.SubscriptionManager; |
| import android.telephony.TelephonyManager; |
| import android.text.BidiFormatter; |
| import android.text.Spannable; |
| import android.text.SpannableString; |
| import android.text.TextUtils; |
| import android.text.format.Formatter; |
| import android.text.style.RelativeSizeSpan; |
| import android.view.Menu; |
| import android.view.MenuInflater; |
| import android.view.MenuItem; |
| import com.android.internal.logging.nano.MetricsProto.MetricsEvent; |
| import com.android.settings.R; |
| import com.android.settings.SummaryPreference; |
| import com.android.settings.Utils; |
| import com.android.settings.dashboard.SummaryLoader; |
| import com.android.settings.search.BaseSearchIndexProvider; |
| import com.android.settings.search.Indexable; |
| import com.android.settingslib.NetworkPolicyEditor; |
| import com.android.settingslib.net.DataUsageController; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import static android.net.ConnectivityManager.TYPE_ETHERNET; |
| import static android.net.ConnectivityManager.TYPE_WIFI; |
| |
| public class DataUsageSummary extends DataUsageBase implements Indexable, DataUsageEditController { |
| |
| private static final String TAG = "DataUsageSummary"; |
| static final boolean LOGD = false; |
| |
| public static final boolean TEST_RADIOS = false; |
| public static final String TEST_RADIOS_PROP = "test.radios"; |
| |
| private static final String KEY_STATUS_HEADER = "status_header"; |
| private static final String KEY_LIMIT_SUMMARY = "limit_summary"; |
| private static final String KEY_RESTRICT_BACKGROUND = "restrict_background"; |
| |
| private DataUsageController mDataUsageController; |
| private DataUsageInfoController mDataInfoController; |
| private SummaryPreference mSummaryPreference; |
| private Preference mLimitPreference; |
| private NetworkTemplate mDefaultTemplate; |
| private int mDataUsageTemplate; |
| |
| @Override |
| protected int getHelpResource() { |
| return R.string.help_url_data_usage; |
| } |
| |
| @Override |
| public void onCreate(Bundle icicle) { |
| super.onCreate(icicle); |
| |
| boolean hasMobileData = hasMobileData(getContext()); |
| mDataUsageController = new DataUsageController(getContext()); |
| mDataInfoController = new DataUsageInfoController(); |
| addPreferencesFromResource(R.xml.data_usage); |
| |
| int defaultSubId = getDefaultSubscriptionId(getContext()); |
| if (defaultSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { |
| hasMobileData = false; |
| } |
| mDefaultTemplate = getDefaultTemplate(getContext(), defaultSubId); |
| mSummaryPreference = (SummaryPreference) findPreference(KEY_STATUS_HEADER); |
| |
| if (!hasMobileData || !isAdmin()) { |
| removePreference(KEY_RESTRICT_BACKGROUND); |
| } |
| if (hasMobileData) { |
| mLimitPreference = findPreference(KEY_LIMIT_SUMMARY); |
| List<SubscriptionInfo> subscriptions = |
| services.mSubscriptionManager.getActiveSubscriptionInfoList(); |
| if (subscriptions == null || subscriptions.size() == 0) { |
| addMobileSection(defaultSubId); |
| } |
| for (int i = 0; subscriptions != null && i < subscriptions.size(); i++) { |
| addMobileSection(subscriptions.get(i).getSubscriptionId()); |
| } |
| mSummaryPreference.setSelectable(true); |
| } else { |
| removePreference(KEY_LIMIT_SUMMARY); |
| mSummaryPreference.setSelectable(false); |
| } |
| boolean hasWifiRadio = hasWifiRadio(getContext()); |
| if (hasWifiRadio) { |
| addWifiSection(); |
| } |
| if (hasEthernet(getContext())) { |
| addEthernetSection(); |
| } |
| mDataUsageTemplate = hasMobileData ? R.string.cell_data_template |
| : hasWifiRadio ? R.string.wifi_data_template |
| : R.string.ethernet_data_template; |
| |
| setHasOptionsMenu(true); |
| } |
| |
| @Override |
| public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { |
| if (UserManager.get(getContext()).isAdminUser()) { |
| inflater.inflate(R.menu.data_usage, menu); |
| } |
| super.onCreateOptionsMenu(menu, inflater); |
| } |
| |
| @Override |
| public boolean onOptionsItemSelected(MenuItem item) { |
| switch (item.getItemId()) { |
| case R.id.data_usage_menu_cellular_networks: { |
| final Intent intent = new Intent(Intent.ACTION_MAIN); |
| intent.setComponent(new ComponentName("com.android.phone", |
| "com.android.phone.MobileNetworkSettings")); |
| startActivity(intent); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public boolean onPreferenceTreeClick(Preference preference) { |
| if (preference == findPreference(KEY_STATUS_HEADER)) { |
| BillingCycleSettings.BytesEditorFragment.show(this, false); |
| return false; |
| } |
| return super.onPreferenceTreeClick(preference); |
| } |
| |
| private void addMobileSection(int subId) { |
| TemplatePreferenceCategory category = (TemplatePreferenceCategory) |
| inflatePreferences(R.xml.data_usage_cellular); |
| category.setTemplate(getNetworkTemplate(subId), subId, services); |
| category.pushTemplates(services); |
| } |
| |
| private void addWifiSection() { |
| TemplatePreferenceCategory category = (TemplatePreferenceCategory) |
| inflatePreferences(R.xml.data_usage_wifi); |
| category.setTemplate(NetworkTemplate.buildTemplateWifiWildcard(), 0, services); |
| } |
| |
| private void addEthernetSection() { |
| TemplatePreferenceCategory category = (TemplatePreferenceCategory) |
| inflatePreferences(R.xml.data_usage_ethernet); |
| category.setTemplate(NetworkTemplate.buildTemplateEthernet(), 0, services); |
| } |
| |
| private Preference inflatePreferences(int resId) { |
| PreferenceScreen rootPreferences = getPreferenceManager().inflateFromResource( |
| getPrefContext(), resId, null); |
| Preference pref = rootPreferences.getPreference(0); |
| rootPreferences.removeAll(); |
| |
| PreferenceScreen screen = getPreferenceScreen(); |
| pref.setOrder(screen.getPreferenceCount()); |
| screen.addPreference(pref); |
| |
| return pref; |
| } |
| |
| private NetworkTemplate getNetworkTemplate(int subscriptionId) { |
| NetworkTemplate mobileAll = NetworkTemplate.buildTemplateMobileAll( |
| services.mTelephonyManager.getSubscriberId(subscriptionId)); |
| return NetworkTemplate.normalize(mobileAll, |
| services.mTelephonyManager.getMergedSubscriberIds()); |
| } |
| |
| @Override |
| public void onResume() { |
| super.onResume(); |
| updateState(); |
| } |
| |
| private static void verySmallSpanExcept(SpannableString s, CharSequence exception) { |
| final float SIZE = 0.8f * 0.8f; |
| final int FLAGS = Spannable.SPAN_INCLUSIVE_INCLUSIVE; |
| final int exceptionStart = TextUtils.indexOf(s, exception); |
| if (exceptionStart == -1) { |
| s.setSpan(new RelativeSizeSpan(SIZE), 0, s.length(), FLAGS); |
| } else { |
| if (exceptionStart > 0) { |
| s.setSpan(new RelativeSizeSpan(SIZE), 0, exceptionStart, FLAGS); |
| } |
| final int exceptionEnd = exceptionStart + exception.length(); |
| if (exceptionEnd < s.length()) { |
| s.setSpan(new RelativeSizeSpan(SIZE), exceptionEnd, s.length(), FLAGS); |
| } |
| } |
| } |
| |
| private static CharSequence formatTitle(Context context, String template, long usageLevel) { |
| final SpannableString amountTemplate = new SpannableString( |
| context.getString(com.android.internal.R.string.fileSizeSuffix) |
| .replace("%1$s", "^1").replace("%2$s", "^2")); |
| verySmallSpanExcept(amountTemplate, "^1"); |
| final Formatter.BytesResult usedResult = Formatter.formatBytes(context.getResources(), |
| usageLevel, Formatter.FLAG_SHORTER); |
| final CharSequence formattedUsage = TextUtils.expandTemplate(amountTemplate, |
| usedResult.value, usedResult.units); |
| |
| final SpannableString fullTemplate = new SpannableString(template.replace("%1$s", "^1")); |
| verySmallSpanExcept(fullTemplate, "^1"); |
| return TextUtils.expandTemplate(fullTemplate, |
| BidiFormatter.getInstance().unicodeWrap(formattedUsage)); |
| } |
| |
| private void updateState() { |
| DataUsageController.DataUsageInfo info = mDataUsageController.getDataUsageInfo( |
| mDefaultTemplate); |
| Context context = getContext(); |
| |
| mDataInfoController.updateDataLimit(info, |
| services.mPolicyEditor.getPolicy(mDefaultTemplate)); |
| |
| if (mSummaryPreference != null) { |
| mSummaryPreference.setTitle( |
| formatTitle(context, getString(mDataUsageTemplate), info.usageLevel)); |
| long limit = mDataInfoController.getSummaryLimit(info); |
| mSummaryPreference.setSummary(info.period); |
| |
| if (limit <= 0) { |
| mSummaryPreference.setChartEnabled(false); |
| } else { |
| mSummaryPreference.setChartEnabled(true); |
| mSummaryPreference.setLabels(Formatter.formatFileSize(context, 0), |
| Formatter.formatFileSize(context, limit)); |
| mSummaryPreference.setRatios(info.usageLevel / (float) limit, 0, |
| (limit - info.usageLevel) / (float) limit); |
| } |
| } |
| if (mLimitPreference != null && (info.warningLevel > 0 || info.limitLevel > 0)) { |
| String warning = Formatter.formatFileSize(context, info.warningLevel); |
| String limit = Formatter.formatFileSize(context, info.limitLevel); |
| mLimitPreference.setSummary(getString(info.limitLevel <= 0 ? R.string.cell_warning_only |
| : R.string.cell_warning_and_limit, warning, limit)); |
| } else if (mLimitPreference != null) { |
| mLimitPreference.setSummary(null); |
| } |
| |
| PreferenceScreen screen = getPreferenceScreen(); |
| for (int i = 1; i < screen.getPreferenceCount(); i++) { |
| ((TemplatePreferenceCategory) screen.getPreference(i)).pushTemplates(services); |
| } |
| } |
| |
| @Override |
| protected int getMetricsCategory() { |
| return MetricsEvent.DATA_USAGE_SUMMARY; |
| } |
| |
| @Override |
| public NetworkPolicyEditor getNetworkPolicyEditor() { |
| return services.mPolicyEditor; |
| } |
| |
| @Override |
| public NetworkTemplate getNetworkTemplate() { |
| return mDefaultTemplate; |
| } |
| |
| @Override |
| public void updateDataUsage() { |
| updateState(); |
| } |
| |
| /** |
| * Test if device has an ethernet network connection. |
| */ |
| public boolean hasEthernet(Context context) { |
| if (TEST_RADIOS) { |
| return SystemProperties.get(TEST_RADIOS_PROP).contains("ethernet"); |
| } |
| |
| final ConnectivityManager conn = ConnectivityManager.from(context); |
| final boolean hasEthernet = conn.isNetworkSupported(TYPE_ETHERNET); |
| |
| final long ethernetBytes; |
| try { |
| INetworkStatsSession statsSession = services.mStatsService.openSession(); |
| if (statsSession != null) { |
| ethernetBytes = statsSession.getSummaryForNetwork( |
| NetworkTemplate.buildTemplateEthernet(), Long.MIN_VALUE, Long.MAX_VALUE) |
| .getTotalBytes(); |
| TrafficStats.closeQuietly(statsSession); |
| } else { |
| ethernetBytes = 0; |
| } |
| } catch (RemoteException e) { |
| throw new RuntimeException(e); |
| } |
| |
| // only show ethernet when both hardware present and traffic has occurred |
| return hasEthernet && ethernetBytes > 0; |
| } |
| |
| public static boolean hasMobileData(Context context) { |
| return ConnectivityManager.from(context).isNetworkSupported( |
| ConnectivityManager.TYPE_MOBILE); |
| } |
| |
| /** |
| * Test if device has a Wi-Fi data radio. |
| */ |
| public static boolean hasWifiRadio(Context context) { |
| if (TEST_RADIOS) { |
| return SystemProperties.get(TEST_RADIOS_PROP).contains("wifi"); |
| } |
| |
| final ConnectivityManager conn = ConnectivityManager.from(context); |
| return conn.isNetworkSupported(TYPE_WIFI); |
| } |
| |
| public static int getDefaultSubscriptionId(Context context) { |
| SubscriptionManager subManager = SubscriptionManager.from(context); |
| if (subManager == null) { |
| return SubscriptionManager.INVALID_SUBSCRIPTION_ID; |
| } |
| SubscriptionInfo subscriptionInfo = subManager.getDefaultDataSubscriptionInfo(); |
| if (subscriptionInfo == null) { |
| List<SubscriptionInfo> list = subManager.getAllSubscriptionInfoList(); |
| if (list.size() == 0) { |
| return SubscriptionManager.INVALID_SUBSCRIPTION_ID; |
| } |
| subscriptionInfo = list.get(0); |
| } |
| return subscriptionInfo.getSubscriptionId(); |
| } |
| |
| public static NetworkTemplate getDefaultTemplate(Context context, int defaultSubId) { |
| if (hasMobileData(context) && defaultSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { |
| TelephonyManager telephonyManager = TelephonyManager.from(context); |
| NetworkTemplate mobileAll = NetworkTemplate.buildTemplateMobileAll( |
| telephonyManager.getSubscriberId(defaultSubId)); |
| return NetworkTemplate.normalize(mobileAll, |
| telephonyManager.getMergedSubscriberIds()); |
| } else if (hasWifiRadio(context)) { |
| return NetworkTemplate.buildTemplateWifiWildcard(); |
| } else { |
| return NetworkTemplate.buildTemplateEthernet(); |
| } |
| } |
| |
| private static class SummaryProvider |
| implements SummaryLoader.SummaryProvider { |
| |
| private final Activity mActivity; |
| private final SummaryLoader mSummaryLoader; |
| private final DataUsageController mDataController; |
| |
| public SummaryProvider(Activity activity, SummaryLoader summaryLoader) { |
| mActivity = activity; |
| mSummaryLoader = summaryLoader; |
| mDataController = new DataUsageController(activity); |
| } |
| |
| @Override |
| public void setListening(boolean listening) { |
| if (listening) { |
| DataUsageController.DataUsageInfo info = mDataController.getDataUsageInfo(); |
| String used; |
| if (info == null) { |
| used = Formatter.formatFileSize(mActivity, 0); |
| } else if (info.limitLevel <= 0) { |
| used = Formatter.formatFileSize(mActivity, info.usageLevel); |
| } else { |
| used = Utils.formatPercentage(info.usageLevel, info.limitLevel); |
| } |
| mSummaryLoader.setSummary(this, |
| mActivity.getString(R.string.data_usage_summary_format, used)); |
| } |
| } |
| } |
| |
| public static final SummaryLoader.SummaryProviderFactory SUMMARY_PROVIDER_FACTORY |
| = new SummaryLoader.SummaryProviderFactory() { |
| @Override |
| public SummaryLoader.SummaryProvider createSummaryProvider(Activity activity, |
| SummaryLoader summaryLoader) { |
| return new SummaryProvider(activity, summaryLoader); |
| } |
| }; |
| |
| /** |
| * For search |
| */ |
| public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = |
| new BaseSearchIndexProvider() { |
| |
| @Override |
| public List<SearchIndexableResource> getXmlResourcesToIndex(Context context, |
| boolean enabled) { |
| ArrayList<SearchIndexableResource> resources = new ArrayList<>(); |
| SearchIndexableResource resource = new SearchIndexableResource(context); |
| resource.xmlResId = R.xml.data_usage; |
| resources.add(resource); |
| |
| if (hasMobileData(context)) { |
| resource = new SearchIndexableResource(context); |
| resource.xmlResId = R.xml.data_usage_cellular; |
| resources.add(resource); |
| } |
| if (hasWifiRadio(context)) { |
| resource = new SearchIndexableResource(context); |
| resource.xmlResId = R.xml.data_usage_wifi; |
| resources.add(resource); |
| } |
| return resources; |
| } |
| |
| @Override |
| public List<String> getNonIndexableKeys(Context context) { |
| ArrayList<String> keys = new ArrayList<>(); |
| boolean hasMobileData = ConnectivityManager.from(context).isNetworkSupported( |
| ConnectivityManager.TYPE_MOBILE); |
| |
| if (hasMobileData) { |
| keys.add(KEY_RESTRICT_BACKGROUND); |
| } |
| |
| return keys; |
| } |
| }; |
| } |