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