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.NetworkTemplate;
     22 import android.os.Bundle;
     23 import android.provider.SearchIndexableResource;
     24 import android.support.annotation.VisibleForTesting;
     25 import android.support.v7.preference.Preference;
     26 import android.support.v7.preference.PreferenceScreen;
     27 import android.telephony.SubscriptionInfo;
     28 import android.telephony.SubscriptionManager;
     29 import android.telephony.SubscriptionPlan;
     30 import android.text.BidiFormatter;
     31 import android.text.Spannable;
     32 import android.text.SpannableString;
     33 import android.text.TextUtils;
     34 import android.text.format.Formatter;
     35 import android.text.style.RelativeSizeSpan;
     36 import android.view.MenuItem;
     37 
     38 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
     39 import com.android.settings.R;
     40 import com.android.settings.Utils;
     41 import com.android.settings.dashboard.SummaryLoader;
     42 import com.android.settings.search.BaseSearchIndexProvider;
     43 import com.android.settings.search.Indexable;
     44 import com.android.settingslib.NetworkPolicyEditor;
     45 import com.android.settingslib.core.AbstractPreferenceController;
     46 import com.android.settingslib.net.DataUsageController;
     47 
     48 import java.util.ArrayList;
     49 import java.util.List;
     50 
     51 /**
     52  * Settings preference fragment that displays data usage summary.
     53  */
     54 public class DataUsageSummary extends DataUsageBaseFragment implements Indexable,
     55         DataUsageEditController {
     56 
     57     private static final String TAG = "DataUsageSummary";
     58 
     59     static final boolean LOGD = false;
     60 
     61     public static final String KEY_RESTRICT_BACKGROUND = "restrict_background";
     62 
     63     private static final String KEY_STATUS_HEADER = "status_header";
     64 
     65     // Mobile data keys
     66     public static final String KEY_MOBILE_USAGE_TITLE = "mobile_category";
     67     public static final String KEY_MOBILE_DATA_USAGE_TOGGLE = "data_usage_enable";
     68     public static final String KEY_MOBILE_DATA_USAGE = "cellular_data_usage";
     69     public static final String KEY_MOBILE_BILLING_CYCLE = "billing_preference";
     70 
     71     // Wifi keys
     72     public static final String KEY_WIFI_USAGE_TITLE = "wifi_category";
     73     public static final String KEY_WIFI_DATA_USAGE = "wifi_data_usage";
     74 
     75     private DataUsageSummaryPreference mSummaryPreference;
     76     private DataUsageSummaryPreferenceController mSummaryController;
     77     private NetworkTemplate mDefaultTemplate;
     78 
     79     @Override
     80     public 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         Context context = getContext();
     88 
     89         boolean hasMobileData = DataUsageUtils.hasMobileData(context);
     90 
     91         int defaultSubId = DataUsageUtils.getDefaultSubscriptionId(context);
     92         if (defaultSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
     93             hasMobileData = false;
     94         }
     95         mDefaultTemplate = DataUsageUtils.getDefaultTemplate(context, defaultSubId);
     96         mSummaryPreference = (DataUsageSummaryPreference) findPreference(KEY_STATUS_HEADER);
     97 
     98         if (!hasMobileData || !isAdmin()) {
     99             removePreference(KEY_RESTRICT_BACKGROUND);
    100         }
    101         boolean hasWifiRadio = DataUsageUtils.hasWifiRadio(context);
    102         if (hasMobileData) {
    103             addMobileSection(defaultSubId);
    104             if (DataUsageUtils.hasSim(context) && hasWifiRadio) {
    105                 // If the device has a SIM installed, the data usage section shows usage for mobile,
    106                 // and the WiFi section is added if there is a WiFi radio - legacy behavior.
    107                 addWifiSection();
    108             }
    109             // Do not add the WiFi section if either there is no WiFi radio (obviously) or if no
    110             // SIM is installed. In the latter case the data usage section will show WiFi usage and
    111             // there should be no explicit WiFi section added.
    112         } else if (hasWifiRadio) {
    113             addWifiSection();
    114         }
    115         if (DataUsageUtils.hasEthernet(context)) {
    116             addEthernetSection();
    117         }
    118         setHasOptionsMenu(true);
    119     }
    120 
    121     @Override
    122     public boolean onOptionsItemSelected(MenuItem item) {
    123         switch (item.getItemId()) {
    124             case R.id.data_usage_menu_cellular_networks: {
    125                 final Intent intent = new Intent(Intent.ACTION_MAIN);
    126                 intent.setComponent(new ComponentName("com.android.phone",
    127                         "com.android.phone.MobileNetworkSettings"));
    128                 startActivity(intent);
    129                 return true;
    130             }
    131         }
    132         return false;
    133     }
    134 
    135     @Override
    136     public boolean onPreferenceTreeClick(Preference preference) {
    137         if (preference == findPreference(KEY_STATUS_HEADER)) {
    138             BillingCycleSettings.BytesEditorFragment.show(this, false);
    139             return false;
    140         }
    141         return super.onPreferenceTreeClick(preference);
    142     }
    143 
    144     @Override
    145     protected int getPreferenceScreenResId() {
    146         return R.xml.data_usage;
    147     }
    148 
    149     @Override
    150     protected String getLogTag() {
    151         return TAG;
    152     }
    153 
    154     @Override
    155     protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
    156         final Activity activity = getActivity();
    157         final ArrayList<AbstractPreferenceController> controllers = new ArrayList<>();
    158         mSummaryController =
    159                 new DataUsageSummaryPreferenceController(activity, getLifecycle(), this);
    160         controllers.add(mSummaryController);
    161         getLifecycle().addObserver(mSummaryController);
    162         return controllers;
    163     }
    164 
    165     @VisibleForTesting
    166     void addMobileSection(int subId) {
    167         addMobileSection(subId, null);
    168     }
    169 
    170     private void addMobileSection(int subId, SubscriptionInfo subInfo) {
    171         TemplatePreferenceCategory category = (TemplatePreferenceCategory)
    172                 inflatePreferences(R.xml.data_usage_cellular);
    173         category.setTemplate(getNetworkTemplate(subId), subId, services);
    174         category.pushTemplates(services);
    175         if (subInfo != null && !TextUtils.isEmpty(subInfo.getDisplayName())) {
    176             Preference title  = category.findPreference(KEY_MOBILE_USAGE_TITLE);
    177             title.setTitle(subInfo.getDisplayName());
    178         }
    179     }
    180 
    181     @VisibleForTesting
    182     void addWifiSection() {
    183         TemplatePreferenceCategory category = (TemplatePreferenceCategory)
    184                 inflatePreferences(R.xml.data_usage_wifi);
    185         category.setTemplate(NetworkTemplate.buildTemplateWifiWildcard(), 0, services);
    186     }
    187 
    188     private void addEthernetSection() {
    189         TemplatePreferenceCategory category = (TemplatePreferenceCategory)
    190                 inflatePreferences(R.xml.data_usage_ethernet);
    191         category.setTemplate(NetworkTemplate.buildTemplateEthernet(), 0, services);
    192     }
    193 
    194     private Preference inflatePreferences(int resId) {
    195         PreferenceScreen rootPreferences = getPreferenceManager().inflateFromResource(
    196                 getPrefContext(), resId, null);
    197         Preference pref = rootPreferences.getPreference(0);
    198         rootPreferences.removeAll();
    199 
    200         PreferenceScreen screen = getPreferenceScreen();
    201         pref.setOrder(screen.getPreferenceCount());
    202         screen.addPreference(pref);
    203 
    204         return pref;
    205     }
    206 
    207     private NetworkTemplate getNetworkTemplate(int subscriptionId) {
    208         NetworkTemplate mobileAll = NetworkTemplate.buildTemplateMobileAll(
    209                 services.mTelephonyManager.getSubscriberId(subscriptionId));
    210         return NetworkTemplate.normalize(mobileAll,
    211                 services.mTelephonyManager.getMergedSubscriberIds());
    212     }
    213 
    214     @Override
    215     public void onResume() {
    216         super.onResume();
    217         updateState();
    218     }
    219 
    220     @VisibleForTesting
    221     static CharSequence formatUsage(Context context, String template, long usageLevel) {
    222         final float LARGER_SIZE = 1.25f * 1.25f;  // (1/0.8)^2
    223         final float SMALLER_SIZE = 1.0f / LARGER_SIZE;  // 0.8^2
    224         return formatUsage(context, template, usageLevel, LARGER_SIZE, SMALLER_SIZE);
    225     }
    226 
    227     static CharSequence formatUsage(Context context, String template, long usageLevel,
    228                                     float larger, float smaller) {
    229         final int FLAGS = Spannable.SPAN_INCLUSIVE_INCLUSIVE;
    230 
    231         final Formatter.BytesResult usedResult = Formatter.formatBytes(context.getResources(),
    232                 usageLevel, Formatter.FLAG_CALCULATE_ROUNDED | Formatter.FLAG_IEC_UNITS);
    233         final SpannableString enlargedValue = new SpannableString(usedResult.value);
    234         enlargedValue.setSpan(new RelativeSizeSpan(larger), 0, enlargedValue.length(), FLAGS);
    235 
    236         final SpannableString amountTemplate = new SpannableString(
    237                 context.getString(com.android.internal.R.string.fileSizeSuffix)
    238                 .replace("%1$s", "^1").replace("%2$s", "^2"));
    239         final CharSequence formattedUsage = TextUtils.expandTemplate(amountTemplate,
    240                 enlargedValue, usedResult.units);
    241 
    242         final SpannableString fullTemplate = new SpannableString(template);
    243         fullTemplate.setSpan(new RelativeSizeSpan(smaller), 0, fullTemplate.length(), FLAGS);
    244         return TextUtils.expandTemplate(fullTemplate,
    245                 BidiFormatter.getInstance().unicodeWrap(formattedUsage.toString()));
    246     }
    247 
    248     private void updateState() {
    249         PreferenceScreen screen = getPreferenceScreen();
    250         for (int i = 1; i < screen.getPreferenceCount(); i++) {
    251           Preference currentPreference = screen.getPreference(i);
    252           if (currentPreference instanceof TemplatePreferenceCategory) {
    253             ((TemplatePreferenceCategory) currentPreference).pushTemplates(services);
    254           }
    255         }
    256     }
    257 
    258     @Override
    259     public int getMetricsCategory() {
    260         return MetricsEvent.DATA_USAGE_SUMMARY;
    261     }
    262 
    263     @Override
    264     public NetworkPolicyEditor getNetworkPolicyEditor() {
    265         return services.mPolicyEditor;
    266     }
    267 
    268     @Override
    269     public NetworkTemplate getNetworkTemplate() {
    270         return mDefaultTemplate;
    271     }
    272 
    273     @Override
    274     public void updateDataUsage() {
    275         updateState();
    276         mSummaryController.updateState(mSummaryPreference);
    277     }
    278 
    279     private static class SummaryProvider
    280             implements SummaryLoader.SummaryProvider {
    281 
    282         private final Activity mActivity;
    283         private final SummaryLoader mSummaryLoader;
    284         private final DataUsageController mDataController;
    285 
    286         public SummaryProvider(Activity activity, SummaryLoader summaryLoader) {
    287             mActivity = activity;
    288             mSummaryLoader = summaryLoader;
    289             mDataController = new DataUsageController(activity);
    290         }
    291 
    292         @Override
    293         public void setListening(boolean listening) {
    294             if (listening) {
    295                 if (DataUsageUtils.hasSim(mActivity)) {
    296                     mSummaryLoader.setSummary(this,
    297                             mActivity.getString(R.string.data_usage_summary_format,
    298                                     formatUsedData()));
    299                 } else {
    300                     final DataUsageController.DataUsageInfo info =
    301                             mDataController
    302                                     .getDataUsageInfo(NetworkTemplate.buildTemplateWifiWildcard());
    303 
    304                     if (info == null) {
    305                         mSummaryLoader.setSummary(this, null);
    306                     } else {
    307                         final CharSequence wifiFormat = mActivity
    308                                 .getText(R.string.data_usage_wifi_format);
    309                         final CharSequence sizeText =
    310                                 DataUsageUtils.formatDataUsage(mActivity, info.usageLevel);
    311                         mSummaryLoader.setSummary(this,
    312                                 TextUtils.expandTemplate(wifiFormat, sizeText));
    313                     }
    314                 }
    315             }
    316         }
    317 
    318         private CharSequence formatUsedData() {
    319             SubscriptionManager subscriptionManager = (SubscriptionManager) mActivity
    320                 .getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
    321             int defaultSubId = subscriptionManager.getDefaultSubscriptionId();
    322             if (defaultSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
    323                 return formatFallbackData();
    324             }
    325             SubscriptionPlan dfltPlan = DataUsageSummaryPreferenceController
    326                     .getPrimaryPlan(subscriptionManager, defaultSubId);
    327             if (dfltPlan == null) {
    328                 return formatFallbackData();
    329             }
    330             if (DataUsageSummaryPreferenceController.unlimited(dfltPlan.getDataLimitBytes())) {
    331                 return DataUsageUtils.formatDataUsage(mActivity, dfltPlan.getDataUsageBytes());
    332             } else {
    333                 return Utils.formatPercentage(dfltPlan.getDataUsageBytes(),
    334                     dfltPlan.getDataLimitBytes());
    335             }
    336         }
    337 
    338         private CharSequence formatFallbackData() {
    339             DataUsageController.DataUsageInfo info = mDataController.getDataUsageInfo();
    340             if (info == null) {
    341                 return DataUsageUtils.formatDataUsage(mActivity, 0);
    342             } else if (info.limitLevel <= 0) {
    343                 return DataUsageUtils.formatDataUsage(mActivity, info.usageLevel);
    344             } else {
    345                 return Utils.formatPercentage(info.usageLevel, info.limitLevel);
    346             }
    347         }
    348     }
    349 
    350     public static final SummaryLoader.SummaryProviderFactory SUMMARY_PROVIDER_FACTORY
    351         = SummaryProvider::new;
    352 
    353     /**
    354      * For search
    355      */
    356     public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
    357         new BaseSearchIndexProvider() {
    358 
    359             @Override
    360             public List<SearchIndexableResource> getXmlResourcesToIndex(Context context,
    361                     boolean enabled) {
    362                 List<SearchIndexableResource> resources = new ArrayList<>();
    363                 SearchIndexableResource resource = new SearchIndexableResource(context);
    364                 resource.xmlResId = R.xml.data_usage;
    365                 resources.add(resource);
    366 
    367                 resource = new SearchIndexableResource(context);
    368                 resource.xmlResId = R.xml.data_usage_cellular;
    369                 resources.add(resource);
    370 
    371                 resource = new SearchIndexableResource(context);
    372                 resource.xmlResId = R.xml.data_usage_wifi;
    373                 resources.add(resource);
    374 
    375                 return resources;
    376             }
    377 
    378             @Override
    379             public List<String> getNonIndexableKeys(Context context) {
    380                 List<String> keys = super.getNonIndexableKeys(context);
    381 
    382                 if (!DataUsageUtils.hasMobileData(context)) {
    383                     keys.add(KEY_MOBILE_USAGE_TITLE);
    384                     keys.add(KEY_MOBILE_DATA_USAGE_TOGGLE);
    385                     keys.add(KEY_MOBILE_DATA_USAGE);
    386                     keys.add(KEY_MOBILE_BILLING_CYCLE);
    387                 }
    388 
    389                 if (!DataUsageUtils.hasWifiRadio(context)) {
    390                     keys.add(KEY_WIFI_DATA_USAGE);
    391                 }
    392 
    393                 // This title is named Wifi, and will confuse users.
    394                 keys.add(KEY_WIFI_USAGE_TITLE);
    395 
    396                 return keys;
    397             }
    398         };
    399 }
    400