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