1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.settings;
18
19import static android.net.ConnectivityManager.TYPE_ETHERNET;
20import static android.net.ConnectivityManager.TYPE_MOBILE;
21import static android.net.ConnectivityManager.TYPE_WIFI;
22import static android.net.ConnectivityManager.TYPE_WIMAX;
23import static android.net.NetworkPolicy.LIMIT_DISABLED;
24import static android.net.NetworkPolicy.WARNING_DISABLED;
25import static android.net.NetworkPolicyManager.EXTRA_NETWORK_TEMPLATE;
26import static android.net.NetworkPolicyManager.POLICY_NONE;
27import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
28import static android.net.NetworkPolicyManager.computeLastCycleBoundary;
29import static android.net.NetworkPolicyManager.computeNextCycleBoundary;
30import static android.net.NetworkTemplate.MATCH_MOBILE_3G_LOWER;
31import static android.net.NetworkTemplate.MATCH_MOBILE_4G;
32import static android.net.NetworkTemplate.MATCH_MOBILE_ALL;
33import static android.net.NetworkTemplate.MATCH_WIFI;
34import static android.net.NetworkTemplate.buildTemplateEthernet;
35import static android.net.NetworkTemplate.buildTemplateMobile3gLower;
36import static android.net.NetworkTemplate.buildTemplateMobile4g;
37import static android.net.NetworkTemplate.buildTemplateMobileAll;
38import static android.net.NetworkTemplate.buildTemplateWifiWildcard;
39import static android.net.TrafficStats.GB_IN_BYTES;
40import static android.net.TrafficStats.MB_IN_BYTES;
41import static android.net.TrafficStats.UID_REMOVED;
42import static android.net.TrafficStats.UID_TETHERING;
43import static android.telephony.TelephonyManager.SIM_STATE_READY;
44import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH;
45import static android.text.format.DateUtils.FORMAT_SHOW_DATE;
46import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
47import static com.android.internal.util.Preconditions.checkNotNull;
48import static com.android.settings.Utils.prepareCustomPreferencesList;
49
50import android.animation.LayoutTransition;
51import android.app.ActivityManager;
52import android.app.AlertDialog;
53import android.app.Dialog;
54import android.app.DialogFragment;
55import android.app.Fragment;
56import android.app.FragmentTransaction;
57import android.app.LoaderManager.LoaderCallbacks;
58import android.content.ComponentName;
59import android.content.Context;
60import android.content.DialogInterface;
61import android.content.Intent;
62import android.content.Loader;
63import android.content.SharedPreferences;
64import android.content.pm.PackageManager;
65import android.content.pm.UserInfo;
66import android.content.res.Resources;
67import android.content.res.TypedArray;
68import android.graphics.Color;
69import android.graphics.drawable.ColorDrawable;
70import android.graphics.drawable.Drawable;
71import android.net.ConnectivityManager;
72import android.net.INetworkPolicyManager;
73import android.net.INetworkStatsService;
74import android.net.INetworkStatsSession;
75import android.net.NetworkPolicy;
76import android.net.NetworkPolicyManager;
77import android.net.NetworkStats;
78import android.net.NetworkStatsHistory;
79import android.net.NetworkTemplate;
80import android.net.TrafficStats;
81import android.os.AsyncTask;
82import android.os.Bundle;
83import android.os.INetworkManagementService;
84import android.os.Parcel;
85import android.os.Parcelable;
86import android.os.RemoteException;
87import android.os.ServiceManager;
88import android.os.SystemProperties;
89import android.os.UserHandle;
90import android.os.UserManager;
91import android.preference.Preference;
92import android.telephony.SubscriptionInfo;
93import android.telephony.SubscriptionManager;
94import android.telephony.TelephonyManager;
95import android.text.TextUtils;
96import android.text.format.DateUtils;
97import android.text.format.Formatter;
98import android.text.format.Time;
99import android.util.Log;
100import android.util.SparseArray;
101import android.util.SparseBooleanArray;
102import android.view.LayoutInflater;
103import android.view.Menu;
104import android.view.MenuInflater;
105import android.view.MenuItem;
106import android.view.View;
107import android.view.View.OnClickListener;
108import android.view.ViewGroup;
109import android.widget.AdapterView;
110import android.widget.AdapterView.OnItemClickListener;
111import android.widget.AdapterView.OnItemSelectedListener;
112import android.widget.ArrayAdapter;
113import android.widget.BaseAdapter;
114import android.widget.Button;
115import android.widget.ImageView;
116import android.widget.LinearLayout;
117import android.widget.ListView;
118import android.widget.NumberPicker;
119import android.widget.ProgressBar;
120import android.widget.Spinner;
121import android.widget.Switch;
122import android.widget.TabHost;
123import android.widget.TabHost.OnTabChangeListener;
124import android.widget.TabHost.TabContentFactory;
125import android.widget.TabHost.TabSpec;
126import android.widget.TabWidget;
127import android.widget.TextView;
128
129import com.android.internal.telephony.PhoneConstants;
130import com.android.settings.drawable.InsetBoundsDrawable;
131import com.android.settings.net.ChartData;
132import com.android.settings.net.ChartDataLoader;
133import com.android.settings.net.DataUsageMeteredSettings;
134import com.android.settings.net.NetworkPolicyEditor;
135import com.android.settings.net.SummaryForAllUidLoader;
136import com.android.settings.net.UidDetail;
137import com.android.settings.net.UidDetailProvider;
138import com.android.settings.search.BaseSearchIndexProvider;
139import com.android.settings.search.Indexable;
140import com.android.settings.search.SearchIndexableRaw;
141import com.android.settings.sim.SimSettings;
142import com.android.settings.widget.ChartDataUsageView;
143import com.android.settings.widget.ChartDataUsageView.DataUsageChartListener;
144import com.android.settings.widget.ChartNetworkSeriesView;
145
146import com.google.android.collect.Lists;
147
148import libcore.util.Objects;
149
150import java.util.ArrayList;
151import java.util.Collections;
152import java.util.HashMap;
153import java.util.List;
154import java.util.Locale;
155import java.util.Map;
156import java.util.Set;
157
158/**
159 * Panel showing data usage history across various networks, including options
160 * to inspect based on usage cycle and control through {@link NetworkPolicy}.
161 */
162public class DataUsageSummary extends HighlightingFragment implements Indexable {
163    private static final String TAG = "DataUsage";
164    private static final boolean LOGD = false;
165
166    // TODO: remove this testing code
167    private static final boolean TEST_ANIM = false;
168    private static final boolean TEST_RADIOS = false;
169
170    private static final String TEST_RADIOS_PROP = "test.radios";
171    private static final String TEST_SUBSCRIBER_PROP = "test.subscriberid";
172
173    private static final String TAB_3G = "3g";
174    private static final String TAB_4G = "4g";
175    private static final String TAB_MOBILE = "mobile";
176    private static final String TAB_WIFI = "wifi";
177    private static final String TAB_ETHERNET = "ethernet";
178
179    private static final String TAG_CONFIRM_DATA_DISABLE = "confirmDataDisable";
180    private static final String TAG_CONFIRM_LIMIT = "confirmLimit";
181    private static final String TAG_CYCLE_EDITOR = "cycleEditor";
182    private static final String TAG_WARNING_EDITOR = "warningEditor";
183    private static final String TAG_LIMIT_EDITOR = "limitEditor";
184    private static final String TAG_CONFIRM_RESTRICT = "confirmRestrict";
185    private static final String TAG_DENIED_RESTRICT = "deniedRestrict";
186    private static final String TAG_CONFIRM_APP_RESTRICT = "confirmAppRestrict";
187    private static final String TAG_APP_DETAILS = "appDetails";
188
189    private static final String DATA_USAGE_ENABLE_MOBILE_KEY = "data_usage_enable_mobile";
190    private static final String DATA_USAGE_DISABLE_MOBILE_LIMIT_KEY =
191            "data_usage_disable_mobile_limit";
192    private static final String DATA_USAGE_CYCLE_KEY = "data_usage_cycle";
193
194    private static final int LOADER_CHART_DATA = 2;
195    private static final int LOADER_SUMMARY = 3;
196
197    private INetworkManagementService mNetworkService;
198    private INetworkStatsService mStatsService;
199    private NetworkPolicyManager mPolicyManager;
200    private TelephonyManager mTelephonyManager;
201    private SubscriptionManager mSubscriptionManager;
202
203    private INetworkStatsSession mStatsSession;
204
205    private static final String PREF_FILE = "data_usage";
206    private static final String PREF_SHOW_WIFI = "show_wifi";
207    private static final String PREF_SHOW_ETHERNET = "show_ethernet";
208
209    private SharedPreferences mPrefs;
210
211    private TabHost mTabHost;
212    private ViewGroup mTabsContainer;
213    private TabWidget mTabWidget;
214    private ListView mListView;
215    private ChartNetworkSeriesView mSeries;
216    private ChartNetworkSeriesView mDetailedSeries;
217    private DataUsageAdapter mAdapter;
218
219    /** Distance to inset content from sides, when needed. */
220    private int mInsetSide = 0;
221
222    private ViewGroup mHeader;
223
224    private ViewGroup mNetworkSwitchesContainer;
225    private LinearLayout mNetworkSwitches;
226    private boolean mDataEnabledSupported;
227    private Switch mDataEnabled;
228    private View mDataEnabledView;
229    private boolean mDisableAtLimitSupported;
230    private Switch mDisableAtLimit;
231    private View mDisableAtLimitView;
232
233    private View mCycleView;
234    private Spinner mCycleSpinner;
235    private CycleAdapter mCycleAdapter;
236    private TextView mCycleSummary;
237
238    private ChartDataUsageView mChart;
239    private View mDisclaimer;
240    private TextView mEmpty;
241    private View mStupidPadding;
242
243    private View mAppDetail;
244    private ImageView mAppIcon;
245    private ViewGroup mAppTitles;
246    private TextView mAppTotal;
247    private TextView mAppForeground;
248    private TextView mAppBackground;
249    private Button mAppSettings;
250
251    private LinearLayout mAppSwitches;
252    private Switch mAppRestrict;
253    private View mAppRestrictView;
254
255    private boolean mShowWifi = false;
256    private boolean mShowEthernet = false;
257
258    private NetworkTemplate mTemplate;
259    private ChartData mChartData;
260
261    private AppItem mCurrentApp = null;
262
263    private Intent mAppSettingsIntent;
264
265    private NetworkPolicyEditor mPolicyEditor;
266
267    private String mCurrentTab = null;
268    private String mIntentTab = null;
269
270    private MenuItem mMenuRestrictBackground;
271    private MenuItem mMenuShowWifi;
272    private MenuItem mMenuShowEthernet;
273    private MenuItem mMenuSimCards;
274    private MenuItem mMenuCellularNetworks;
275
276    private List<SubscriptionInfo> mSubInfoList;
277    private Map<Integer,String> mMobileTagMap;
278
279    /** Flag used to ignore listeners during binding. */
280    private boolean mBinding;
281
282    private UidDetailProvider mUidDetailProvider;
283
284    /**
285     * Local cache of data enabled for subId, used to work around delays.
286     */
287    private final Map<String, Boolean> mMobileDataEnabled = new HashMap<String, Boolean>();
288
289    @Override
290    public void onCreate(Bundle savedInstanceState) {
291        super.onCreate(savedInstanceState);
292        final Context context = getActivity();
293
294        mNetworkService = INetworkManagementService.Stub.asInterface(
295                ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
296        mStatsService = INetworkStatsService.Stub.asInterface(
297                ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
298        mPolicyManager = NetworkPolicyManager.from(context);
299        mTelephonyManager = TelephonyManager.from(context);
300        mSubscriptionManager = SubscriptionManager.from(context);
301
302        mPrefs = getActivity().getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE);
303
304        mPolicyEditor = new NetworkPolicyEditor(mPolicyManager);
305        mPolicyEditor.read();
306
307        mSubInfoList = mSubscriptionManager.getActiveSubscriptionInfoList();
308        mMobileTagMap = initMobileTabTag(mSubInfoList);
309
310        try {
311            if (!mNetworkService.isBandwidthControlEnabled()) {
312                Log.w(TAG, "No bandwidth control; leaving");
313                getActivity().finish();
314            }
315        } catch (RemoteException e) {
316            Log.w(TAG, "No bandwidth control; leaving");
317            getActivity().finish();
318        }
319
320        try {
321            mStatsSession = mStatsService.openSession();
322        } catch (RemoteException e) {
323            throw new RuntimeException(e);
324        }
325
326        mShowWifi = mPrefs.getBoolean(PREF_SHOW_WIFI, false);
327        mShowEthernet = mPrefs.getBoolean(PREF_SHOW_ETHERNET, false);
328
329        // override preferences when no mobile radio
330        if (!hasReadyMobileRadio(context)) {
331            mShowWifi = true;
332            mShowEthernet = true;
333        }
334
335        setHasOptionsMenu(true);
336    }
337
338    @Override
339    public View onCreateView(LayoutInflater inflater, ViewGroup container,
340            Bundle savedInstanceState) {
341
342        final Context context = inflater.getContext();
343        final View view = inflater.inflate(R.layout.data_usage_summary, container, false);
344
345        mUidDetailProvider = new UidDetailProvider(context);
346
347        mTabHost = (TabHost) view.findViewById(android.R.id.tabhost);
348        mTabsContainer = (ViewGroup) view.findViewById(R.id.tabs_container);
349        mTabWidget = (TabWidget) view.findViewById(android.R.id.tabs);
350        mListView = (ListView) view.findViewById(android.R.id.list);
351
352        // decide if we need to manually inset our content, or if we should rely
353        // on parent container for inset.
354        final boolean shouldInset = mListView.getScrollBarStyle()
355                == View.SCROLLBARS_OUTSIDE_OVERLAY;
356        mInsetSide = 0;
357
358        // adjust padding around tabwidget as needed
359        prepareCustomPreferencesList(container, view, mListView, false);
360
361        mTabHost.setup();
362        mTabHost.setOnTabChangedListener(mTabListener);
363
364        mHeader = (ViewGroup) inflater.inflate(R.layout.data_usage_header, mListView, false);
365        mHeader.setClickable(true);
366
367        mListView.addHeaderView(new View(context), null, true);
368        mListView.addHeaderView(mHeader, null, true);
369        mListView.setItemsCanFocus(true);
370
371        if (mInsetSide > 0) {
372            // inset selector and divider drawables
373            insetListViewDrawables(mListView, mInsetSide);
374            mHeader.setPaddingRelative(mInsetSide, 0, mInsetSide, 0);
375        }
376
377        {
378            // bind network switches
379            mNetworkSwitchesContainer = (ViewGroup) mHeader.findViewById(
380                    R.id.network_switches_container);
381            mNetworkSwitches = (LinearLayout) mHeader.findViewById(R.id.network_switches);
382
383            mDataEnabled = new Switch(inflater.getContext());
384            mDataEnabled.setClickable(false);
385            mDataEnabled.setFocusable(false);
386            mDataEnabledView = inflatePreference(inflater, mNetworkSwitches, mDataEnabled);
387            mDataEnabledView.setTag(R.id.preference_highlight_key,
388                    DATA_USAGE_ENABLE_MOBILE_KEY);
389            mDataEnabledView.setClickable(true);
390            mDataEnabledView.setFocusable(true);
391            mDataEnabledView.setOnClickListener(mDataEnabledListener);
392            mNetworkSwitches.addView(mDataEnabledView);
393
394            mDisableAtLimit = new Switch(inflater.getContext());
395            mDisableAtLimit.setClickable(false);
396            mDisableAtLimit.setFocusable(false);
397            mDisableAtLimitView = inflatePreference(inflater, mNetworkSwitches, mDisableAtLimit);
398            mDisableAtLimitView.setTag(R.id.preference_highlight_key,
399                    DATA_USAGE_DISABLE_MOBILE_LIMIT_KEY);
400            mDisableAtLimitView.setClickable(true);
401            mDisableAtLimitView.setFocusable(true);
402            mDisableAtLimitView.setOnClickListener(mDisableAtLimitListener);
403            mNetworkSwitches.addView(mDisableAtLimitView);
404
405            mCycleView = inflater.inflate(R.layout.data_usage_cycles, mNetworkSwitches, false);
406            mCycleView.setTag(R.id.preference_highlight_key, DATA_USAGE_CYCLE_KEY);
407            mCycleSpinner = (Spinner) mCycleView.findViewById(R.id.cycles_spinner);
408            mCycleAdapter = new CycleAdapter(context);
409            mCycleSpinner.setAdapter(mCycleAdapter);
410            mCycleSpinner.setOnItemSelectedListener(mCycleListener);
411            mCycleSummary = (TextView) mCycleView.findViewById(R.id.cycle_summary);
412            mNetworkSwitches.addView(mCycleView);
413            mSeries = (ChartNetworkSeriesView)view.findViewById(R.id.series);
414            mDetailedSeries = (ChartNetworkSeriesView)view.findViewById(R.id.detail_series);
415        }
416
417        mChart = (ChartDataUsageView) mHeader.findViewById(R.id.chart);
418        mChart.setListener(mChartListener);
419        mChart.bindNetworkPolicy(null);
420
421        {
422            // bind app detail controls
423            mAppDetail = mHeader.findViewById(R.id.app_detail);
424            mAppIcon = (ImageView) mAppDetail.findViewById(R.id.app_icon);
425            mAppTitles = (ViewGroup) mAppDetail.findViewById(R.id.app_titles);
426            mAppForeground = (TextView) mAppDetail.findViewById(R.id.app_foreground);
427            mAppBackground = (TextView) mAppDetail.findViewById(R.id.app_background);
428            mAppSwitches = (LinearLayout) mAppDetail.findViewById(R.id.app_switches);
429
430            mAppSettings = (Button) mAppDetail.findViewById(R.id.app_settings);
431
432            mAppRestrict = new Switch(inflater.getContext());
433            mAppRestrict.setClickable(false);
434            mAppRestrict.setFocusable(false);
435            mAppRestrictView = inflatePreference(inflater, mAppSwitches, mAppRestrict);
436            mAppRestrictView.setClickable(true);
437            mAppRestrictView.setFocusable(true);
438            mAppRestrictView.setOnClickListener(mAppRestrictListener);
439            mAppSwitches.addView(mAppRestrictView);
440        }
441
442        mDisclaimer = mHeader.findViewById(R.id.disclaimer);
443        mEmpty = (TextView) mHeader.findViewById(android.R.id.empty);
444        mStupidPadding = mHeader.findViewById(R.id.stupid_padding);
445
446        final UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
447        mAdapter = new DataUsageAdapter(um, mUidDetailProvider, mInsetSide);
448        mListView.setOnItemClickListener(mListListener);
449        mListView.setAdapter(mAdapter);
450
451        return view;
452    }
453
454    @Override
455    public void onViewStateRestored(Bundle savedInstanceState) {
456        super.onViewStateRestored(savedInstanceState);
457
458        // pick default tab based on incoming intent
459        final Intent intent = getActivity().getIntent();
460        mIntentTab = computeTabFromIntent(intent);
461
462        // this kicks off chain reaction which creates tabs, binds the body to
463        // selected network, and binds chart, cycles and detail list.
464        updateTabs();
465    }
466
467    @Override
468    public void onResume() {
469        super.onResume();
470
471        getView().post(new Runnable() {
472            @Override
473            public void run() {
474                highlightViewIfNeeded();
475            }
476        });
477
478        // kick off background task to update stats
479        new AsyncTask<Void, Void, Void>() {
480            @Override
481            protected Void doInBackground(Void... params) {
482                try {
483                    // wait a few seconds before kicking off
484                    Thread.sleep(2 * DateUtils.SECOND_IN_MILLIS);
485                    mStatsService.forceUpdate();
486                } catch (InterruptedException e) {
487                } catch (RemoteException e) {
488                }
489                return null;
490            }
491
492            @Override
493            protected void onPostExecute(Void result) {
494                if (isAdded()) {
495                    updateBody();
496                }
497            }
498        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
499    }
500
501    @Override
502    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
503        inflater.inflate(R.menu.data_usage, menu);
504    }
505
506    @Override
507    public void onPrepareOptionsMenu(Menu menu) {
508        final Context context = getActivity();
509        final boolean appDetailMode = isAppDetailMode();
510        final boolean isOwner = ActivityManager.getCurrentUser() == UserHandle.USER_OWNER;
511
512        mMenuShowWifi = menu.findItem(R.id.data_usage_menu_show_wifi);
513        if (hasWifiRadio(context) && hasReadyMobileRadio(context)) {
514            mMenuShowWifi.setVisible(!appDetailMode);
515        } else {
516            mMenuShowWifi.setVisible(false);
517        }
518
519        mMenuShowEthernet = menu.findItem(R.id.data_usage_menu_show_ethernet);
520        if (hasEthernet(context) && hasReadyMobileRadio(context)) {
521            mMenuShowEthernet.setVisible(!appDetailMode);
522        } else {
523            mMenuShowEthernet.setVisible(false);
524        }
525
526        mMenuRestrictBackground = menu.findItem(R.id.data_usage_menu_restrict_background);
527        mMenuRestrictBackground.setVisible(
528                hasReadyMobileRadio(context) && isOwner && !appDetailMode);
529
530        final MenuItem metered = menu.findItem(R.id.data_usage_menu_metered);
531        if (hasReadyMobileRadio(context) || hasWifiRadio(context)) {
532            metered.setVisible(!appDetailMode);
533        } else {
534            metered.setVisible(false);
535        }
536
537        // TODO: show when multiple sims available
538        mMenuSimCards = menu.findItem(R.id.data_usage_menu_sim_cards);
539        mMenuSimCards.setVisible(false);
540
541        mMenuCellularNetworks = menu.findItem(R.id.data_usage_menu_cellular_networks);
542        mMenuCellularNetworks.setVisible(hasReadyMobileRadio(context)
543                && !appDetailMode && isOwner);
544
545        final MenuItem help = menu.findItem(R.id.data_usage_menu_help);
546        String helpUrl;
547        if (!TextUtils.isEmpty(helpUrl = getResources().getString(R.string.help_url_data_usage))) {
548            HelpUtils.prepareHelpMenuItem(context, help, helpUrl);
549        } else {
550            help.setVisible(false);
551        }
552
553        updateMenuTitles();
554    }
555
556    private void updateMenuTitles() {
557        if (mPolicyManager.getRestrictBackground()) {
558            mMenuRestrictBackground.setTitle(R.string.data_usage_menu_allow_background);
559        } else {
560            mMenuRestrictBackground.setTitle(R.string.data_usage_menu_restrict_background);
561        }
562
563        if (mShowWifi) {
564            mMenuShowWifi.setTitle(R.string.data_usage_menu_hide_wifi);
565        } else {
566            mMenuShowWifi.setTitle(R.string.data_usage_menu_show_wifi);
567        }
568
569        if (mShowEthernet) {
570            mMenuShowEthernet.setTitle(R.string.data_usage_menu_hide_ethernet);
571        } else {
572            mMenuShowEthernet.setTitle(R.string.data_usage_menu_show_ethernet);
573        }
574    }
575
576    @Override
577    public boolean onOptionsItemSelected(MenuItem item) {
578        switch (item.getItemId()) {
579            case R.id.data_usage_menu_restrict_background: {
580                final boolean restrictBackground = !mPolicyManager.getRestrictBackground();
581                if (restrictBackground) {
582                    ConfirmRestrictFragment.show(this);
583                } else {
584                    // no confirmation to drop restriction
585                    setRestrictBackground(false);
586                }
587                return true;
588            }
589            case R.id.data_usage_menu_show_wifi: {
590                mShowWifi = !mShowWifi;
591                mPrefs.edit().putBoolean(PREF_SHOW_WIFI, mShowWifi).apply();
592                updateMenuTitles();
593                updateTabs();
594                return true;
595            }
596            case R.id.data_usage_menu_show_ethernet: {
597                mShowEthernet = !mShowEthernet;
598                mPrefs.edit().putBoolean(PREF_SHOW_ETHERNET, mShowEthernet).apply();
599                updateMenuTitles();
600                updateTabs();
601                return true;
602            }
603            case R.id.data_usage_menu_sim_cards: {
604                // TODO: hook up to sim cards
605                return true;
606            }
607            case R.id.data_usage_menu_cellular_networks: {
608                final Intent intent = new Intent(Intent.ACTION_MAIN);
609                intent.setComponent(new ComponentName("com.android.phone",
610                        "com.android.phone.MobileNetworkSettings"));
611                startActivity(intent);
612                return true;
613            }
614            case R.id.data_usage_menu_metered: {
615                final SettingsActivity sa = (SettingsActivity) getActivity();
616                sa.startPreferencePanel(DataUsageMeteredSettings.class.getCanonicalName(), null,
617                        R.string.data_usage_metered_title, null, this, 0);
618                return true;
619            }
620        }
621        return false;
622    }
623
624    @Override
625    public void onDestroy() {
626        mDataEnabledView = null;
627        mDisableAtLimitView = null;
628
629        mUidDetailProvider.clearCache();
630        mUidDetailProvider = null;
631
632        TrafficStats.closeQuietly(mStatsSession);
633
634        super.onDestroy();
635    }
636
637    /**
638     * Build and assign {@link LayoutTransition} to various containers. Should
639     * only be assigned after initial layout is complete.
640     */
641    private void ensureLayoutTransitions() {
642        // skip when already setup
643        if (mChart.getLayoutTransition() != null) return;
644
645        mTabsContainer.setLayoutTransition(buildLayoutTransition());
646        mHeader.setLayoutTransition(buildLayoutTransition());
647        mNetworkSwitchesContainer.setLayoutTransition(buildLayoutTransition());
648
649        final LayoutTransition chartTransition = buildLayoutTransition();
650        chartTransition.disableTransitionType(LayoutTransition.APPEARING);
651        chartTransition.disableTransitionType(LayoutTransition.DISAPPEARING);
652        mChart.setLayoutTransition(chartTransition);
653    }
654
655    private static LayoutTransition buildLayoutTransition() {
656        final LayoutTransition transition = new LayoutTransition();
657        if (TEST_ANIM) {
658            transition.setDuration(1500);
659        }
660        transition.setAnimateParentHierarchy(false);
661        return transition;
662    }
663
664    /**
665     * Rebuild all tabs based on {@link NetworkPolicyEditor} and
666     * {@link #mShowWifi}, hiding the tabs entirely when applicable. Selects
667     * first tab, and kicks off a full rebind of body contents.
668     */
669    private void updateTabs() {
670        final Context context = getActivity();
671        mTabHost.clearAllTabs();
672
673        int simCount = mTelephonyManager.getSimCount();
674
675        for (int i = 0; i < simCount; i++) {
676            final SubscriptionInfo sir = Utils.findRecordBySlotId(context, i);
677            if (sir != null) {
678                addMobileTab(context, sir, (simCount > 1));
679            }
680        }
681
682        if (mShowWifi && hasWifiRadio(context)) {
683            mTabHost.addTab(buildTabSpec(TAB_WIFI, R.string.data_usage_tab_wifi));
684        }
685
686        if (mShowEthernet && hasEthernet(context)) {
687            mTabHost.addTab(buildTabSpec(TAB_ETHERNET, R.string.data_usage_tab_ethernet));
688        }
689
690        final boolean noTabs = mTabWidget.getTabCount() == 0;
691        final boolean multipleTabs = mTabWidget.getTabCount() > 1;
692        mTabWidget.setVisibility(multipleTabs ? View.VISIBLE : View.GONE);
693        if (mIntentTab != null) {
694            if (Objects.equal(mIntentTab, mTabHost.getCurrentTabTag())) {
695                // already hit updateBody() when added; ignore
696                updateBody();
697            } else {
698                mTabHost.setCurrentTabByTag(mIntentTab);
699            }
700            mIntentTab = null;
701        } else if (noTabs) {
702            // no usable tabs, so hide body
703            updateBody();
704        } else {
705            // already hit updateBody() when added; ignore
706        }
707    }
708
709    /**
710     * Factory that provide empty {@link View} to make {@link TabHost} happy.
711     */
712    private TabContentFactory mEmptyTabContent = new TabContentFactory() {
713        @Override
714        public View createTabContent(String tag) {
715            return new View(mTabHost.getContext());
716        }
717    };
718
719    /**
720     * Build {@link TabSpec} with thin indicator, and empty content.
721     */
722    private TabSpec buildTabSpec(String tag, int titleRes) {
723        return mTabHost.newTabSpec(tag).setIndicator(getText(titleRes)).setContent(
724                mEmptyTabContent);
725    }
726
727    /**
728     * Build {@link TabSpec} with thin indicator, and empty content.
729     */
730    private TabSpec buildTabSpec(String tag, CharSequence title) {
731        return mTabHost.newTabSpec(tag).setIndicator(title).setContent(
732                mEmptyTabContent);
733    }
734
735
736    private OnTabChangeListener mTabListener = new OnTabChangeListener() {
737        @Override
738        public void onTabChanged(String tabId) {
739            // user changed tab; update body
740            updateBody();
741        }
742    };
743
744    /**
745     * Update body content based on current tab. Loads
746     * {@link NetworkStatsHistory} and {@link NetworkPolicy} from system, and
747     * binds them to visible controls.
748     */
749    private void updateBody() {
750        mBinding = true;
751        if (!isAdded()) return;
752
753        final Context context = getActivity();
754        final Resources resources = context.getResources();
755        final String currentTab = mTabHost.getCurrentTabTag();
756        final boolean isOwner = ActivityManager.getCurrentUser() == UserHandle.USER_OWNER;
757
758        if (currentTab == null) {
759            Log.w(TAG, "no tab selected; hiding body");
760            mListView.setVisibility(View.GONE);
761            return;
762        } else {
763            mListView.setVisibility(View.VISIBLE);
764        }
765
766        mCurrentTab = currentTab;
767
768        if (LOGD) Log.d(TAG, "updateBody() with currentTab=" + currentTab);
769
770        mDataEnabledSupported = isOwner;
771        mDisableAtLimitSupported = true;
772
773        // TODO: remove mobile tabs when SIM isn't ready probably by
774        // TODO: using SubscriptionManager.getActiveSubscriptionInfoList.
775        if (LOGD) Log.d(TAG, "updateBody() isMobileTab=" + isMobileTab(currentTab));
776
777        if (isMobileTab(currentTab)) {
778            if (LOGD) Log.d(TAG, "updateBody() mobile tab");
779            setPreferenceTitle(mDataEnabledView, R.string.data_usage_enable_mobile);
780            setPreferenceTitle(mDisableAtLimitView, R.string.data_usage_disable_mobile_limit);
781            mDataEnabledSupported = isMobileDataAvailable(getSubId(currentTab));
782
783            // Match mobile traffic for this subscriber, but normalize it to
784            // catch any other merged subscribers.
785            mTemplate = buildTemplateMobileAll(
786                    getActiveSubscriberId(context, getSubId(currentTab)));
787            mTemplate = NetworkTemplate.normalize(mTemplate,
788                    mTelephonyManager.getMergedSubscriberIds());
789
790        } else if (TAB_3G.equals(currentTab)) {
791            if (LOGD) Log.d(TAG, "updateBody() 3g tab");
792            setPreferenceTitle(mDataEnabledView, R.string.data_usage_enable_3g);
793            setPreferenceTitle(mDisableAtLimitView, R.string.data_usage_disable_3g_limit);
794            // TODO: bind mDataEnabled to 3G radio state
795            mTemplate = buildTemplateMobile3gLower(getActiveSubscriberId(context));
796
797        } else if (TAB_4G.equals(currentTab)) {
798            if (LOGD) Log.d(TAG, "updateBody() 4g tab");
799            setPreferenceTitle(mDataEnabledView, R.string.data_usage_enable_4g);
800            setPreferenceTitle(mDisableAtLimitView, R.string.data_usage_disable_4g_limit);
801            // TODO: bind mDataEnabled to 4G radio state
802            mTemplate = buildTemplateMobile4g(getActiveSubscriberId(context));
803
804        } else if (TAB_WIFI.equals(currentTab)) {
805            // wifi doesn't have any controls
806            if (LOGD) Log.d(TAG, "updateBody() wifi tab");
807            mDataEnabledSupported = false;
808            mDisableAtLimitSupported = false;
809            mTemplate = buildTemplateWifiWildcard();
810
811        } else if (TAB_ETHERNET.equals(currentTab)) {
812            // ethernet doesn't have any controls
813            if (LOGD) Log.d(TAG, "updateBody() ethernet tab");
814            mDataEnabledSupported = false;
815            mDisableAtLimitSupported = false;
816            mTemplate = buildTemplateEthernet();
817
818        } else {
819            if (LOGD) Log.d(TAG, "updateBody() unknown tab");
820            throw new IllegalStateException("unknown tab: " + currentTab);
821        }
822
823        // kick off loader for network history
824        // TODO: consider chaining two loaders together instead of reloading
825        // network history when showing app detail.
826        getLoaderManager().restartLoader(LOADER_CHART_DATA,
827                ChartDataLoader.buildArgs(mTemplate, mCurrentApp), mChartDataCallbacks);
828
829        // detail mode can change visible menus, invalidate
830        getActivity().invalidateOptionsMenu();
831
832        mBinding = false;
833
834        int seriesColor = resources.getColor(R.color.sim_noitification);
835        if (mCurrentTab != null && mCurrentTab.length() > TAB_MOBILE.length() ){
836            final int slotId = Integer.parseInt(mCurrentTab.substring(TAB_MOBILE.length(),
837                    mCurrentTab.length()));
838            final SubscriptionInfo sir = com.android.settings.Utils.findRecordBySlotId(context,
839                    slotId);
840
841            if (sir != null) {
842                seriesColor = sir.getIconTint();
843            }
844        }
845
846        final int secondaryColor = Color.argb(127, Color.red(seriesColor), Color.green(seriesColor),
847                Color.blue(seriesColor));
848        mSeries.setChartColor(Color.BLACK, seriesColor, secondaryColor);
849        mDetailedSeries.setChartColor(Color.BLACK, seriesColor, secondaryColor);
850    }
851
852    private boolean isAppDetailMode() {
853        return mCurrentApp != null;
854    }
855
856    /**
857     * Update UID details panels to match {@link #mCurrentApp}, showing or
858     * hiding them depending on {@link #isAppDetailMode()}.
859     */
860    private void updateAppDetail() {
861        final Context context = getActivity();
862        final PackageManager pm = context.getPackageManager();
863        final LayoutInflater inflater = getActivity().getLayoutInflater();
864
865        if (isAppDetailMode()) {
866            mAppDetail.setVisibility(View.VISIBLE);
867            mCycleAdapter.setChangeVisible(false);
868        } else {
869            mAppDetail.setVisibility(View.GONE);
870            mCycleAdapter.setChangeVisible(true);
871
872            // hide detail stats when not in detail mode
873            mChart.bindDetailNetworkStats(null);
874            return;
875        }
876
877        // remove warning/limit sweeps while in detail mode
878        mChart.bindNetworkPolicy(null);
879
880        // show icon and all labels appearing under this app
881        final int uid = mCurrentApp.key;
882        final UidDetail detail = mUidDetailProvider.getUidDetail(uid, true);
883        mAppIcon.setImageDrawable(detail.icon);
884
885        mAppTitles.removeAllViews();
886
887        View title = null;
888        if (detail.detailLabels != null) {
889            final int n = detail.detailLabels.length;
890            for (int i = 0; i < n; ++i) {
891                CharSequence label = detail.detailLabels[i];
892                CharSequence contentDescription = detail.detailContentDescriptions[i];
893                title = inflater.inflate(R.layout.data_usage_app_title, mAppTitles, false);
894                TextView appTitle = (TextView) title.findViewById(R.id.app_title);
895                appTitle.setText(label);
896                appTitle.setContentDescription(contentDescription);
897                mAppTitles.addView(title);
898            }
899        } else {
900            title = inflater.inflate(R.layout.data_usage_app_title, mAppTitles, false);
901            TextView appTitle = (TextView) title.findViewById(R.id.app_title);
902            appTitle.setText(detail.label);
903            appTitle.setContentDescription(detail.contentDescription);
904            mAppTitles.addView(title);
905        }
906
907        // Remember last slot for summary
908        if (title != null) {
909            mAppTotal = (TextView) title.findViewById(R.id.app_summary);
910        } else {
911            mAppTotal = null;
912        }
913
914        // enable settings button when package provides it
915        final String[] packageNames = pm.getPackagesForUid(uid);
916        if (packageNames != null && packageNames.length > 0) {
917            mAppSettingsIntent = new Intent(Intent.ACTION_MANAGE_NETWORK_USAGE);
918            mAppSettingsIntent.addCategory(Intent.CATEGORY_DEFAULT);
919
920            // Search for match across all packages
921            boolean matchFound = false;
922            for (String packageName : packageNames) {
923                mAppSettingsIntent.setPackage(packageName);
924                if (pm.resolveActivity(mAppSettingsIntent, 0) != null) {
925                    matchFound = true;
926                    break;
927                }
928            }
929
930            mAppSettings.setOnClickListener(new OnClickListener() {
931                @Override
932                public void onClick(View v) {
933                    if (!isAdded()) {
934                        return;
935                    }
936
937                    // TODO: target towards entire UID instead of just first package
938                    getActivity().startActivityAsUser(mAppSettingsIntent,
939                            new UserHandle(UserHandle.getUserId(uid)));
940                }
941            });
942            mAppSettings.setEnabled(matchFound);
943            mAppSettings.setVisibility(View.VISIBLE);
944
945        } else {
946            mAppSettingsIntent = null;
947            mAppSettings.setOnClickListener(null);
948            mAppSettings.setVisibility(View.GONE);
949        }
950
951        updateDetailData();
952
953        if (UserHandle.isApp(uid) && !mPolicyManager.getRestrictBackground()
954                && isBandwidthControlEnabled() && hasReadyMobileRadio(context)) {
955            setPreferenceTitle(mAppRestrictView, R.string.data_usage_app_restrict_background);
956            setPreferenceSummary(mAppRestrictView,
957                    getString(R.string.data_usage_app_restrict_background_summary));
958
959            mAppRestrictView.setVisibility(View.VISIBLE);
960            mAppRestrict.setChecked(getAppRestrictBackground());
961
962        } else {
963            mAppRestrictView.setVisibility(View.GONE);
964        }
965    }
966
967    private void setPolicyWarningBytes(long warningBytes) {
968        if (LOGD) Log.d(TAG, "setPolicyWarningBytes()");
969        mPolicyEditor.setPolicyWarningBytes(mTemplate, warningBytes);
970        updatePolicy(false);
971    }
972
973    private void setPolicyLimitBytes(long limitBytes) {
974        if (LOGD) Log.d(TAG, "setPolicyLimitBytes()");
975        mPolicyEditor.setPolicyLimitBytes(mTemplate, limitBytes);
976        updatePolicy(false);
977    }
978
979    private boolean isMobileDataEnabled(int subId) {
980        if (LOGD) Log.d(TAG, "isMobileDataEnabled:+ subId=" + subId);
981        boolean isEnable = false;
982        if (mMobileDataEnabled.get(String.valueOf(subId)) != null) {
983            //TODO: deprecate and remove this once enabled flag is on policy
984            //Multiple Subscriptions, the value need to be reseted
985            isEnable = mMobileDataEnabled.get(String.valueOf(subId)).booleanValue();
986            if (LOGD) {
987                Log.d(TAG, "isMobileDataEnabled: != null, subId=" + subId
988                        + " isEnable=" + isEnable);
989            }
990            mMobileDataEnabled.put(String.valueOf(subId), null);
991        } else {
992            // SUB SELECT
993            isEnable = mTelephonyManager.getDataEnabled(subId);
994            if (LOGD) {
995                Log.d(TAG, "isMobileDataEnabled: == null, subId=" + subId
996                        + " isEnable=" + isEnable);
997            }
998        }
999        return isEnable;
1000    }
1001
1002    private void setMobileDataEnabled(int subId, boolean enabled) {
1003        if (LOGD) Log.d(TAG, "setMobileDataEnabled()");
1004        mTelephonyManager.setDataEnabled(subId, enabled);
1005        mMobileDataEnabled.put(String.valueOf(subId), enabled);
1006        updatePolicy(false);
1007    }
1008
1009    private boolean isNetworkPolicyModifiable(NetworkPolicy policy) {
1010        return policy != null && isBandwidthControlEnabled() && mDataEnabled.isChecked()
1011                && ActivityManager.getCurrentUser() == UserHandle.USER_OWNER;
1012    }
1013
1014    private boolean isBandwidthControlEnabled() {
1015        try {
1016            return mNetworkService.isBandwidthControlEnabled();
1017        } catch (RemoteException e) {
1018            Log.w(TAG, "problem talking with INetworkManagementService: " + e);
1019            return false;
1020        }
1021    }
1022
1023    public void setRestrictBackground(boolean restrictBackground) {
1024        mPolicyManager.setRestrictBackground(restrictBackground);
1025        updateMenuTitles();
1026    }
1027
1028    private boolean getAppRestrictBackground() {
1029        final int uid = mCurrentApp.key;
1030        final int uidPolicy = mPolicyManager.getUidPolicy(uid);
1031        return (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0;
1032    }
1033
1034    private void setAppRestrictBackground(boolean restrictBackground) {
1035        if (LOGD) Log.d(TAG, "setAppRestrictBackground()");
1036        final int uid = mCurrentApp.key;
1037        mPolicyManager.setUidPolicy(
1038                uid, restrictBackground ? POLICY_REJECT_METERED_BACKGROUND : POLICY_NONE);
1039        mAppRestrict.setChecked(restrictBackground);
1040    }
1041
1042    /**
1043     * Update chart sweeps and cycle list to reflect {@link NetworkPolicy} for
1044     * current {@link #mTemplate}.
1045     */
1046    private void updatePolicy(boolean refreshCycle) {
1047        boolean dataEnabledVisible = mDataEnabledSupported;
1048        boolean disableAtLimitVisible = mDisableAtLimitSupported;
1049
1050        if (isAppDetailMode()) {
1051            dataEnabledVisible = false;
1052            disableAtLimitVisible = false;
1053        }
1054
1055        // TODO: move enabled state directly into policy
1056        if (isMobileTab(mCurrentTab)) {
1057            mBinding = true;
1058            mDataEnabled.setChecked(isMobileDataEnabled(getSubId(mCurrentTab)));
1059            mBinding = false;
1060        }
1061
1062        final NetworkPolicy policy = mPolicyEditor.getPolicy(mTemplate);
1063        //SUB SELECT
1064        if (isNetworkPolicyModifiable(policy) && isMobileDataAvailable(getSubId(mCurrentTab))) {
1065            mDisableAtLimit.setChecked(policy != null && policy.limitBytes != LIMIT_DISABLED);
1066            if (!isAppDetailMode()) {
1067                mChart.bindNetworkPolicy(policy);
1068            }
1069
1070        } else {
1071            // controls are disabled; don't bind warning/limit sweeps
1072            disableAtLimitVisible = false;
1073            mChart.bindNetworkPolicy(null);
1074        }
1075
1076        mDataEnabledView.setVisibility(dataEnabledVisible ? View.VISIBLE : View.GONE);
1077        mDisableAtLimitView.setVisibility(disableAtLimitVisible ? View.VISIBLE : View.GONE);
1078
1079        if (refreshCycle) {
1080            // generate cycle list based on policy and available history
1081            updateCycleList(policy);
1082        }
1083    }
1084
1085    /**
1086     * Rebuild {@link #mCycleAdapter} based on {@link NetworkPolicy#cycleDay}
1087     * and available {@link NetworkStatsHistory} data. Always selects the newest
1088     * item, updating the inspection range on {@link #mChart}.
1089     */
1090    private void updateCycleList(NetworkPolicy policy) {
1091        // stash away currently selected cycle to try restoring below
1092        final CycleItem previousItem = (CycleItem) mCycleSpinner.getSelectedItem();
1093        mCycleAdapter.clear();
1094
1095        final Context context = mCycleSpinner.getContext();
1096
1097        long historyStart = Long.MAX_VALUE;
1098        long historyEnd = Long.MIN_VALUE;
1099        if (mChartData != null) {
1100            historyStart = mChartData.network.getStart();
1101            historyEnd = mChartData.network.getEnd();
1102        }
1103
1104        final long now = System.currentTimeMillis();
1105        if (historyStart == Long.MAX_VALUE) historyStart = now;
1106        if (historyEnd == Long.MIN_VALUE) historyEnd = now + 1;
1107
1108        boolean hasCycles = false;
1109        if (policy != null) {
1110            // find the next cycle boundary
1111            long cycleEnd = computeNextCycleBoundary(historyEnd, policy);
1112
1113            // walk backwards, generating all valid cycle ranges
1114            while (cycleEnd > historyStart) {
1115                final long cycleStart = computeLastCycleBoundary(cycleEnd, policy);
1116                Log.d(TAG, "generating cs=" + cycleStart + " to ce=" + cycleEnd + " waiting for hs="
1117                        + historyStart);
1118                mCycleAdapter.add(new CycleItem(context, cycleStart, cycleEnd));
1119                cycleEnd = cycleStart;
1120                hasCycles = true;
1121            }
1122
1123            // one last cycle entry to modify policy cycle day
1124            mCycleAdapter.setChangePossible(isNetworkPolicyModifiable(policy));
1125        }
1126
1127        if (!hasCycles) {
1128            // no policy defined cycles; show entry for each four-week period
1129            long cycleEnd = historyEnd;
1130            while (cycleEnd > historyStart) {
1131                final long cycleStart = cycleEnd - (DateUtils.WEEK_IN_MILLIS * 4);
1132                mCycleAdapter.add(new CycleItem(context, cycleStart, cycleEnd));
1133                cycleEnd = cycleStart;
1134            }
1135
1136            mCycleAdapter.setChangePossible(false);
1137        }
1138
1139        // force pick the current cycle (first item)
1140        if (mCycleAdapter.getCount() > 0) {
1141            final int position = mCycleAdapter.findNearestPosition(previousItem);
1142            mCycleSpinner.setSelection(position);
1143
1144            // only force-update cycle when changed; skipping preserves any
1145            // user-defined inspection region.
1146            final CycleItem selectedItem = mCycleAdapter.getItem(position);
1147            if (!Objects.equal(selectedItem, previousItem)) {
1148                mCycleListener.onItemSelected(mCycleSpinner, null, position, 0);
1149            } else {
1150                // but still kick off loader for detailed list
1151                updateDetailData();
1152            }
1153        } else {
1154            updateDetailData();
1155        }
1156    }
1157
1158    private void disableDataForOtherSubscriptions(SubscriptionInfo currentSir) {
1159        if (mSubInfoList != null) {
1160            for (SubscriptionInfo subInfo : mSubInfoList) {
1161                if (subInfo.getSubscriptionId() != currentSir.getSubscriptionId()) {
1162                    setMobileDataEnabled(subInfo.getSubscriptionId(), false);
1163                }
1164            }
1165        }
1166    }
1167
1168    private View.OnClickListener mDataEnabledListener = new View.OnClickListener() {
1169        @Override
1170        public void onClick(View v) {
1171            if (mBinding) return;
1172
1173            final boolean dataEnabled = !mDataEnabled.isChecked();
1174            final String currentTab = mCurrentTab;
1175            if (isMobileTab(currentTab)) {
1176                if (dataEnabled) {
1177                    // If we are showing the Sim Card tile then we are a Multi-Sim device.
1178                    if (Utils.showSimCardTile(getActivity())) {
1179                        handleMultiSimDataDialog();
1180                    } else {
1181                        setMobileDataEnabled(getSubId(currentTab), true);
1182                    }
1183                } else {
1184                    // disabling data; show confirmation dialog which eventually
1185                    // calls setMobileDataEnabled() once user confirms.
1186                    ConfirmDataDisableFragment.show(DataUsageSummary.this, getSubId(mCurrentTab));
1187                }
1188            }
1189
1190            updatePolicy(false);
1191        }
1192    };
1193
1194    private void handleMultiSimDataDialog() {
1195        final Context context = getActivity();
1196        final SubscriptionInfo currentSir = getCurrentTabSubInfo(context);
1197
1198        //If sim has not loaded after toggling data switch, return.
1199        if (currentSir == null) {
1200            return;
1201        }
1202
1203        final SubscriptionInfo nextSir = mSubscriptionManager.getActiveSubscriptionInfo(
1204                mSubscriptionManager.getDefaultDataSubId());
1205
1206        // If the device is single SIM or is enabling data on the active data SIM then forgo
1207        // the pop-up.
1208        if (!Utils.showSimCardTile(context) ||
1209                (nextSir != null && currentSir != null &&
1210                currentSir.getSubscriptionId() == nextSir.getSubscriptionId())) {
1211            setMobileDataEnabled(currentSir.getSubscriptionId(), true);
1212            if (nextSir != null && currentSir != null &&
1213                currentSir.getSubscriptionId() == nextSir.getSubscriptionId()) {
1214                disableDataForOtherSubscriptions(currentSir);
1215            }
1216            updateBody();
1217            return;
1218        }
1219
1220        final String previousName = (nextSir == null)
1221            ? context.getResources().getString(R.string.sim_selection_required_pref)
1222            : nextSir.getDisplayName().toString();
1223
1224        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
1225
1226        builder.setTitle(R.string.sim_change_data_title);
1227        builder.setMessage(getActivity().getResources().getString(R.string.sim_change_data_message,
1228                    currentSir.getDisplayName(), previousName));
1229
1230        builder.setPositiveButton(R.string.okay, new DialogInterface.OnClickListener() {
1231            @Override
1232            public void onClick(DialogInterface dialog, int id) {
1233                mSubscriptionManager.setDefaultDataSubId(currentSir.getSubscriptionId());
1234                setMobileDataEnabled(currentSir.getSubscriptionId(), true);
1235                disableDataForOtherSubscriptions(currentSir);
1236                updateBody();
1237            }
1238        });
1239        builder.setNegativeButton(R.string.cancel, null);
1240
1241        builder.create().show();
1242    }
1243
1244    private View.OnClickListener mDisableAtLimitListener = new View.OnClickListener() {
1245        @Override
1246        public void onClick(View v) {
1247            final boolean disableAtLimit = !mDisableAtLimit.isChecked();
1248            if (disableAtLimit) {
1249                // enabling limit; show confirmation dialog which eventually
1250                // calls setPolicyLimitBytes() once user confirms.
1251                ConfirmLimitFragment.show(DataUsageSummary.this);
1252            } else {
1253                setPolicyLimitBytes(LIMIT_DISABLED);
1254            }
1255        }
1256    };
1257
1258    private View.OnClickListener mAppRestrictListener = new View.OnClickListener() {
1259        @Override
1260        public void onClick(View v) {
1261            final boolean restrictBackground = !mAppRestrict.isChecked();
1262
1263            if (restrictBackground) {
1264                // enabling restriction; show confirmation dialog which
1265                // eventually calls setRestrictBackground() once user
1266                // confirms.
1267                ConfirmAppRestrictFragment.show(DataUsageSummary.this);
1268            } else {
1269                setAppRestrictBackground(false);
1270            }
1271        }
1272    };
1273
1274    private OnItemClickListener mListListener = new OnItemClickListener() {
1275        @Override
1276        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
1277            final Context context = view.getContext();
1278            final AppItem app = (AppItem) parent.getItemAtPosition(position);
1279
1280            // TODO: sigh, remove this hack once we understand 6450986
1281            if (mUidDetailProvider == null || app == null) return;
1282
1283            final UidDetail detail = mUidDetailProvider.getUidDetail(app.key, true);
1284            AppDetailsFragment.show(DataUsageSummary.this, app, detail.label);
1285        }
1286    };
1287
1288    private OnItemSelectedListener mCycleListener = new OnItemSelectedListener() {
1289        @Override
1290        public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
1291            final CycleItem cycle = (CycleItem) parent.getItemAtPosition(position);
1292            if (cycle instanceof CycleChangeItem) {
1293                // show cycle editor; will eventually call setPolicyCycleDay()
1294                // when user finishes editing.
1295                CycleEditorFragment.show(DataUsageSummary.this);
1296
1297                // reset spinner to something other than "change cycle..."
1298                mCycleSpinner.setSelection(0);
1299
1300            } else {
1301                if (LOGD) {
1302                    Log.d(TAG, "showing cycle " + cycle + ", start=" + cycle.start + ", end="
1303                            + cycle.end + "]");
1304                }
1305
1306                // update chart to show selected cycle, and update detail data
1307                // to match updated sweep bounds.
1308                mChart.setVisibleRange(cycle.start, cycle.end);
1309
1310                updateDetailData();
1311            }
1312        }
1313
1314        @Override
1315        public void onNothingSelected(AdapterView<?> parent) {
1316            // ignored
1317        }
1318    };
1319
1320    /**
1321     * Update details based on {@link #mChart} inspection range depending on
1322     * current mode. In network mode, updates {@link #mAdapter} with sorted list
1323     * of applications data usage, and when {@link #isAppDetailMode()} update
1324     * app details.
1325     */
1326    private void updateDetailData() {
1327        if (LOGD) Log.d(TAG, "updateDetailData()");
1328
1329        final long start = mChart.getInspectStart();
1330        final long end = mChart.getInspectEnd();
1331        final long now = System.currentTimeMillis();
1332
1333        final Context context = getActivity();
1334
1335        NetworkStatsHistory.Entry entry = null;
1336        if (isAppDetailMode() && mChartData != null && mChartData.detail != null) {
1337            // bind foreground/background to piechart and labels
1338            entry = mChartData.detailDefault.getValues(start, end, now, entry);
1339            final long defaultBytes = entry.rxBytes + entry.txBytes;
1340            entry = mChartData.detailForeground.getValues(start, end, now, entry);
1341            final long foregroundBytes = entry.rxBytes + entry.txBytes;
1342            final long totalBytes = defaultBytes + foregroundBytes;
1343
1344            if (mAppTotal != null) {
1345                mAppTotal.setText(Formatter.formatFileSize(context, totalBytes));
1346            }
1347            mAppBackground.setText(Formatter.formatFileSize(context, defaultBytes));
1348            mAppForeground.setText(Formatter.formatFileSize(context, foregroundBytes));
1349
1350            // and finally leave with summary data for label below
1351            entry = mChartData.detail.getValues(start, end, now, null);
1352
1353            getLoaderManager().destroyLoader(LOADER_SUMMARY);
1354
1355            mCycleSummary.setVisibility(View.GONE);
1356
1357        } else {
1358            if (mChartData != null) {
1359                entry = mChartData.network.getValues(start, end, now, null);
1360            }
1361
1362            mCycleSummary.setVisibility(View.VISIBLE);
1363
1364            // kick off loader for detailed stats
1365            getLoaderManager().restartLoader(LOADER_SUMMARY,
1366                    SummaryForAllUidLoader.buildArgs(mTemplate, start, end), mSummaryCallbacks);
1367        }
1368
1369        final long totalBytes = entry != null ? entry.rxBytes + entry.txBytes : 0;
1370        final String totalPhrase = Formatter.formatFileSize(context, totalBytes);
1371        mCycleSummary.setText(totalPhrase);
1372
1373        if (isMobileTab(mCurrentTab) || TAB_3G.equals(mCurrentTab)
1374                || TAB_4G.equals(mCurrentTab)) {
1375            if (isAppDetailMode()) {
1376                mDisclaimer.setVisibility(View.GONE);
1377            } else {
1378                mDisclaimer.setVisibility(View.VISIBLE);
1379            }
1380        } else {
1381            mDisclaimer.setVisibility(View.GONE);
1382        }
1383
1384        // initial layout is finished above, ensure we have transitions
1385        ensureLayoutTransitions();
1386    }
1387
1388    private final LoaderCallbacks<ChartData> mChartDataCallbacks = new LoaderCallbacks<
1389            ChartData>() {
1390        @Override
1391        public Loader<ChartData> onCreateLoader(int id, Bundle args) {
1392            return new ChartDataLoader(getActivity(), mStatsSession, args);
1393        }
1394
1395        @Override
1396        public void onLoadFinished(Loader<ChartData> loader, ChartData data) {
1397            mChartData = data;
1398            mChart.bindNetworkStats(mChartData.network);
1399            mChart.bindDetailNetworkStats(mChartData.detail);
1400
1401            // calcuate policy cycles based on available data
1402            updatePolicy(true);
1403            updateAppDetail();
1404
1405            // force scroll to top of body when showing detail
1406            if (mChartData.detail != null) {
1407                mListView.smoothScrollToPosition(0);
1408            }
1409        }
1410
1411        @Override
1412        public void onLoaderReset(Loader<ChartData> loader) {
1413            mChartData = null;
1414            mChart.bindNetworkStats(null);
1415            mChart.bindDetailNetworkStats(null);
1416        }
1417    };
1418
1419    private final LoaderCallbacks<NetworkStats> mSummaryCallbacks = new LoaderCallbacks<
1420            NetworkStats>() {
1421        @Override
1422        public Loader<NetworkStats> onCreateLoader(int id, Bundle args) {
1423            return new SummaryForAllUidLoader(getActivity(), mStatsSession, args);
1424        }
1425
1426        @Override
1427        public void onLoadFinished(Loader<NetworkStats> loader, NetworkStats data) {
1428            final int[] restrictedUids = mPolicyManager.getUidsWithPolicy(
1429                    POLICY_REJECT_METERED_BACKGROUND);
1430            mAdapter.bindStats(data, restrictedUids);
1431            updateEmptyVisible();
1432        }
1433
1434        @Override
1435        public void onLoaderReset(Loader<NetworkStats> loader) {
1436            mAdapter.bindStats(null, new int[0]);
1437            updateEmptyVisible();
1438        }
1439
1440        private void updateEmptyVisible() {
1441            final boolean isEmpty = mAdapter.isEmpty() && !isAppDetailMode();
1442            mEmpty.setVisibility(isEmpty ? View.VISIBLE : View.GONE);
1443            mStupidPadding.setVisibility(isEmpty ? View.VISIBLE : View.GONE);
1444        }
1445    };
1446
1447    private static String getActiveSubscriberId(Context context) {
1448        final TelephonyManager tele = TelephonyManager.from(context);
1449        final String actualSubscriberId = tele.getSubscriberId();
1450        String retVal = SystemProperties.get(TEST_SUBSCRIBER_PROP, actualSubscriberId);
1451        if (LOGD) Log.d(TAG, "getActiveSubscriberId=" + retVal + " actualSubscriberId=" + actualSubscriberId);
1452        return retVal;
1453    }
1454
1455    private static String getActiveSubscriberId(Context context, int subId) {
1456        final TelephonyManager tele = TelephonyManager.from(context);
1457        String retVal = tele.getSubscriberId(subId);
1458        if (LOGD) Log.d(TAG, "getActiveSubscriberId=" + retVal + " subId=" + subId);
1459        return retVal;
1460    }
1461
1462    private DataUsageChartListener mChartListener = new DataUsageChartListener() {
1463        @Override
1464        public void onWarningChanged() {
1465            setPolicyWarningBytes(mChart.getWarningBytes());
1466        }
1467
1468        @Override
1469        public void onLimitChanged() {
1470            setPolicyLimitBytes(mChart.getLimitBytes());
1471        }
1472
1473        @Override
1474        public void requestWarningEdit() {
1475            WarningEditorFragment.show(DataUsageSummary.this);
1476        }
1477
1478        @Override
1479        public void requestLimitEdit() {
1480            LimitEditorFragment.show(DataUsageSummary.this);
1481        }
1482    };
1483
1484    /**
1485     * List item that reflects a specific data usage cycle.
1486     */
1487    public static class CycleItem implements Comparable<CycleItem> {
1488        public CharSequence label;
1489        public long start;
1490        public long end;
1491
1492        CycleItem(CharSequence label) {
1493            this.label = label;
1494        }
1495
1496        public CycleItem(Context context, long start, long end) {
1497            this.label = formatDateRange(context, start, end);
1498            this.start = start;
1499            this.end = end;
1500        }
1501
1502        @Override
1503        public String toString() {
1504            return label.toString();
1505        }
1506
1507        @Override
1508        public boolean equals(Object o) {
1509            if (o instanceof CycleItem) {
1510                final CycleItem another = (CycleItem) o;
1511                return start == another.start && end == another.end;
1512            }
1513            return false;
1514        }
1515
1516        @Override
1517        public int compareTo(CycleItem another) {
1518            return Long.compare(start, another.start);
1519        }
1520    }
1521
1522    private static final StringBuilder sBuilder = new StringBuilder(50);
1523    private static final java.util.Formatter sFormatter = new java.util.Formatter(
1524            sBuilder, Locale.getDefault());
1525
1526    public static String formatDateRange(Context context, long start, long end) {
1527        final int flags = FORMAT_SHOW_DATE | FORMAT_ABBREV_MONTH;
1528
1529        synchronized (sBuilder) {
1530            sBuilder.setLength(0);
1531            return DateUtils.formatDateRange(context, sFormatter, start, end, flags, null)
1532                    .toString();
1533        }
1534    }
1535
1536    /**
1537     * Special-case data usage cycle that triggers dialog to change
1538     * {@link NetworkPolicy#cycleDay}.
1539     */
1540    public static class CycleChangeItem extends CycleItem {
1541        public CycleChangeItem(Context context) {
1542            super(context.getString(R.string.data_usage_change_cycle));
1543        }
1544    }
1545
1546    public static class CycleAdapter extends ArrayAdapter<CycleItem> {
1547        private boolean mChangePossible = false;
1548        private boolean mChangeVisible = false;
1549
1550        private final CycleChangeItem mChangeItem;
1551
1552        public CycleAdapter(Context context) {
1553            super(context, R.layout.data_usage_cycle_item);
1554            setDropDownViewResource(R.layout.data_usage_cycle_item_dropdown);
1555            mChangeItem = new CycleChangeItem(context);
1556        }
1557
1558        public void setChangePossible(boolean possible) {
1559            mChangePossible = possible;
1560            updateChange();
1561        }
1562
1563        public void setChangeVisible(boolean visible) {
1564            mChangeVisible = visible;
1565            updateChange();
1566        }
1567
1568        private void updateChange() {
1569            remove(mChangeItem);
1570            if (mChangePossible && mChangeVisible) {
1571                add(mChangeItem);
1572            }
1573        }
1574
1575        /**
1576         * Find position of {@link CycleItem} in this adapter which is nearest
1577         * the given {@link CycleItem}.
1578         */
1579        public int findNearestPosition(CycleItem target) {
1580            if (target != null) {
1581                final int count = getCount();
1582                for (int i = count - 1; i >= 0; i--) {
1583                    final CycleItem item = getItem(i);
1584                    if (item instanceof CycleChangeItem) {
1585                        continue;
1586                    } else if (item.compareTo(target) >= 0) {
1587                        return i;
1588                    }
1589                }
1590            }
1591            return 0;
1592        }
1593    }
1594
1595    public static class AppItem implements Comparable<AppItem>, Parcelable {
1596        public static final int CATEGORY_USER = 0;
1597        public static final int CATEGORY_APP_TITLE = 1;
1598        public static final int CATEGORY_APP = 2;
1599
1600        public final int key;
1601        public boolean restricted;
1602        public int category;
1603
1604        public SparseBooleanArray uids = new SparseBooleanArray();
1605        public long total;
1606
1607        public AppItem() {
1608            this.key = 0;
1609        }
1610
1611        public AppItem(int key) {
1612            this.key = key;
1613        }
1614
1615        public AppItem(Parcel parcel) {
1616            key = parcel.readInt();
1617            uids = parcel.readSparseBooleanArray();
1618            total = parcel.readLong();
1619        }
1620
1621        public void addUid(int uid) {
1622            uids.put(uid, true);
1623        }
1624
1625        @Override
1626        public void writeToParcel(Parcel dest, int flags) {
1627            dest.writeInt(key);
1628            dest.writeSparseBooleanArray(uids);
1629            dest.writeLong(total);
1630        }
1631
1632        @Override
1633        public int describeContents() {
1634            return 0;
1635        }
1636
1637        @Override
1638        public int compareTo(AppItem another) {
1639            int comparison = Integer.compare(category, another.category);
1640            if (comparison == 0) {
1641                comparison = Long.compare(another.total, total);
1642            }
1643            return comparison;
1644        }
1645
1646        public static final Creator<AppItem> CREATOR = new Creator<AppItem>() {
1647            @Override
1648            public AppItem createFromParcel(Parcel in) {
1649                return new AppItem(in);
1650            }
1651
1652            @Override
1653            public AppItem[] newArray(int size) {
1654                return new AppItem[size];
1655            }
1656        };
1657    }
1658
1659    /**
1660     * Adapter of applications, sorted by total usage descending.
1661     */
1662    public static class DataUsageAdapter extends BaseAdapter {
1663        private final UidDetailProvider mProvider;
1664        private final int mInsetSide;
1665        private final UserManager mUm;
1666
1667        private ArrayList<AppItem> mItems = Lists.newArrayList();
1668        private long mLargest;
1669
1670        public DataUsageAdapter(final UserManager userManager, UidDetailProvider provider, int insetSide) {
1671            mProvider = checkNotNull(provider);
1672            mInsetSide = insetSide;
1673            mUm = userManager;
1674        }
1675
1676        /**
1677         * Bind the given {@link NetworkStats}, or {@code null} to clear list.
1678         */
1679        public void bindStats(NetworkStats stats, int[] restrictedUids) {
1680            mItems.clear();
1681            mLargest = 0;
1682
1683            final int currentUserId = ActivityManager.getCurrentUser();
1684            final List<UserHandle> profiles = mUm.getUserProfiles();
1685            final SparseArray<AppItem> knownItems = new SparseArray<AppItem>();
1686
1687            NetworkStats.Entry entry = null;
1688            final int size = stats != null ? stats.size() : 0;
1689            for (int i = 0; i < size; i++) {
1690                entry = stats.getValues(i, entry);
1691
1692                // Decide how to collapse items together
1693                final int uid = entry.uid;
1694
1695                final int collapseKey;
1696                final int category;
1697                final int userId = UserHandle.getUserId(uid);
1698                if (UserHandle.isApp(uid)) {
1699                    if (profiles.contains(new UserHandle(userId))) {
1700                        if (userId != currentUserId) {
1701                            // Add to a managed user item.
1702                            final int managedKey = UidDetailProvider.buildKeyForUser(userId);
1703                            accumulate(managedKey, knownItems, entry,
1704                                    AppItem.CATEGORY_USER);
1705                        }
1706                        // Add to app item.
1707                        collapseKey = uid;
1708                        category = AppItem.CATEGORY_APP;
1709                    } else {
1710                        // If it is a removed user add it to the removed users' key
1711                        final UserInfo info = mUm.getUserInfo(userId);
1712                        if (info == null) {
1713                            collapseKey = UID_REMOVED;
1714                            category = AppItem.CATEGORY_APP;
1715                        } else {
1716                            // Add to other user item.
1717                            collapseKey = UidDetailProvider.buildKeyForUser(userId);
1718                            category = AppItem.CATEGORY_USER;
1719                        }
1720                    }
1721                } else if (uid == UID_REMOVED || uid == UID_TETHERING) {
1722                    collapseKey = uid;
1723                    category = AppItem.CATEGORY_APP;
1724                } else {
1725                    collapseKey = android.os.Process.SYSTEM_UID;
1726                    category = AppItem.CATEGORY_APP;
1727                }
1728                accumulate(collapseKey, knownItems, entry, category);
1729            }
1730
1731            final int restrictedUidsMax = restrictedUids.length;
1732            for (int i = 0; i < restrictedUidsMax; ++i) {
1733                final int uid = restrictedUids[i];
1734                // Only splice in restricted state for current user or managed users
1735                if (!profiles.contains(new UserHandle(UserHandle.getUserId(uid)))) {
1736                    continue;
1737                }
1738
1739                AppItem item = knownItems.get(uid);
1740                if (item == null) {
1741                    item = new AppItem(uid);
1742                    item.total = -1;
1743                    mItems.add(item);
1744                    knownItems.put(item.key, item);
1745                }
1746                item.restricted = true;
1747            }
1748
1749            if (!mItems.isEmpty()) {
1750                final AppItem title = new AppItem();
1751                title.category = AppItem.CATEGORY_APP_TITLE;
1752                mItems.add(title);
1753            }
1754
1755            Collections.sort(mItems);
1756            notifyDataSetChanged();
1757        }
1758
1759        /**
1760         * Accumulate data usage of a network stats entry for the item mapped by the collapse key.
1761         * Creates the item if needed.
1762         *
1763         * @param collapseKey the collapse key used to map the item.
1764         * @param knownItems collection of known (already existing) items.
1765         * @param entry the network stats entry to extract data usage from.
1766         * @param itemCategory the item is categorized on the list view by this category. Must be
1767         *            either AppItem.APP_ITEM_CATEGORY or AppItem.MANAGED_USER_ITEM_CATEGORY
1768         */
1769        private void accumulate(int collapseKey, final SparseArray<AppItem> knownItems,
1770                NetworkStats.Entry entry, int itemCategory) {
1771            final int uid = entry.uid;
1772            AppItem item = knownItems.get(collapseKey);
1773            if (item == null) {
1774                item = new AppItem(collapseKey);
1775                item.category = itemCategory;
1776                mItems.add(item);
1777                knownItems.put(item.key, item);
1778            }
1779            item.addUid(uid);
1780            item.total += entry.rxBytes + entry.txBytes;
1781            if (mLargest < item.total) {
1782                mLargest = item.total;
1783            }
1784        }
1785
1786        @Override
1787        public int getCount() {
1788            return mItems.size();
1789        }
1790
1791        @Override
1792        public Object getItem(int position) {
1793            return mItems.get(position);
1794        }
1795
1796        @Override
1797        public long getItemId(int position) {
1798            return mItems.get(position).key;
1799        }
1800
1801        /**
1802         * See {@link #getItemViewType} for the view types.
1803         */
1804        @Override
1805        public int getViewTypeCount() {
1806            return 2;
1807        }
1808
1809        /**
1810         * Returns 1 for separator items and 0 for anything else.
1811         */
1812        @Override
1813        public int getItemViewType(int position) {
1814            final AppItem item = mItems.get(position);
1815            if (item.category == AppItem.CATEGORY_APP_TITLE) {
1816                return 1;
1817            } else {
1818                return 0;
1819            }
1820        }
1821
1822        @Override
1823        public boolean areAllItemsEnabled() {
1824            return false;
1825        }
1826
1827        @Override
1828        public boolean isEnabled(int position) {
1829            if (position > mItems.size()) {
1830                throw new ArrayIndexOutOfBoundsException();
1831            }
1832            return getItemViewType(position) == 0;
1833        }
1834
1835        @Override
1836        public View getView(int position, View convertView, ViewGroup parent) {
1837            final AppItem item = mItems.get(position);
1838            if (getItemViewType(position) == 1) {
1839                if (convertView == null) {
1840                    convertView = inflateCategoryHeader(LayoutInflater.from(parent.getContext()),
1841                            parent);
1842                }
1843
1844                final TextView title = (TextView) convertView.findViewById(android.R.id.title);
1845                title.setText(R.string.data_usage_app);
1846
1847            } else {
1848                if (convertView == null) {
1849                    convertView = LayoutInflater.from(parent.getContext()).inflate(
1850                            R.layout.data_usage_item, parent, false);
1851
1852                    if (mInsetSide > 0) {
1853                        convertView.setPaddingRelative(mInsetSide, 0, mInsetSide, 0);
1854                    }
1855                }
1856
1857                final Context context = parent.getContext();
1858
1859                final TextView text1 = (TextView) convertView.findViewById(android.R.id.text1);
1860                final ProgressBar progress = (ProgressBar) convertView.findViewById(
1861                        android.R.id.progress);
1862
1863                // kick off async load of app details
1864                UidDetailTask.bindView(mProvider, item, convertView);
1865
1866                if (item.restricted && item.total <= 0) {
1867                    text1.setText(R.string.data_usage_app_restricted);
1868                    progress.setVisibility(View.GONE);
1869                } else {
1870                    text1.setText(Formatter.formatFileSize(context, item.total));
1871                    progress.setVisibility(View.VISIBLE);
1872                }
1873
1874                final int percentTotal = mLargest != 0 ? (int) (item.total * 100 / mLargest) : 0;
1875                progress.setProgress(percentTotal);
1876            }
1877
1878            return convertView;
1879        }
1880    }
1881
1882    /**
1883     * Empty {@link Fragment} that controls display of UID details in
1884     * {@link DataUsageSummary}.
1885     */
1886    public static class AppDetailsFragment extends Fragment {
1887        private static final String EXTRA_APP = "app";
1888
1889        public static void show(DataUsageSummary parent, AppItem app, CharSequence label) {
1890            if (!parent.isAdded()) return;
1891
1892            final Bundle args = new Bundle();
1893            args.putParcelable(EXTRA_APP, app);
1894
1895            final AppDetailsFragment fragment = new AppDetailsFragment();
1896            fragment.setArguments(args);
1897            fragment.setTargetFragment(parent, 0);
1898            final FragmentTransaction ft = parent.getFragmentManager().beginTransaction();
1899            ft.add(fragment, TAG_APP_DETAILS);
1900            ft.addToBackStack(TAG_APP_DETAILS);
1901            ft.setBreadCrumbTitle(
1902                    parent.getResources().getString(R.string.data_usage_app_summary_title));
1903            ft.commitAllowingStateLoss();
1904        }
1905
1906        @Override
1907        public void onStart() {
1908            super.onStart();
1909            final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
1910            target.mCurrentApp = getArguments().getParcelable(EXTRA_APP);
1911            target.updateBody();
1912        }
1913
1914        @Override
1915        public void onStop() {
1916            super.onStop();
1917            final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
1918            target.mCurrentApp = null;
1919            target.updateBody();
1920        }
1921    }
1922
1923    /**
1924     * Dialog to request user confirmation before setting
1925     * {@link NetworkPolicy#limitBytes}.
1926     */
1927    public static class ConfirmLimitFragment extends DialogFragment {
1928        private static final String EXTRA_MESSAGE = "message";
1929        private static final String EXTRA_LIMIT_BYTES = "limitBytes";
1930
1931        public static void show(DataUsageSummary parent) {
1932            if (!parent.isAdded()) return;
1933
1934            final NetworkPolicy policy = parent.mPolicyEditor.getPolicy(parent.mTemplate);
1935            if (policy == null) return;
1936
1937            final Resources res = parent.getResources();
1938            final CharSequence message;
1939            final long minLimitBytes = (long) (policy.warningBytes * 1.2f);
1940            final long limitBytes;
1941
1942            // TODO: customize default limits based on network template
1943            final String currentTab = parent.mCurrentTab;
1944            if (TAB_3G.equals(currentTab)) {
1945                message = res.getString(R.string.data_usage_limit_dialog_mobile);
1946                limitBytes = Math.max(5 * GB_IN_BYTES, minLimitBytes);
1947            } else if (TAB_4G.equals(currentTab)) {
1948                message = res.getString(R.string.data_usage_limit_dialog_mobile);
1949                limitBytes = Math.max(5 * GB_IN_BYTES, minLimitBytes);
1950            } else if (isMobileTab(currentTab)) {
1951                message = res.getString(R.string.data_usage_limit_dialog_mobile);
1952                limitBytes = Math.max(5 * GB_IN_BYTES, minLimitBytes);
1953            } else {
1954                throw new IllegalArgumentException("unknown current tab: " + currentTab);
1955            }
1956
1957            final Bundle args = new Bundle();
1958            args.putCharSequence(EXTRA_MESSAGE, message);
1959            args.putLong(EXTRA_LIMIT_BYTES, limitBytes);
1960
1961            final ConfirmLimitFragment dialog = new ConfirmLimitFragment();
1962            dialog.setArguments(args);
1963            dialog.setTargetFragment(parent, 0);
1964            dialog.show(parent.getFragmentManager(), TAG_CONFIRM_LIMIT);
1965        }
1966
1967        @Override
1968        public Dialog onCreateDialog(Bundle savedInstanceState) {
1969            final Context context = getActivity();
1970
1971            final CharSequence message = getArguments().getCharSequence(EXTRA_MESSAGE);
1972            final long limitBytes = getArguments().getLong(EXTRA_LIMIT_BYTES);
1973
1974            final AlertDialog.Builder builder = new AlertDialog.Builder(context);
1975            builder.setTitle(R.string.data_usage_limit_dialog_title);
1976            builder.setMessage(message);
1977
1978            builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
1979                @Override
1980                public void onClick(DialogInterface dialog, int which) {
1981                    final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
1982                    if (target != null) {
1983                        target.setPolicyLimitBytes(limitBytes);
1984                    }
1985                }
1986            });
1987
1988            return builder.create();
1989        }
1990    }
1991
1992    /**
1993     * Dialog to edit {@link NetworkPolicy#cycleDay}.
1994     */
1995    public static class CycleEditorFragment extends DialogFragment {
1996        private static final String EXTRA_TEMPLATE = "template";
1997
1998        public static void show(DataUsageSummary parent) {
1999            if (!parent.isAdded()) return;
2000
2001            final Bundle args = new Bundle();
2002            args.putParcelable(EXTRA_TEMPLATE, parent.mTemplate);
2003
2004            final CycleEditorFragment dialog = new CycleEditorFragment();
2005            dialog.setArguments(args);
2006            dialog.setTargetFragment(parent, 0);
2007            dialog.show(parent.getFragmentManager(), TAG_CYCLE_EDITOR);
2008        }
2009
2010        @Override
2011        public Dialog onCreateDialog(Bundle savedInstanceState) {
2012            final Context context = getActivity();
2013            final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
2014            final NetworkPolicyEditor editor = target.mPolicyEditor;
2015
2016            final AlertDialog.Builder builder = new AlertDialog.Builder(context);
2017            final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
2018
2019            final View view = dialogInflater.inflate(R.layout.data_usage_cycle_editor, null, false);
2020            final NumberPicker cycleDayPicker = (NumberPicker) view.findViewById(R.id.cycle_day);
2021
2022            final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE);
2023            final int cycleDay = editor.getPolicyCycleDay(template);
2024
2025            cycleDayPicker.setMinValue(1);
2026            cycleDayPicker.setMaxValue(31);
2027            cycleDayPicker.setValue(cycleDay);
2028            cycleDayPicker.setWrapSelectorWheel(true);
2029
2030            builder.setTitle(R.string.data_usage_cycle_editor_title);
2031            builder.setView(view);
2032
2033            builder.setPositiveButton(R.string.data_usage_cycle_editor_positive,
2034                    new DialogInterface.OnClickListener() {
2035                        @Override
2036                        public void onClick(DialogInterface dialog, int which) {
2037                            // clear focus to finish pending text edits
2038                            cycleDayPicker.clearFocus();
2039
2040                            final int cycleDay = cycleDayPicker.getValue();
2041                            final String cycleTimezone = new Time().timezone;
2042                            editor.setPolicyCycleDay(template, cycleDay, cycleTimezone);
2043                            target.updatePolicy(true);
2044                        }
2045                    });
2046
2047            return builder.create();
2048        }
2049    }
2050
2051    /**
2052     * Dialog to edit {@link NetworkPolicy#warningBytes}.
2053     */
2054    public static class WarningEditorFragment extends DialogFragment {
2055        private static final String EXTRA_TEMPLATE = "template";
2056
2057        public static void show(DataUsageSummary parent) {
2058            if (!parent.isAdded()) return;
2059
2060            final Bundle args = new Bundle();
2061            args.putParcelable(EXTRA_TEMPLATE, parent.mTemplate);
2062
2063            final WarningEditorFragment dialog = new WarningEditorFragment();
2064            dialog.setArguments(args);
2065            dialog.setTargetFragment(parent, 0);
2066            dialog.show(parent.getFragmentManager(), TAG_WARNING_EDITOR);
2067        }
2068
2069        @Override
2070        public Dialog onCreateDialog(Bundle savedInstanceState) {
2071            final Context context = getActivity();
2072            final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
2073            final NetworkPolicyEditor editor = target.mPolicyEditor;
2074
2075            final AlertDialog.Builder builder = new AlertDialog.Builder(context);
2076            final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
2077
2078            final View view = dialogInflater.inflate(R.layout.data_usage_bytes_editor, null, false);
2079            final NumberPicker bytesPicker = (NumberPicker) view.findViewById(R.id.bytes);
2080
2081            final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE);
2082            final long warningBytes = editor.getPolicyWarningBytes(template);
2083            final long limitBytes = editor.getPolicyLimitBytes(template);
2084
2085            bytesPicker.setMinValue(0);
2086            if (limitBytes != LIMIT_DISABLED) {
2087                bytesPicker.setMaxValue((int) (limitBytes / MB_IN_BYTES) - 1);
2088            } else {
2089                bytesPicker.setMaxValue(Integer.MAX_VALUE);
2090            }
2091            bytesPicker.setValue((int) (warningBytes / MB_IN_BYTES));
2092            bytesPicker.setWrapSelectorWheel(false);
2093
2094            builder.setTitle(R.string.data_usage_warning_editor_title);
2095            builder.setView(view);
2096
2097            builder.setPositiveButton(R.string.data_usage_cycle_editor_positive,
2098                    new DialogInterface.OnClickListener() {
2099                        @Override
2100                        public void onClick(DialogInterface dialog, int which) {
2101                            // clear focus to finish pending text edits
2102                            bytesPicker.clearFocus();
2103
2104                            final long bytes = bytesPicker.getValue() * MB_IN_BYTES;
2105                            editor.setPolicyWarningBytes(template, bytes);
2106                            target.updatePolicy(false);
2107                        }
2108                    });
2109
2110            return builder.create();
2111        }
2112    }
2113
2114    /**
2115     * Dialog to edit {@link NetworkPolicy#limitBytes}.
2116     */
2117    public static class LimitEditorFragment extends DialogFragment {
2118        private static final String EXTRA_TEMPLATE = "template";
2119
2120        public static void show(DataUsageSummary parent) {
2121            if (!parent.isAdded()) return;
2122
2123            final Bundle args = new Bundle();
2124            args.putParcelable(EXTRA_TEMPLATE, parent.mTemplate);
2125
2126            final LimitEditorFragment dialog = new LimitEditorFragment();
2127            dialog.setArguments(args);
2128            dialog.setTargetFragment(parent, 0);
2129            dialog.show(parent.getFragmentManager(), TAG_LIMIT_EDITOR);
2130        }
2131
2132        @Override
2133        public Dialog onCreateDialog(Bundle savedInstanceState) {
2134            final Context context = getActivity();
2135            final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
2136            final NetworkPolicyEditor editor = target.mPolicyEditor;
2137
2138            final AlertDialog.Builder builder = new AlertDialog.Builder(context);
2139            final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
2140
2141            final View view = dialogInflater.inflate(R.layout.data_usage_bytes_editor, null, false);
2142            final NumberPicker bytesPicker = (NumberPicker) view.findViewById(R.id.bytes);
2143
2144            final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE);
2145            final long warningBytes = editor.getPolicyWarningBytes(template);
2146            final long limitBytes = editor.getPolicyLimitBytes(template);
2147
2148            bytesPicker.setMaxValue(Integer.MAX_VALUE);
2149            if (warningBytes != WARNING_DISABLED && limitBytes > 0) {
2150                bytesPicker.setMinValue((int) (warningBytes / MB_IN_BYTES) + 1);
2151            } else {
2152                bytesPicker.setMinValue(0);
2153            }
2154            bytesPicker.setValue((int) (limitBytes / MB_IN_BYTES));
2155            bytesPicker.setWrapSelectorWheel(false);
2156
2157            builder.setTitle(R.string.data_usage_limit_editor_title);
2158            builder.setView(view);
2159
2160            builder.setPositiveButton(R.string.data_usage_cycle_editor_positive,
2161                    new DialogInterface.OnClickListener() {
2162                        @Override
2163                        public void onClick(DialogInterface dialog, int which) {
2164                            // clear focus to finish pending text edits
2165                            bytesPicker.clearFocus();
2166
2167                            final long bytes = bytesPicker.getValue() * MB_IN_BYTES;
2168                            editor.setPolicyLimitBytes(template, bytes);
2169                            target.updatePolicy(false);
2170                        }
2171                    });
2172
2173            return builder.create();
2174        }
2175    }
2176    /**
2177     * Dialog to request user confirmation before disabling data.
2178     */
2179    public static class ConfirmDataDisableFragment extends DialogFragment {
2180        static int mSubId;
2181        public static void show(DataUsageSummary parent, int subId) {
2182            mSubId = subId;
2183            if (!parent.isAdded()) return;
2184
2185            final ConfirmDataDisableFragment dialog = new ConfirmDataDisableFragment();
2186            dialog.setTargetFragment(parent, 0);
2187            dialog.show(parent.getFragmentManager(), TAG_CONFIRM_DATA_DISABLE);
2188        }
2189
2190        @Override
2191        public Dialog onCreateDialog(Bundle savedInstanceState) {
2192            final Context context = getActivity();
2193
2194            final AlertDialog.Builder builder = new AlertDialog.Builder(context);
2195            builder.setMessage(R.string.data_usage_disable_mobile);
2196
2197            builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
2198                @Override
2199                public void onClick(DialogInterface dialog, int which) {
2200                    final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
2201                    if (target != null) {
2202                        // TODO: extend to modify policy enabled flag.
2203                        target.setMobileDataEnabled(mSubId, false);
2204                    }
2205                }
2206            });
2207            builder.setNegativeButton(android.R.string.cancel, null);
2208
2209            return builder.create();
2210        }
2211    }
2212
2213    /**
2214     * Dialog to request user confirmation before setting
2215     * {@link INetworkPolicyManager#setRestrictBackground(boolean)}.
2216     */
2217    public static class ConfirmRestrictFragment extends DialogFragment {
2218        public static void show(DataUsageSummary parent) {
2219            if (!parent.isAdded()) return;
2220
2221            final ConfirmRestrictFragment dialog = new ConfirmRestrictFragment();
2222            dialog.setTargetFragment(parent, 0);
2223            dialog.show(parent.getFragmentManager(), TAG_CONFIRM_RESTRICT);
2224        }
2225
2226        @Override
2227        public Dialog onCreateDialog(Bundle savedInstanceState) {
2228            final Context context = getActivity();
2229
2230            final AlertDialog.Builder builder = new AlertDialog.Builder(context);
2231            builder.setTitle(R.string.data_usage_restrict_background_title);
2232            if (Utils.hasMultipleUsers(context)) {
2233                builder.setMessage(R.string.data_usage_restrict_background_multiuser);
2234            } else {
2235                builder.setMessage(R.string.data_usage_restrict_background);
2236            }
2237
2238            builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
2239                @Override
2240                public void onClick(DialogInterface dialog, int which) {
2241                    final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
2242                    if (target != null) {
2243                        target.setRestrictBackground(true);
2244                    }
2245                }
2246            });
2247            builder.setNegativeButton(android.R.string.cancel, null);
2248
2249            return builder.create();
2250        }
2251    }
2252
2253    /**
2254     * Dialog to inform user that {@link #POLICY_REJECT_METERED_BACKGROUND}
2255     * change has been denied, usually based on
2256     * {@link DataUsageSummary#hasLimitedNetworks()}.
2257     */
2258    public static class DeniedRestrictFragment extends DialogFragment {
2259        public static void show(DataUsageSummary parent) {
2260            if (!parent.isAdded()) return;
2261
2262            final DeniedRestrictFragment dialog = new DeniedRestrictFragment();
2263            dialog.setTargetFragment(parent, 0);
2264            dialog.show(parent.getFragmentManager(), TAG_DENIED_RESTRICT);
2265        }
2266
2267        @Override
2268        public Dialog onCreateDialog(Bundle savedInstanceState) {
2269            final Context context = getActivity();
2270
2271            final AlertDialog.Builder builder = new AlertDialog.Builder(context);
2272            builder.setTitle(R.string.data_usage_app_restrict_background);
2273            builder.setMessage(R.string.data_usage_restrict_denied_dialog);
2274            builder.setPositiveButton(android.R.string.ok, null);
2275
2276            return builder.create();
2277        }
2278    }
2279
2280    /**
2281     * Dialog to request user confirmation before setting
2282     * {@link #POLICY_REJECT_METERED_BACKGROUND}.
2283     */
2284    public static class ConfirmAppRestrictFragment extends DialogFragment {
2285        public static void show(DataUsageSummary parent) {
2286            if (!parent.isAdded()) return;
2287
2288            final ConfirmAppRestrictFragment dialog = new ConfirmAppRestrictFragment();
2289            dialog.setTargetFragment(parent, 0);
2290            dialog.show(parent.getFragmentManager(), TAG_CONFIRM_APP_RESTRICT);
2291        }
2292
2293        @Override
2294        public Dialog onCreateDialog(Bundle savedInstanceState) {
2295            final Context context = getActivity();
2296
2297            final AlertDialog.Builder builder = new AlertDialog.Builder(context);
2298            builder.setTitle(R.string.data_usage_app_restrict_dialog_title);
2299            builder.setMessage(R.string.data_usage_app_restrict_dialog);
2300
2301            builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
2302                @Override
2303                public void onClick(DialogInterface dialog, int which) {
2304                    final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
2305                    if (target != null) {
2306                        target.setAppRestrictBackground(true);
2307                    }
2308                }
2309            });
2310            builder.setNegativeButton(android.R.string.cancel, null);
2311
2312            return builder.create();
2313        }
2314    }
2315
2316    /**
2317     * Compute default tab that should be selected, based on
2318     * {@link NetworkPolicyManager#EXTRA_NETWORK_TEMPLATE} extra.
2319     */
2320    private static String computeTabFromIntent(Intent intent) {
2321        final NetworkTemplate template = intent.getParcelableExtra(EXTRA_NETWORK_TEMPLATE);
2322        if (template == null) {
2323            final int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
2324                    SubscriptionManager.INVALID_SUBSCRIPTION_ID);
2325            if (SubscriptionManager.isValidSubscriptionId(subId)) {
2326                return TAB_MOBILE + String.valueOf(subId);
2327            }
2328            return null;
2329        }
2330
2331        switch (template.getMatchRule()) {
2332            case MATCH_MOBILE_3G_LOWER:
2333                return TAB_3G;
2334            case MATCH_MOBILE_4G:
2335                return TAB_4G;
2336            case MATCH_MOBILE_ALL:
2337                return TAB_MOBILE;
2338            case MATCH_WIFI:
2339                return TAB_WIFI;
2340            default:
2341                return null;
2342        }
2343    }
2344
2345    /**
2346     * Background task that loads {@link UidDetail}, binding to
2347     * {@link DataUsageAdapter} row item when finished.
2348     */
2349    private static class UidDetailTask extends AsyncTask<Void, Void, UidDetail> {
2350        private final UidDetailProvider mProvider;
2351        private final AppItem mItem;
2352        private final View mTarget;
2353
2354        private UidDetailTask(UidDetailProvider provider, AppItem item, View target) {
2355            mProvider = checkNotNull(provider);
2356            mItem = checkNotNull(item);
2357            mTarget = checkNotNull(target);
2358        }
2359
2360        public static void bindView(
2361                UidDetailProvider provider, AppItem item, View target) {
2362            final UidDetailTask existing = (UidDetailTask) target.getTag();
2363            if (existing != null) {
2364                existing.cancel(false);
2365            }
2366
2367            final UidDetail cachedDetail = provider.getUidDetail(item.key, false);
2368            if (cachedDetail != null) {
2369                bindView(cachedDetail, target);
2370            } else {
2371                target.setTag(new UidDetailTask(provider, item, target).executeOnExecutor(
2372                        AsyncTask.THREAD_POOL_EXECUTOR));
2373            }
2374        }
2375
2376        private static void bindView(UidDetail detail, View target) {
2377            final ImageView icon = (ImageView) target.findViewById(android.R.id.icon);
2378            final TextView title = (TextView) target.findViewById(android.R.id.title);
2379
2380            if (detail != null) {
2381                icon.setImageDrawable(detail.icon);
2382                title.setText(detail.label);
2383                title.setContentDescription(detail.contentDescription);
2384            } else {
2385                icon.setImageDrawable(null);
2386                title.setText(null);
2387            }
2388        }
2389
2390        @Override
2391        protected void onPreExecute() {
2392            bindView(null, mTarget);
2393        }
2394
2395        @Override
2396        protected UidDetail doInBackground(Void... params) {
2397            return mProvider.getUidDetail(mItem.key, true);
2398        }
2399
2400        @Override
2401        protected void onPostExecute(UidDetail result) {
2402            bindView(result, mTarget);
2403        }
2404    }
2405
2406    /**
2407     * Test if device has a mobile data radio with SIM in ready state.
2408     */
2409    public static boolean hasReadyMobileRadio(Context context) {
2410        if (TEST_RADIOS) {
2411            return SystemProperties.get(TEST_RADIOS_PROP).contains("mobile");
2412        }
2413
2414        final ConnectivityManager conn = ConnectivityManager.from(context);
2415        final TelephonyManager tele = TelephonyManager.from(context);
2416
2417        final List<SubscriptionInfo> subInfoList =
2418                SubscriptionManager.from(context).getActiveSubscriptionInfoList();
2419        // No activated Subscriptions
2420        if (subInfoList == null) {
2421            if (LOGD) Log.d(TAG, "hasReadyMobileRadio: subInfoList=null");
2422            return false;
2423        }
2424        // require both supported network and ready SIM
2425        boolean isReady = true;
2426        for (SubscriptionInfo subInfo : subInfoList) {
2427            isReady = isReady & tele.getSimState(subInfo.getSimSlotIndex()) == SIM_STATE_READY;
2428            if (LOGD) Log.d(TAG, "hasReadyMobileRadio: subInfo=" + subInfo);
2429        }
2430        boolean retVal = conn.isNetworkSupported(TYPE_MOBILE) && isReady;
2431        if (LOGD) {
2432            Log.d(TAG, "hasReadyMobileRadio:"
2433                    + " conn.isNetworkSupported(TYPE_MOBILE)="
2434                                            + conn.isNetworkSupported(TYPE_MOBILE)
2435                    + " isReady=" + isReady);
2436        }
2437        return retVal;
2438    }
2439
2440    /*
2441     * TODO: consider adding to TelephonyManager or SubscritpionManager.
2442     */
2443    public static boolean hasReadyMobileRadio(Context context, int subId) {
2444        if (TEST_RADIOS) {
2445            return SystemProperties.get(TEST_RADIOS_PROP).contains("mobile");
2446        }
2447
2448        final ConnectivityManager conn = ConnectivityManager.from(context);
2449        final TelephonyManager tele = TelephonyManager.from(context);
2450        final int slotId = SubscriptionManager.getSlotId(subId);
2451        final boolean isReady = tele.getSimState(slotId) == SIM_STATE_READY;
2452
2453        boolean retVal =  conn.isNetworkSupported(TYPE_MOBILE) && isReady;
2454        if (LOGD) Log.d(TAG, "hasReadyMobileRadio: subId=" + subId
2455                + " conn.isNetworkSupported(TYPE_MOBILE)=" + conn.isNetworkSupported(TYPE_MOBILE)
2456                + " isReady=" + isReady);
2457        return retVal;
2458    }
2459
2460    /**
2461     * Test if device has a mobile 4G data radio.
2462     */
2463    public static boolean hasReadyMobile4gRadio(Context context) {
2464        if (!NetworkPolicyEditor.ENABLE_SPLIT_POLICIES) {
2465            return false;
2466        }
2467        if (TEST_RADIOS) {
2468            return SystemProperties.get(TEST_RADIOS_PROP).contains("4g");
2469        }
2470
2471        final ConnectivityManager conn = ConnectivityManager.from(context);
2472        final TelephonyManager tele = TelephonyManager.from(context);
2473
2474        final boolean hasWimax = conn.isNetworkSupported(TYPE_WIMAX);
2475        final boolean hasLte = (tele.getLteOnCdmaMode() == PhoneConstants.LTE_ON_CDMA_TRUE)
2476                && hasReadyMobileRadio(context);
2477        return hasWimax || hasLte;
2478    }
2479
2480    /**
2481     * Test if device has a Wi-Fi data radio.
2482     */
2483    public static boolean hasWifiRadio(Context context) {
2484        if (TEST_RADIOS) {
2485            return SystemProperties.get(TEST_RADIOS_PROP).contains("wifi");
2486        }
2487
2488        final ConnectivityManager conn = ConnectivityManager.from(context);
2489        return conn.isNetworkSupported(TYPE_WIFI);
2490    }
2491
2492    /**
2493     * Test if device has an ethernet network connection.
2494     */
2495    public boolean hasEthernet(Context context) {
2496        if (TEST_RADIOS) {
2497            return SystemProperties.get(TEST_RADIOS_PROP).contains("ethernet");
2498        }
2499
2500        final ConnectivityManager conn = ConnectivityManager.from(context);
2501        final boolean hasEthernet = conn.isNetworkSupported(TYPE_ETHERNET);
2502
2503        final long ethernetBytes;
2504        if (mStatsSession != null) {
2505            try {
2506                ethernetBytes = mStatsSession.getSummaryForNetwork(
2507                        NetworkTemplate.buildTemplateEthernet(), Long.MIN_VALUE, Long.MAX_VALUE)
2508                        .getTotalBytes();
2509            } catch (RemoteException e) {
2510                throw new RuntimeException(e);
2511            }
2512        } else {
2513            ethernetBytes = 0;
2514        }
2515
2516        // only show ethernet when both hardware present and traffic has occurred
2517        return hasEthernet && ethernetBytes > 0;
2518    }
2519
2520    /**
2521     * Inflate a {@link Preference} style layout, adding the given {@link View}
2522     * widget into {@link android.R.id#widget_frame}.
2523     */
2524    private static View inflatePreference(LayoutInflater inflater, ViewGroup root, View widget) {
2525        final View view = inflater.inflate(R.layout.preference, root, false);
2526        final LinearLayout widgetFrame = (LinearLayout) view.findViewById(
2527                android.R.id.widget_frame);
2528        widgetFrame.addView(widget, new LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
2529        return view;
2530    }
2531
2532    private static View inflateCategoryHeader(LayoutInflater inflater, ViewGroup root) {
2533        final TypedArray a = inflater.getContext().obtainStyledAttributes(null,
2534                com.android.internal.R.styleable.Preference,
2535                com.android.internal.R.attr.preferenceCategoryStyle, 0);
2536        final int resId = a.getResourceId(com.android.internal.R.styleable.Preference_layout, 0);
2537        return inflater.inflate(resId, root, false);
2538    }
2539
2540    /**
2541     * Test if any networks are currently limited.
2542     */
2543    private boolean hasLimitedNetworks() {
2544        return !buildLimitedNetworksList().isEmpty();
2545    }
2546
2547    /**
2548     * Build string describing currently limited networks, which defines when
2549     * background data is restricted.
2550     */
2551    @Deprecated
2552    private CharSequence buildLimitedNetworksString() {
2553        final List<CharSequence> limited = buildLimitedNetworksList();
2554
2555        // handle case where no networks limited
2556        if (limited.isEmpty()) {
2557            limited.add(getText(R.string.data_usage_list_none));
2558        }
2559
2560        return TextUtils.join(limited);
2561    }
2562
2563    /**
2564     * Build list of currently limited networks, which defines when background
2565     * data is restricted.
2566     */
2567    @Deprecated
2568    private List<CharSequence> buildLimitedNetworksList() {
2569        final Context context = getActivity();
2570
2571        // build combined list of all limited networks
2572        final ArrayList<CharSequence> limited = Lists.newArrayList();
2573
2574        final TelephonyManager tele = TelephonyManager.from(context);
2575        if (tele.getSimState() == SIM_STATE_READY) {
2576            final String subscriberId = getActiveSubscriberId(context);
2577            if (mPolicyEditor.hasLimitedPolicy(buildTemplateMobileAll(subscriberId))) {
2578                limited.add(getText(R.string.data_usage_list_mobile));
2579            }
2580            if (mPolicyEditor.hasLimitedPolicy(buildTemplateMobile3gLower(subscriberId))) {
2581                limited.add(getText(R.string.data_usage_tab_3g));
2582            }
2583            if (mPolicyEditor.hasLimitedPolicy(buildTemplateMobile4g(subscriberId))) {
2584                limited.add(getText(R.string.data_usage_tab_4g));
2585            }
2586        }
2587
2588        if (mPolicyEditor.hasLimitedPolicy(buildTemplateWifiWildcard())) {
2589            limited.add(getText(R.string.data_usage_tab_wifi));
2590        }
2591        if (mPolicyEditor.hasLimitedPolicy(buildTemplateEthernet())) {
2592            limited.add(getText(R.string.data_usage_tab_ethernet));
2593        }
2594
2595        return limited;
2596    }
2597
2598    /**
2599     * Inset both selector and divider {@link Drawable} on the given
2600     * {@link ListView} by the requested dimensions.
2601     */
2602    private static void insetListViewDrawables(ListView view, int insetSide) {
2603        final Drawable selector = view.getSelector();
2604        final Drawable divider = view.getDivider();
2605
2606        // fully unregister these drawables so callbacks can be maintained after
2607        // wrapping below.
2608        final Drawable stub = new ColorDrawable(Color.TRANSPARENT);
2609        view.setSelector(stub);
2610        view.setDivider(stub);
2611
2612        view.setSelector(new InsetBoundsDrawable(selector, insetSide));
2613        view.setDivider(new InsetBoundsDrawable(divider, insetSide));
2614    }
2615
2616    /**
2617     * Set {@link android.R.id#title} for a preference view inflated with
2618     * {@link #inflatePreference(LayoutInflater, ViewGroup, View)}.
2619     */
2620    private static void setPreferenceTitle(View parent, int resId) {
2621        final TextView title = (TextView) parent.findViewById(android.R.id.title);
2622        title.setText(resId);
2623    }
2624
2625    /**
2626     * Set {@link android.R.id#summary} for a preference view inflated with
2627     * {@link #inflatePreference(LayoutInflater, ViewGroup, View)}.
2628     */
2629    private static void setPreferenceSummary(View parent, CharSequence string) {
2630        final TextView summary = (TextView) parent.findViewById(android.R.id.summary);
2631        summary.setVisibility(View.VISIBLE);
2632        summary.setText(string);
2633    }
2634
2635    /**
2636     * For search
2637     */
2638    public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
2639        new BaseSearchIndexProvider() {
2640            @Override
2641            public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
2642                final List<SearchIndexableRaw> result = new ArrayList<SearchIndexableRaw>();
2643
2644                final Resources res = context.getResources();
2645
2646                // Add fragment title
2647                SearchIndexableRaw data = new SearchIndexableRaw(context);
2648                data.title = res.getString(R.string.data_usage_summary_title);
2649                data.screenTitle = res.getString(R.string.data_usage_summary_title);
2650                result.add(data);
2651
2652                // Mobile data
2653                data = new SearchIndexableRaw(context);
2654                data.key = DATA_USAGE_ENABLE_MOBILE_KEY;
2655                data.title = res.getString(R.string.data_usage_enable_mobile);
2656                data.screenTitle = res.getString(R.string.data_usage_summary_title);
2657                result.add(data);
2658
2659                // Set mobile data limit
2660                data = new SearchIndexableRaw(context);
2661                data.key = DATA_USAGE_DISABLE_MOBILE_LIMIT_KEY;
2662                data.title = res.getString(R.string.data_usage_disable_mobile_limit);
2663                data.screenTitle = res.getString(R.string.data_usage_summary_title);
2664                result.add(data);
2665
2666                // Data usage cycle
2667                data = new SearchIndexableRaw(context);
2668                data.key = DATA_USAGE_CYCLE_KEY;
2669                data.title = res.getString(R.string.data_usage_cycle);
2670                data.screenTitle = res.getString(R.string.data_usage_summary_title);
2671                result.add(data);
2672
2673                return result;
2674            }
2675        };
2676
2677        private void addMobileTab(Context context, SubscriptionInfo subInfo, boolean isMultiSim) {
2678            if (subInfo != null && mMobileTagMap != null) {
2679                if (hasReadyMobileRadio(context, subInfo.getSubscriptionId())) {
2680                    if (isMultiSim) {
2681                        mTabHost.addTab(buildTabSpec(mMobileTagMap.get(subInfo.getSubscriptionId()),
2682                                subInfo.getDisplayName()));
2683                    } else {
2684                        mTabHost.addTab(buildTabSpec(mMobileTagMap.get(subInfo.getSubscriptionId()),
2685                                R.string.data_usage_tab_mobile));
2686                    }
2687                }
2688            } else {
2689                if (LOGD) Log.d(TAG, "addMobileTab: subInfoList is null");
2690            }
2691        }
2692
2693        private SubscriptionInfo getCurrentTabSubInfo(Context context) {
2694            if (mSubInfoList != null && mTabHost != null) {
2695                final int currentTagIndex = mTabHost.getCurrentTab();
2696                int i = 0;
2697                for (SubscriptionInfo subInfo : mSubInfoList) {
2698                    if (hasReadyMobileRadio(context, subInfo.getSubscriptionId())) {
2699                        if (i++ == currentTagIndex) {
2700                            return subInfo;
2701                        }
2702                    }
2703                }
2704            }
2705            return null;
2706        }
2707
2708        /**
2709         * Init a map with subId key and mobile tag name
2710         * @param subInfoList The subscription Info List
2711         * @return The map or null if no activated subscription
2712         */
2713        private Map<Integer, String> initMobileTabTag(List<SubscriptionInfo> subInfoList) {
2714            Map<Integer, String> map = null;
2715            if (subInfoList != null) {
2716                String mobileTag;
2717                map = new HashMap<Integer, String>();
2718                for (SubscriptionInfo subInfo : subInfoList) {
2719                    mobileTag = TAB_MOBILE + String.valueOf(subInfo.getSubscriptionId());
2720                    map.put(subInfo.getSubscriptionId(), mobileTag);
2721                }
2722            }
2723            return map;
2724        }
2725
2726        private static boolean isMobileTab(String currentTab) {
2727            return currentTab != null ? currentTab.contains(TAB_MOBILE) : false;
2728        }
2729
2730        private int getSubId(String currentTab) {
2731            if (mMobileTagMap != null) {
2732                Set<Integer> set = mMobileTagMap.keySet();
2733                for (Integer subId : set) {
2734                    if (mMobileTagMap.get(subId).equals(currentTab)) {
2735                        return subId;
2736                    }
2737                }
2738            }
2739            Log.e(TAG, "currentTab = " + currentTab + " non mobile tab called this function");
2740            return -1;
2741        }
2742
2743        //SUB SELECT
2744        private boolean isMobileDataAvailable(long subId) {
2745            int[] subIds = SubscriptionManager.getSubId(PhoneConstants.SUB1);
2746            if (subIds != null && subIds[0] == subId) {
2747                return true;
2748            }
2749
2750            subIds = SubscriptionManager.getSubId(PhoneConstants.SUB2);
2751            if (subIds != null && subIds[0] == subId) {
2752                return true;
2753            }
2754
2755            subIds = SubscriptionManager.getSubId(PhoneConstants.SUB3);
2756            if (subIds != null && subIds[0] == subId) {
2757                return true;
2758            }
2759            return false;
2760        }
2761}
2762