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