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