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