Home | History | Annotate | Download | only in datausage
      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
     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
    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
    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 
    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 
    152     private void addWifiSection() {
    153         TemplatePreferenceCategory category = (TemplatePreferenceCategory)
    154                 inflatePreferences(R.xml.data_usage_wifi);
    155         category.setTemplate(NetworkTemplate.buildTemplateWifiWildcard(), 0, services);
    156     }
    157 
    158     private void addEthernetSection() {
    159         TemplatePreferenceCategory category = (TemplatePreferenceCategory)
    160                 inflatePreferences(R.xml.data_usage_ethernet);
    161         category.setTemplate(NetworkTemplate.buildTemplateEthernet(), 0, services);
    162     }
    163 
    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 
    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
    185     public void onResume() {
    186         super.onResume();
    187         updateState();
    188     }
    189 
    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 
    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 
    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
    257     protected int getMetricsCategory() {
    258         return MetricsEvent.DATA_USAGE_SUMMARY;
    259     }
    260 
    261     /**
    262      * Test if device has an ethernet network connection.
    263      */
    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 
    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      */
    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 
    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 
    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 
    345         public SummaryProvider(Activity activity, SummaryLoader summaryLoader) {
    346             mActivity = activity;
    347             mSummaryLoader = summaryLoader;
    348             mDataController = new DataUsageController(activity);
    349         }
    350 
    351         @Override
    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