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 
17 package com.android.settings;
18 
19 import static android.net.ConnectivityManager.TYPE_ETHERNET;
20 import static android.net.ConnectivityManager.TYPE_MOBILE;
21 import static android.net.ConnectivityManager.TYPE_WIFI;
22 import static android.net.ConnectivityManager.TYPE_WIMAX;
23 import static android.net.NetworkPolicy.LIMIT_DISABLED;
24 import static android.net.NetworkPolicy.WARNING_DISABLED;
25 import static android.net.NetworkPolicyManager.EXTRA_NETWORK_TEMPLATE;
26 import static android.net.NetworkPolicyManager.POLICY_NONE;
27 import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
28 import static android.net.NetworkPolicyManager.computeLastCycleBoundary;
29 import static android.net.NetworkPolicyManager.computeNextCycleBoundary;
30 import static android.net.NetworkTemplate.MATCH_MOBILE_3G_LOWER;
31 import static android.net.NetworkTemplate.MATCH_MOBILE_4G;
32 import static android.net.NetworkTemplate.MATCH_MOBILE_ALL;
33 import static android.net.NetworkTemplate.MATCH_WIFI;
34 import static android.net.NetworkTemplate.buildTemplateEthernet;
35 import static android.net.NetworkTemplate.buildTemplateMobile3gLower;
36 import static android.net.NetworkTemplate.buildTemplateMobile4g;
37 import static android.net.NetworkTemplate.buildTemplateMobileAll;
38 import static android.net.NetworkTemplate.buildTemplateWifiWildcard;
39 import static android.net.TrafficStats.GB_IN_BYTES;
40 import static android.net.TrafficStats.MB_IN_BYTES;
41 import static android.net.TrafficStats.UID_REMOVED;
42 import static android.net.TrafficStats.UID_TETHERING;
43 import static android.telephony.TelephonyManager.SIM_STATE_READY;
44 import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH;
45 import static android.text.format.DateUtils.FORMAT_SHOW_DATE;
46 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
47 import static com.android.internal.util.Preconditions.checkNotNull;
48 import static com.android.settings.Utils.prepareCustomPreferencesList;
49 
50 import android.animation.LayoutTransition;
51 import android.app.ActivityManager;
52 import android.app.AlertDialog;
53 import android.app.Dialog;
54 import android.app.DialogFragment;
55 import android.app.Fragment;
56 import android.app.FragmentTransaction;
57 import android.app.LoaderManager.LoaderCallbacks;
58 import android.content.ComponentName;
59 import android.content.Context;
60 import android.content.DialogInterface;
61 import android.content.Intent;
62 import android.content.Loader;
63 import android.content.SharedPreferences;
64 import android.content.pm.PackageManager;
65 import android.content.pm.UserInfo;
66 import android.content.res.Resources;
67 import android.content.res.TypedArray;
68 import android.graphics.Color;
69 import android.graphics.drawable.ColorDrawable;
70 import android.graphics.drawable.Drawable;
71 import android.net.ConnectivityManager;
72 import android.net.INetworkPolicyManager;
73 import android.net.INetworkStatsService;
74 import android.net.INetworkStatsSession;
75 import android.net.NetworkPolicy;
76 import android.net.NetworkPolicyManager;
77 import android.net.NetworkStats;
78 import android.net.NetworkStatsHistory;
79 import android.net.NetworkTemplate;
80 import android.net.TrafficStats;
81 import android.os.AsyncTask;
82 import android.os.Bundle;
83 import android.os.INetworkManagementService;
84 import android.os.Parcel;
85 import android.os.Parcelable;
86 import android.os.RemoteException;
87 import android.os.ServiceManager;
88 import android.os.SystemProperties;
89 import android.os.UserHandle;
90 import android.os.UserManager;
91 import android.preference.Preference;
92 import android.telephony.SubscriptionInfo;
93 import android.telephony.SubscriptionManager;
94 import android.telephony.TelephonyManager;
95 import android.text.TextUtils;
96 import android.text.format.DateUtils;
97 import android.text.format.Formatter;
98 import android.text.format.Time;
99 import android.util.Log;
100 import android.util.SparseArray;
101 import android.util.SparseBooleanArray;
102 import android.view.LayoutInflater;
103 import android.view.Menu;
104 import android.view.MenuInflater;
105 import android.view.MenuItem;
106 import android.view.View;
107 import android.view.View.OnClickListener;
108 import android.view.ViewGroup;
109 import android.widget.AdapterView;
110 import android.widget.AdapterView.OnItemClickListener;
111 import android.widget.AdapterView.OnItemSelectedListener;
112 import android.widget.ArrayAdapter;
113 import android.widget.BaseAdapter;
114 import android.widget.Button;
115 import android.widget.ImageView;
116 import android.widget.LinearLayout;
117 import android.widget.ListView;
118 import android.widget.NumberPicker;
119 import android.widget.ProgressBar;
120 import android.widget.Spinner;
121 import android.widget.Switch;
122 import android.widget.TabHost;
123 import android.widget.TabHost.OnTabChangeListener;
124 import android.widget.TabHost.TabContentFactory;
125 import android.widget.TabHost.TabSpec;
126 import android.widget.TabWidget;
127 import android.widget.TextView;
128 
129 import com.android.internal.telephony.PhoneConstants;
130 import com.android.settings.drawable.InsetBoundsDrawable;
131 import com.android.settings.net.ChartData;
132 import com.android.settings.net.ChartDataLoader;
133 import com.android.settings.net.DataUsageMeteredSettings;
134 import com.android.settings.net.NetworkPolicyEditor;
135 import com.android.settings.net.SummaryForAllUidLoader;
136 import com.android.settings.net.UidDetail;
137 import com.android.settings.net.UidDetailProvider;
138 import com.android.settings.search.BaseSearchIndexProvider;
139 import com.android.settings.search.Indexable;
140 import com.android.settings.search.SearchIndexableRaw;
141 import com.android.settings.sim.SimSettings;
142 import com.android.settings.widget.ChartDataUsageView;
143 import com.android.settings.widget.ChartDataUsageView.DataUsageChartListener;
144 import com.android.settings.widget.ChartNetworkSeriesView;
145 
146 import com.google.android.collect.Lists;
147 
148 import libcore.util.Objects;
149 
150 import java.util.ArrayList;
151 import java.util.Collections;
152 import java.util.HashMap;
153 import java.util.List;
154 import java.util.Locale;
155 import java.util.Map;
156 import 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  */
162 public 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
onCreate(Bundle savedInstanceState)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
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)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
onViewStateRestored(Bundle savedInstanceState)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
onResume()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
onCreateOptionsMenu(Menu menu, MenuInflater inflater)502     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
503         inflater.inflate(R.menu.data_usage, menu);
504     }
505 
506     @Override
onPrepareOptionsMenu(Menu menu)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 
updateMenuTitles()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
onOptionsItemSelected(MenuItem item)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
onDestroy()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      */
ensureLayoutTransitions()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 
buildLayoutTransition()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      */
updateTabs()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      */
buildTabSpec(String tag, int titleRes)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      */
buildTabSpec(String tag, CharSequence title)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      */
updateBody()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 
isAppDetailMode()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      */
updateAppDetail()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 
setPolicyWarningBytes(long warningBytes)967     private void setPolicyWarningBytes(long warningBytes) {
968         if (LOGD) Log.d(TAG, "setPolicyWarningBytes()");
969         mPolicyEditor.setPolicyWarningBytes(mTemplate, warningBytes);
970         updatePolicy(false);
971     }
972 
setPolicyLimitBytes(long limitBytes)973     private void setPolicyLimitBytes(long limitBytes) {
974         if (LOGD) Log.d(TAG, "setPolicyLimitBytes()");
975         mPolicyEditor.setPolicyLimitBytes(mTemplate, limitBytes);
976         updatePolicy(false);
977     }
978 
isMobileDataEnabled(int subId)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 
setMobileDataEnabled(int subId, boolean enabled)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 
isNetworkPolicyModifiable(NetworkPolicy policy)1009     private boolean isNetworkPolicyModifiable(NetworkPolicy policy) {
1010         return policy != null && isBandwidthControlEnabled() && mDataEnabled.isChecked()
1011                 && ActivityManager.getCurrentUser() == UserHandle.USER_OWNER;
1012     }
1013 
isBandwidthControlEnabled()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 
setRestrictBackground(boolean restrictBackground)1023     public void setRestrictBackground(boolean restrictBackground) {
1024         mPolicyManager.setRestrictBackground(restrictBackground);
1025         updateMenuTitles();
1026     }
1027 
getAppRestrictBackground()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 
setAppRestrictBackground(boolean restrictBackground)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      */
updatePolicy(boolean refreshCycle)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      */
updateCycleList(NetworkPolicy policy)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 
disableDataForOtherSubscriptions(SubscriptionInfo currentSir)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 
handleMultiSimDataDialog()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      */
updateDetailData()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 
getActiveSubscriberId(Context context)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 
getActiveSubscriberId(Context context, int subId)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 
CycleItem(CharSequence label)1492         CycleItem(CharSequence label) {
1493             this.label = label;
1494         }
1495 
CycleItem(Context context, long start, long end)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
toString()1503         public String toString() {
1504             return label.toString();
1505         }
1506 
1507         @Override
equals(Object o)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
compareTo(CycleItem another)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 
formatDateRange(Context context, long start, long end)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 {
CycleChangeItem(Context context)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 
CycleAdapter(Context context)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 
setChangePossible(boolean possible)1558         public void setChangePossible(boolean possible) {
1559             mChangePossible = possible;
1560             updateChange();
1561         }
1562 
setChangeVisible(boolean visible)1563         public void setChangeVisible(boolean visible) {
1564             mChangeVisible = visible;
1565             updateChange();
1566         }
1567 
updateChange()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          */
findNearestPosition(CycleItem target)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 
AppItem()1607         public AppItem() {
1608             this.key = 0;
1609         }
1610 
AppItem(int key)1611         public AppItem(int key) {
1612             this.key = key;
1613         }
1614 
AppItem(Parcel parcel)1615         public AppItem(Parcel parcel) {
1616             key = parcel.readInt();
1617             uids = parcel.readSparseBooleanArray();
1618             total = parcel.readLong();
1619         }
1620 
addUid(int uid)1621         public void addUid(int uid) {
1622             uids.put(uid, true);
1623         }
1624 
1625         @Override
writeToParcel(Parcel dest, int flags)1626         public void writeToParcel(Parcel dest, int flags) {
1627             dest.writeInt(key);
1628             dest.writeSparseBooleanArray(uids);
1629             dest.writeLong(total);
1630         }
1631 
1632         @Override
describeContents()1633         public int describeContents() {
1634             return 0;
1635         }
1636 
1637         @Override
compareTo(AppItem another)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 
DataUsageAdapter(final UserManager userManager, UidDetailProvider provider, int insetSide)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          */
bindStats(NetworkStats stats, int[] restrictedUids)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          */
accumulate(int collapseKey, final SparseArray<AppItem> knownItems, NetworkStats.Entry entry, int itemCategory)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
getCount()1787         public int getCount() {
1788             return mItems.size();
1789         }
1790 
1791         @Override
getItem(int position)1792         public Object getItem(int position) {
1793             return mItems.get(position);
1794         }
1795 
1796         @Override
getItemId(int position)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
getViewTypeCount()1805         public int getViewTypeCount() {
1806             return 2;
1807         }
1808 
1809         /**
1810          * Returns 1 for separator items and 0 for anything else.
1811          */
1812         @Override
getItemViewType(int position)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
areAllItemsEnabled()1823         public boolean areAllItemsEnabled() {
1824             return false;
1825         }
1826 
1827         @Override
isEnabled(int position)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
getView(int position, View convertView, ViewGroup parent)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 
show(DataUsageSummary parent, AppItem app, CharSequence label)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
onStart()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
onStop()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 
show(DataUsageSummary parent)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
onCreateDialog(Bundle savedInstanceState)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 
show(DataUsageSummary parent)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
onCreateDialog(Bundle savedInstanceState)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 
show(DataUsageSummary parent)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
onCreateDialog(Bundle savedInstanceState)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 
show(DataUsageSummary parent)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
onCreateDialog(Bundle savedInstanceState)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;
show(DataUsageSummary parent, int subId)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
onCreateDialog(Bundle savedInstanceState)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 {
show(DataUsageSummary parent)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
onCreateDialog(Bundle savedInstanceState)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 {
show(DataUsageSummary parent)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
onCreateDialog(Bundle savedInstanceState)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 {
show(DataUsageSummary parent)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
onCreateDialog(Bundle savedInstanceState)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      */
computeTabFromIntent(Intent intent)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 
UidDetailTask(UidDetailProvider provider, AppItem item, View target)2354         private UidDetailTask(UidDetailProvider provider, AppItem item, View target) {
2355             mProvider = checkNotNull(provider);
2356             mItem = checkNotNull(item);
2357             mTarget = checkNotNull(target);
2358         }
2359 
bindView( UidDetailProvider provider, AppItem item, View target)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 
bindView(UidDetail detail, View target)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
onPreExecute()2391         protected void onPreExecute() {
2392             bindView(null, mTarget);
2393         }
2394 
2395         @Override
doInBackground(Void... params)2396         protected UidDetail doInBackground(Void... params) {
2397             return mProvider.getUidDetail(mItem.key, true);
2398         }
2399 
2400         @Override
onPostExecute(UidDetail result)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      */
hasReadyMobileRadio(Context context)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      */
hasReadyMobileRadio(Context context, int subId)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      */
hasReadyMobile4gRadio(Context context)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      */
hasWifiRadio(Context context)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      */
hasEthernet(Context context)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      */
inflatePreference(LayoutInflater inflater, ViewGroup root, View widget)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 
inflateCategoryHeader(LayoutInflater inflater, ViewGroup root)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      */
hasLimitedNetworks()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
buildLimitedNetworksString()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
buildLimitedNetworksList()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      */
insetListViewDrawables(ListView view, int insetSide)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      */
setPreferenceTitle(View parent, int resId)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      */
setPreferenceSummary(View parent, CharSequence string)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 
addMobileTab(Context context, SubscriptionInfo subInfo, boolean isMultiSim)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 
getCurrentTabSubInfo(Context context)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          */
initMobileTabTag(List<SubscriptionInfo> subInfoList)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 
isMobileTab(String currentTab)2726         private static boolean isMobileTab(String currentTab) {
2727             return currentTab != null ? currentTab.contains(TAB_MOBILE) : false;
2728         }
2729 
getSubId(String currentTab)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
isMobileDataAvailable(long subId)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