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
15package com.android.settings.datausage;
16
17import android.app.Activity;
18import android.content.ComponentName;
19import android.content.Context;
20import android.content.Intent;
21import android.net.NetworkPolicyManager;
22import android.net.NetworkTemplate;
23import android.os.Bundle;
24import android.os.UserManager;
25import android.provider.SearchIndexableResource;
26import android.support.annotation.VisibleForTesting;
27import android.support.v7.preference.Preference;
28import android.support.v7.preference.PreferenceScreen;
29import android.telephony.SubscriptionInfo;
30import android.telephony.SubscriptionManager;
31import android.text.BidiFormatter;
32import android.text.Spannable;
33import android.text.SpannableString;
34import android.text.TextUtils;
35import android.text.format.Formatter;
36import android.text.style.RelativeSizeSpan;
37import android.view.Menu;
38import android.view.MenuInflater;
39import android.view.MenuItem;
40
41import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
42import com.android.settings.R;
43import com.android.settings.SummaryPreference;
44import com.android.settings.Utils;
45import com.android.settings.dashboard.SummaryLoader;
46import com.android.settings.search.BaseSearchIndexProvider;
47import com.android.settings.search.Indexable;
48import com.android.settingslib.NetworkPolicyEditor;
49import com.android.settingslib.core.AbstractPreferenceController;
50import com.android.settingslib.net.DataUsageController;
51
52import java.util.ArrayList;
53import java.util.List;
54
55/**
56 * Legacy {@link DataUsageSummary} fragment.
57 */
58public class DataUsageSummaryLegacy extends DataUsageBaseFragment implements Indexable,
59        DataUsageEditController {
60
61    private static final String TAG = "DataUsageSummaryLegacy";
62
63    static final boolean LOGD = false;
64
65    public static final String KEY_RESTRICT_BACKGROUND = "restrict_background_legacy";
66
67    private static final String KEY_STATUS_HEADER = "status_header";
68    private static final String KEY_LIMIT_SUMMARY = "limit_summary";
69
70    // Mobile data keys
71    public static final String KEY_MOBILE_USAGE_TITLE = "mobile_category";
72    public static final String KEY_MOBILE_DATA_USAGE_TOGGLE = "data_usage_enable";
73    public static final String KEY_MOBILE_DATA_USAGE = "cellular_data_usage";
74    public static final String KEY_MOBILE_BILLING_CYCLE = "billing_preference";
75
76    // Wifi keys
77    public static final String KEY_WIFI_USAGE_TITLE = "wifi_category";
78    public static final String KEY_WIFI_DATA_USAGE = "wifi_data_usage";
79
80    private DataUsageController mDataUsageController;
81    private DataUsageInfoController mDataInfoController;
82    private SummaryPreference mSummaryPreference;
83    private Preference mLimitPreference;
84    private NetworkTemplate mDefaultTemplate;
85    private int mDataUsageTemplate;
86    private NetworkPolicyEditor mPolicyEditor;
87
88    @Override
89    public int getHelpResource() {
90        return R.string.help_url_data_usage;
91    }
92
93    @Override
94    public void onCreate(Bundle icicle) {
95        super.onCreate(icicle);
96
97        final Context context = getContext();
98        NetworkPolicyManager policyManager = NetworkPolicyManager.from(context);
99        mPolicyEditor = new NetworkPolicyEditor(policyManager);
100
101        boolean hasMobileData = DataUsageUtils.hasMobileData(context);
102        mDataUsageController = new DataUsageController(context);
103        mDataInfoController = new DataUsageInfoController();
104
105        int defaultSubId = DataUsageUtils.getDefaultSubscriptionId(context);
106        if (defaultSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
107            hasMobileData = false;
108        }
109        mDefaultTemplate = DataUsageUtils.getDefaultTemplate(context, defaultSubId);
110        mSummaryPreference = (SummaryPreference) findPreference(KEY_STATUS_HEADER);
111
112        if (!hasMobileData || !isAdmin()) {
113            removePreference(KEY_RESTRICT_BACKGROUND);
114        }
115        if (hasMobileData) {
116            mLimitPreference = findPreference(KEY_LIMIT_SUMMARY);
117            List<SubscriptionInfo> subscriptions =
118                    services.mSubscriptionManager.getActiveSubscriptionInfoList();
119            if (subscriptions == null || subscriptions.size() == 0) {
120                addMobileSection(defaultSubId);
121            }
122            for (int i = 0; subscriptions != null && i < subscriptions.size(); i++) {
123                SubscriptionInfo subInfo = subscriptions.get(i);
124                if (subscriptions.size() > 1) {
125                    addMobileSection(subInfo.getSubscriptionId(), subInfo);
126                } else {
127                    addMobileSection(subInfo.getSubscriptionId());
128                }
129            }
130            mSummaryPreference.setSelectable(true);
131        } else {
132            removePreference(KEY_LIMIT_SUMMARY);
133            mSummaryPreference.setSelectable(false);
134        }
135        boolean hasWifiRadio = DataUsageUtils.hasWifiRadio(context);
136        if (hasWifiRadio) {
137            addWifiSection();
138        }
139        if (hasEthernet(context)) {
140            addEthernetSection();
141        }
142        mDataUsageTemplate = hasMobileData ? R.string.cell_data_template
143                : hasWifiRadio ? R.string.wifi_data_template
144                : R.string.ethernet_data_template;
145
146        setHasOptionsMenu(true);
147    }
148
149    @Override
150    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
151        if (UserManager.get(getContext()).isAdminUser()) {
152            inflater.inflate(R.menu.data_usage, menu);
153        }
154        super.onCreateOptionsMenu(menu, inflater);
155    }
156
157    @Override
158    public boolean onOptionsItemSelected(MenuItem item) {
159        switch (item.getItemId()) {
160            case R.id.data_usage_menu_cellular_networks: {
161                final Intent intent = new Intent(Intent.ACTION_MAIN);
162                intent.setComponent(new ComponentName("com.android.phone",
163                        "com.android.phone.MobileNetworkSettings"));
164                startActivity(intent);
165                return true;
166            }
167        }
168        return false;
169    }
170
171    @Override
172    public boolean onPreferenceTreeClick(Preference preference) {
173        if (preference == findPreference(KEY_STATUS_HEADER)) {
174            BillingCycleSettings.BytesEditorFragment.show(this, false);
175            return false;
176        }
177        return super.onPreferenceTreeClick(preference);
178    }
179
180    @Override
181    protected int getPreferenceScreenResId() {
182        return R.xml.data_usage_legacy;
183    }
184
185    @Override
186    protected String getLogTag() {
187        return TAG;
188    }
189
190    @Override
191    protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
192        return null;
193    }
194
195    private void addMobileSection(int subId) {
196        addMobileSection(subId, null);
197    }
198
199    private void addMobileSection(int subId, SubscriptionInfo subInfo) {
200        TemplatePreferenceCategory category = (TemplatePreferenceCategory)
201                inflatePreferences(R.xml.data_usage_cellular);
202        category.setTemplate(getNetworkTemplate(subId), subId, services);
203        category.pushTemplates(services);
204        if (subInfo != null && !TextUtils.isEmpty(subInfo.getDisplayName())) {
205            Preference title  = category.findPreference(KEY_MOBILE_USAGE_TITLE);
206            title.setTitle(subInfo.getDisplayName());
207        }
208    }
209
210    private void addWifiSection() {
211        TemplatePreferenceCategory category = (TemplatePreferenceCategory)
212                inflatePreferences(R.xml.data_usage_wifi);
213        category.setTemplate(NetworkTemplate.buildTemplateWifiWildcard(), 0, services);
214    }
215
216    private void addEthernetSection() {
217        TemplatePreferenceCategory category = (TemplatePreferenceCategory)
218                inflatePreferences(R.xml.data_usage_ethernet);
219        category.setTemplate(NetworkTemplate.buildTemplateEthernet(), 0, services);
220    }
221
222    private Preference inflatePreferences(int resId) {
223        PreferenceScreen rootPreferences = getPreferenceManager().inflateFromResource(
224                getPrefContext(), resId, null);
225        Preference pref = rootPreferences.getPreference(0);
226        rootPreferences.removeAll();
227
228        PreferenceScreen screen = getPreferenceScreen();
229        pref.setOrder(screen.getPreferenceCount());
230        screen.addPreference(pref);
231
232        return pref;
233    }
234
235    private NetworkTemplate getNetworkTemplate(int subscriptionId) {
236        NetworkTemplate mobileAll = NetworkTemplate.buildTemplateMobileAll(
237                services.mTelephonyManager.getSubscriberId(subscriptionId));
238        return NetworkTemplate.normalize(mobileAll,
239                services.mTelephonyManager.getMergedSubscriberIds());
240    }
241
242    @Override
243    public void onResume() {
244        super.onResume();
245        updateState();
246    }
247
248    @VisibleForTesting
249    static CharSequence formatUsage(Context context, String template, long usageLevel) {
250        final float LARGER_SIZE = 1.25f * 1.25f;  // (1/0.8)^2
251        final float SMALLER_SIZE = 1.0f / LARGER_SIZE;  // 0.8^2
252        final int FLAGS = Spannable.SPAN_INCLUSIVE_INCLUSIVE;
253
254        final Formatter.BytesResult usedResult = Formatter.formatBytes(context.getResources(),
255                usageLevel, Formatter.FLAG_CALCULATE_ROUNDED);
256        final SpannableString enlargedValue = new SpannableString(usedResult.value);
257        enlargedValue.setSpan(new RelativeSizeSpan(LARGER_SIZE), 0, enlargedValue.length(), FLAGS);
258
259        final SpannableString amountTemplate = new SpannableString(
260                context.getString(com.android.internal.R.string.fileSizeSuffix)
261                .replace("%1$s", "^1").replace("%2$s", "^2"));
262        final CharSequence formattedUsage = TextUtils.expandTemplate(amountTemplate,
263                enlargedValue, usedResult.units);
264
265        final SpannableString fullTemplate = new SpannableString(template);
266        fullTemplate.setSpan(new RelativeSizeSpan(SMALLER_SIZE), 0, fullTemplate.length(), FLAGS);
267        return TextUtils.expandTemplate(fullTemplate,
268                BidiFormatter.getInstance().unicodeWrap(formattedUsage.toString()));
269    }
270
271    private void updateState() {
272        DataUsageController.DataUsageInfo info = mDataUsageController.getDataUsageInfo(
273                mDefaultTemplate);
274        Context context = getContext();
275        mDataInfoController.updateDataLimit(info,
276                services.mPolicyEditor.getPolicy(mDefaultTemplate));
277
278        if (mSummaryPreference != null) {
279            mSummaryPreference.setTitle(
280                    formatUsage(context, getString(mDataUsageTemplate), info.usageLevel));
281            final long limit = mDataInfoController.getSummaryLimit(info);
282            mSummaryPreference.setSummary(info.period);
283            if (limit <= 0) {
284                mSummaryPreference.setChartEnabled(false);
285            } else {
286                mSummaryPreference.setChartEnabled(true);
287                mSummaryPreference.setLabels(Formatter.formatFileSize(context, 0),
288                        Formatter.formatFileSize(context, limit));
289                mSummaryPreference.setRatios(info.usageLevel / (float) limit, 0,
290                        (limit - info.usageLevel) / (float) limit);
291            }
292        }
293        if (mLimitPreference != null && (info.warningLevel > 0 || info.limitLevel > 0)) {
294            String warning = Formatter.formatFileSize(context, info.warningLevel);
295            String limit = Formatter.formatFileSize(context, info.limitLevel);
296            mLimitPreference.setSummary(getString(info.limitLevel <= 0 ? R.string.cell_warning_only
297                    : R.string.cell_warning_and_limit, warning, limit));
298        } else if (mLimitPreference != null) {
299            mLimitPreference.setSummary(null);
300        }
301
302        PreferenceScreen screen = getPreferenceScreen();
303        for (int i = 1; i < screen.getPreferenceCount(); i++) {
304            ((TemplatePreferenceCategory) screen.getPreference(i)).pushTemplates(services);
305        }
306    }
307
308    @Override
309    public int getMetricsCategory() {
310        return MetricsEvent.DATA_USAGE_SUMMARY;
311    }
312
313    @Override
314    public NetworkPolicyEditor getNetworkPolicyEditor() {
315        return services.mPolicyEditor;
316    }
317
318    @Override
319    public NetworkTemplate getNetworkTemplate() {
320        return mDefaultTemplate;
321    }
322
323    @Override
324    public void updateDataUsage() {
325        updateState();
326    }
327
328    private static class SummaryProvider
329            implements SummaryLoader.SummaryProvider {
330
331        private final Activity mActivity;
332        private final SummaryLoader mSummaryLoader;
333        private final DataUsageController mDataController;
334
335        public SummaryProvider(Activity activity, SummaryLoader summaryLoader) {
336            mActivity = activity;
337            mSummaryLoader = summaryLoader;
338            mDataController = new DataUsageController(activity);
339        }
340
341        @Override
342        public void setListening(boolean listening) {
343            if (listening) {
344                DataUsageController.DataUsageInfo info = mDataController.getDataUsageInfo();
345                String used;
346                if (info == null) {
347                    used = Formatter.formatFileSize(mActivity, 0);
348                } else if (info.limitLevel <= 0) {
349                    used = Formatter.formatFileSize(mActivity, info.usageLevel);
350                } else {
351                    used = Utils.formatPercentage(info.usageLevel, info.limitLevel);
352                }
353                mSummaryLoader.setSummary(this,
354                        mActivity.getString(R.string.data_usage_summary_format, used));
355            }
356        }
357    }
358
359    public static final SummaryLoader.SummaryProviderFactory SUMMARY_PROVIDER_FACTORY
360        = SummaryProvider::new;
361
362    /**
363     * For search
364     */
365    public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
366        new BaseSearchIndexProvider() {
367
368            @Override
369            public List<SearchIndexableResource> getXmlResourcesToIndex(Context context,
370                    boolean enabled) {
371                List<SearchIndexableResource> resources = new ArrayList<>();
372                SearchIndexableResource resource = new SearchIndexableResource(context);
373                resource.xmlResId = R.xml.data_usage_legacy;
374                resources.add(resource);
375
376                resource = new SearchIndexableResource(context);
377                resource.xmlResId = R.xml.data_usage_cellular;
378                resources.add(resource);
379
380                resource = new SearchIndexableResource(context);
381                resource.xmlResId = R.xml.data_usage_wifi;
382                resources.add(resource);
383
384                return resources;
385            }
386
387            @Override
388            public List<String> getNonIndexableKeys(Context context) {
389                List<String> keys = super.getNonIndexableKeys(context);
390
391                if (!DataUsageUtils.hasMobileData(context)) {
392                    keys.add(KEY_MOBILE_USAGE_TITLE);
393                    keys.add(KEY_MOBILE_DATA_USAGE_TOGGLE);
394                    keys.add(KEY_MOBILE_DATA_USAGE);
395                    keys.add(KEY_MOBILE_BILLING_CYCLE);
396                }
397
398                if (!DataUsageUtils.hasWifiRadio(context)) {
399                    keys.add(KEY_WIFI_DATA_USAGE);
400                }
401
402                // This title is named Wifi, and will confuse users.
403                keys.add(KEY_WIFI_USAGE_TITLE);
404
405                return keys;
406            }
407        };
408}
409