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_WIMAX; 22 import static android.net.NetworkPolicy.LIMIT_DISABLED; 23 import static android.net.NetworkPolicy.WARNING_DISABLED; 24 import static android.net.NetworkPolicyManager.EXTRA_NETWORK_TEMPLATE; 25 import static android.net.NetworkPolicyManager.POLICY_NONE; 26 import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND; 27 import static android.net.NetworkPolicyManager.computeLastCycleBoundary; 28 import static android.net.NetworkPolicyManager.computeNextCycleBoundary; 29 import static android.net.NetworkTemplate.MATCH_MOBILE_3G_LOWER; 30 import static android.net.NetworkTemplate.MATCH_MOBILE_4G; 31 import static android.net.NetworkTemplate.MATCH_MOBILE_ALL; 32 import static android.net.NetworkTemplate.MATCH_WIFI; 33 import static android.net.NetworkTemplate.buildTemplateEthernet; 34 import static android.net.NetworkTemplate.buildTemplateMobile3gLower; 35 import static android.net.NetworkTemplate.buildTemplateMobile4g; 36 import static android.net.NetworkTemplate.buildTemplateMobileAll; 37 import static android.net.NetworkTemplate.buildTemplateWifi; 38 import static android.net.TrafficStats.UID_REMOVED; 39 import static android.net.TrafficStats.UID_TETHERING; 40 import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH; 41 import static android.text.format.DateUtils.FORMAT_SHOW_DATE; 42 import static android.text.format.Time.TIMEZONE_UTC; 43 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; 44 import static com.android.internal.util.Preconditions.checkNotNull; 45 import static com.android.settings.Utils.prepareCustomPreferencesList; 46 47 import android.animation.LayoutTransition; 48 import android.app.AlertDialog; 49 import android.app.Dialog; 50 import android.app.DialogFragment; 51 import android.app.Fragment; 52 import android.app.FragmentTransaction; 53 import android.app.LoaderManager.LoaderCallbacks; 54 import android.content.ContentResolver; 55 import android.content.Context; 56 import android.content.DialogInterface; 57 import android.content.Intent; 58 import android.content.Loader; 59 import android.content.SharedPreferences; 60 import android.content.pm.PackageManager; 61 import android.content.res.Resources; 62 import android.graphics.Color; 63 import android.graphics.drawable.ColorDrawable; 64 import android.graphics.drawable.Drawable; 65 import android.net.ConnectivityManager; 66 import android.net.INetworkPolicyManager; 67 import android.net.INetworkStatsService; 68 import android.net.NetworkPolicy; 69 import android.net.NetworkPolicyManager; 70 import android.net.NetworkStats; 71 import android.net.NetworkStatsHistory; 72 import android.net.NetworkTemplate; 73 import android.os.AsyncTask; 74 import android.os.Bundle; 75 import android.os.INetworkManagementService; 76 import android.os.RemoteException; 77 import android.os.ServiceManager; 78 import android.os.SystemProperties; 79 import android.preference.Preference; 80 import android.provider.Settings; 81 import android.telephony.TelephonyManager; 82 import android.text.TextUtils; 83 import android.text.format.DateUtils; 84 import android.text.format.Formatter; 85 import android.util.Log; 86 import android.util.SparseArray; 87 import android.view.LayoutInflater; 88 import android.view.Menu; 89 import android.view.MenuInflater; 90 import android.view.MenuItem; 91 import android.view.View; 92 import android.view.View.OnClickListener; 93 import android.view.ViewGroup; 94 import android.view.ViewTreeObserver.OnGlobalLayoutListener; 95 import android.widget.AdapterView; 96 import android.widget.AdapterView.OnItemClickListener; 97 import android.widget.AdapterView.OnItemSelectedListener; 98 import android.widget.ArrayAdapter; 99 import android.widget.BaseAdapter; 100 import android.widget.Button; 101 import android.widget.CheckBox; 102 import android.widget.CompoundButton; 103 import android.widget.CompoundButton.OnCheckedChangeListener; 104 import android.widget.ImageView; 105 import android.widget.LinearLayout; 106 import android.widget.ListView; 107 import android.widget.NumberPicker; 108 import android.widget.ProgressBar; 109 import android.widget.Spinner; 110 import android.widget.Switch; 111 import android.widget.TabHost; 112 import android.widget.TabHost.OnTabChangeListener; 113 import android.widget.TabHost.TabContentFactory; 114 import android.widget.TabHost.TabSpec; 115 import android.widget.TabWidget; 116 import android.widget.TextView; 117 118 import com.android.internal.telephony.Phone; 119 import com.android.settings.drawable.InsetBoundsDrawable; 120 import com.android.settings.net.ChartData; 121 import com.android.settings.net.ChartDataLoader; 122 import com.android.settings.net.NetworkPolicyEditor; 123 import com.android.settings.net.SummaryForAllUidLoader; 124 import com.android.settings.net.UidDetail; 125 import com.android.settings.net.UidDetailProvider; 126 import com.android.settings.widget.ChartDataUsageView; 127 import com.android.settings.widget.ChartDataUsageView.DataUsageChartListener; 128 import com.android.settings.widget.PieChartView; 129 import com.google.android.collect.Lists; 130 131 import java.util.ArrayList; 132 import java.util.Arrays; 133 import java.util.Collections; 134 import java.util.List; 135 import java.util.Locale; 136 137 import libcore.util.Objects; 138 139 /** 140 * Panel show data usage history across various networks, including options to 141 * inspect based on usage cycle and control through {@link NetworkPolicy}. 142 */ 143 public class DataUsageSummary extends Fragment { 144 private static final String TAG = "DataUsage"; 145 private static final boolean LOGD = true; 146 147 // TODO: remove this testing code 148 private static final boolean TEST_ANIM = false; 149 private static final boolean TEST_RADIOS = false; 150 private static final String TEST_RADIOS_PROP = "test.radios"; 151 152 private static final String TAB_3G = "3g"; 153 private static final String TAB_4G = "4g"; 154 private static final String TAB_MOBILE = "mobile"; 155 private static final String TAB_WIFI = "wifi"; 156 private static final String TAB_ETHERNET = "ethernet"; 157 158 private static final String TAG_CONFIRM_DATA_DISABLE = "confirmDataDisable"; 159 private static final String TAG_CONFIRM_DATA_ROAMING = "confirmDataRoaming"; 160 private static final String TAG_CONFIRM_LIMIT = "confirmLimit"; 161 private static final String TAG_CYCLE_EDITOR = "cycleEditor"; 162 private static final String TAG_WARNING_EDITOR = "warningEditor"; 163 private static final String TAG_LIMIT_EDITOR = "limitEditor"; 164 private static final String TAG_CONFIRM_RESTRICT = "confirmRestrict"; 165 private static final String TAG_DENIED_RESTRICT = "deniedRestrict"; 166 private static final String TAG_CONFIRM_APP_RESTRICT = "confirmAppRestrict"; 167 private static final String TAG_APP_DETAILS = "appDetails"; 168 169 private static final int LOADER_CHART_DATA = 2; 170 private static final int LOADER_SUMMARY = 3; 171 172 private static final long KB_IN_BYTES = 1024; 173 private static final long MB_IN_BYTES = KB_IN_BYTES * 1024; 174 private static final long GB_IN_BYTES = MB_IN_BYTES * 1024; 175 176 private INetworkManagementService mNetworkService; 177 private INetworkStatsService mStatsService; 178 private INetworkPolicyManager mPolicyService; 179 private ConnectivityManager mConnService; 180 181 private static final String PREF_FILE = "data_usage"; 182 private static final String PREF_SHOW_WIFI = "show_wifi"; 183 private static final String PREF_SHOW_ETHERNET = "show_ethernet"; 184 185 private SharedPreferences mPrefs; 186 187 private TabHost mTabHost; 188 private ViewGroup mTabsContainer; 189 private TabWidget mTabWidget; 190 private ListView mListView; 191 private DataUsageAdapter mAdapter; 192 193 /** Distance to inset content from sides, when needed. */ 194 private int mInsetSide = 0; 195 196 private ViewGroup mHeader; 197 198 private ViewGroup mNetworkSwitchesContainer; 199 private LinearLayout mNetworkSwitches; 200 private Switch mDataEnabled; 201 private View mDataEnabledView; 202 private CheckBox mDisableAtLimit; 203 private View mDisableAtLimitView; 204 205 private View mCycleView; 206 private Spinner mCycleSpinner; 207 private CycleAdapter mCycleAdapter; 208 209 private ChartDataUsageView mChart; 210 private TextView mUsageSummary; 211 private TextView mEmpty; 212 213 private View mAppDetail; 214 private ImageView mAppIcon; 215 private ViewGroup mAppTitles; 216 private PieChartView mAppPieChart; 217 private TextView mAppForeground; 218 private TextView mAppBackground; 219 private Button mAppSettings; 220 221 private LinearLayout mAppSwitches; 222 private CheckBox mAppRestrict; 223 private View mAppRestrictView; 224 225 private boolean mShowWifi = false; 226 private boolean mShowEthernet = false; 227 228 private NetworkTemplate mTemplate; 229 private ChartData mChartData; 230 231 private int[] mAppDetailUids = null; 232 233 private Intent mAppSettingsIntent; 234 235 private NetworkPolicyEditor mPolicyEditor; 236 237 private String mCurrentTab = null; 238 private String mIntentTab = null; 239 240 private MenuItem mMenuDataRoaming; 241 private MenuItem mMenuRestrictBackground; 242 243 /** Flag used to ignore listeners during binding. */ 244 private boolean mBinding; 245 246 private UidDetailProvider mUidDetailProvider; 247 248 @Override onCreate(Bundle savedInstanceState)249 public void onCreate(Bundle savedInstanceState) { 250 super.onCreate(savedInstanceState); 251 252 mNetworkService = INetworkManagementService.Stub.asInterface( 253 ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)); 254 mStatsService = INetworkStatsService.Stub.asInterface( 255 ServiceManager.getService(Context.NETWORK_STATS_SERVICE)); 256 mPolicyService = INetworkPolicyManager.Stub.asInterface( 257 ServiceManager.getService(Context.NETWORK_POLICY_SERVICE)); 258 mConnService = (ConnectivityManager) getActivity().getSystemService( 259 Context.CONNECTIVITY_SERVICE); 260 261 mPrefs = getActivity().getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE); 262 263 mPolicyEditor = new NetworkPolicyEditor(mPolicyService); 264 mPolicyEditor.read(); 265 266 mShowWifi = mPrefs.getBoolean(PREF_SHOW_WIFI, false); 267 mShowEthernet = mPrefs.getBoolean(PREF_SHOW_ETHERNET, false); 268 269 setHasOptionsMenu(true); 270 } 271 272 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)273 public View onCreateView(LayoutInflater inflater, ViewGroup container, 274 Bundle savedInstanceState) { 275 276 final Context context = inflater.getContext(); 277 final View view = inflater.inflate(R.layout.data_usage_summary, container, false); 278 279 mUidDetailProvider = new UidDetailProvider(context); 280 281 mTabHost = (TabHost) view.findViewById(android.R.id.tabhost); 282 mTabsContainer = (ViewGroup) view.findViewById(R.id.tabs_container); 283 mTabWidget = (TabWidget) view.findViewById(android.R.id.tabs); 284 mListView = (ListView) view.findViewById(android.R.id.list); 285 286 // decide if we need to manually inset our content, or if we should rely 287 // on parent container for inset. 288 final boolean shouldInset = mListView.getScrollBarStyle() 289 == View.SCROLLBARS_OUTSIDE_OVERLAY; 290 if (shouldInset) { 291 mInsetSide = view.getResources().getDimensionPixelOffset( 292 com.android.internal.R.dimen.preference_fragment_padding_side); 293 } else { 294 mInsetSide = 0; 295 } 296 297 // adjust padding around tabwidget as needed 298 prepareCustomPreferencesList(container, view, mListView, true); 299 300 mTabHost.setup(); 301 mTabHost.setOnTabChangedListener(mTabListener); 302 303 mHeader = (ViewGroup) inflater.inflate(R.layout.data_usage_header, mListView, false); 304 mHeader.setClickable(true); 305 306 mListView.addHeaderView(mHeader, null, true); 307 mListView.setItemsCanFocus(true); 308 309 if (mInsetSide > 0) { 310 // inset selector and divider drawables 311 insetListViewDrawables(mListView, mInsetSide); 312 mHeader.setPadding(mInsetSide, 0, mInsetSide, 0); 313 } 314 315 { 316 // bind network switches 317 mNetworkSwitchesContainer = (ViewGroup) mHeader.findViewById( 318 R.id.network_switches_container); 319 mNetworkSwitches = (LinearLayout) mHeader.findViewById(R.id.network_switches); 320 321 mDataEnabled = new Switch(inflater.getContext()); 322 mDataEnabledView = inflatePreference(inflater, mNetworkSwitches, mDataEnabled); 323 mDataEnabled.setOnCheckedChangeListener(mDataEnabledListener); 324 mNetworkSwitches.addView(mDataEnabledView); 325 326 mDisableAtLimit = new CheckBox(inflater.getContext()); 327 mDisableAtLimit.setClickable(false); 328 mDisableAtLimit.setFocusable(false); 329 mDisableAtLimitView = inflatePreference(inflater, mNetworkSwitches, mDisableAtLimit); 330 mDisableAtLimitView.setClickable(true); 331 mDisableAtLimitView.setFocusable(true); 332 mDisableAtLimitView.setOnClickListener(mDisableAtLimitListener); 333 mNetworkSwitches.addView(mDisableAtLimitView); 334 } 335 336 // bind cycle dropdown 337 mCycleView = mHeader.findViewById(R.id.cycles); 338 mCycleSpinner = (Spinner) mCycleView.findViewById(R.id.cycles_spinner); 339 mCycleAdapter = new CycleAdapter(context); 340 mCycleSpinner.setAdapter(mCycleAdapter); 341 mCycleSpinner.setOnItemSelectedListener(mCycleListener); 342 343 mChart = (ChartDataUsageView) mHeader.findViewById(R.id.chart); 344 mChart.setListener(mChartListener); 345 346 { 347 // bind app detail controls 348 mAppDetail = mHeader.findViewById(R.id.app_detail); 349 mAppIcon = (ImageView) mAppDetail.findViewById(R.id.app_icon); 350 mAppTitles = (ViewGroup) mAppDetail.findViewById(R.id.app_titles); 351 mAppPieChart = (PieChartView) mAppDetail.findViewById(R.id.app_pie_chart); 352 mAppForeground = (TextView) mAppDetail.findViewById(R.id.app_foreground); 353 mAppBackground = (TextView) mAppDetail.findViewById(R.id.app_background); 354 mAppSwitches = (LinearLayout) mAppDetail.findViewById(R.id.app_switches); 355 356 mAppSettings = (Button) mAppDetail.findViewById(R.id.app_settings); 357 mAppSettings.setOnClickListener(mAppSettingsListener); 358 359 mAppRestrict = new CheckBox(inflater.getContext()); 360 mAppRestrict.setClickable(false); 361 mAppRestrict.setFocusable(false); 362 mAppRestrictView = inflatePreference(inflater, mAppSwitches, mAppRestrict); 363 mAppRestrictView.setClickable(true); 364 mAppRestrictView.setFocusable(true); 365 mAppRestrictView.setOnClickListener(mAppRestrictListener); 366 mAppSwitches.addView(mAppRestrictView); 367 } 368 369 mUsageSummary = (TextView) mHeader.findViewById(R.id.usage_summary); 370 mEmpty = (TextView) mHeader.findViewById(android.R.id.empty); 371 372 // only assign layout transitions once first layout is finished 373 mListView.getViewTreeObserver().addOnGlobalLayoutListener(mFirstLayoutListener); 374 375 mAdapter = new DataUsageAdapter(mUidDetailProvider, mInsetSide); 376 mListView.setOnItemClickListener(mListListener); 377 mListView.setAdapter(mAdapter); 378 379 return view; 380 } 381 382 @Override onResume()383 public void onResume() { 384 super.onResume(); 385 386 // pick default tab based on incoming intent 387 final Intent intent = getActivity().getIntent(); 388 mIntentTab = computeTabFromIntent(intent); 389 390 // this kicks off chain reaction which creates tabs, binds the body to 391 // selected network, and binds chart, cycles and detail list. 392 updateTabs(); 393 394 // kick off background task to update stats 395 new AsyncTask<Void, Void, Void>() { 396 @Override 397 protected Void doInBackground(Void... params) { 398 try { 399 // wait a few seconds before kicking off 400 Thread.sleep(2 * DateUtils.SECOND_IN_MILLIS); 401 mStatsService.forceUpdate(); 402 } catch (InterruptedException e) { 403 } catch (RemoteException e) { 404 } 405 return null; 406 } 407 408 @Override 409 protected void onPostExecute(Void result) { 410 if (isAdded()) { 411 updateBody(); 412 } 413 } 414 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 415 } 416 417 @Override onCreateOptionsMenu(Menu menu, MenuInflater inflater)418 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 419 inflater.inflate(R.menu.data_usage, menu); 420 } 421 422 @Override onPrepareOptionsMenu(Menu menu)423 public void onPrepareOptionsMenu(Menu menu) { 424 final Context context = getActivity(); 425 final boolean appDetailMode = isAppDetailMode(); 426 427 mMenuDataRoaming = menu.findItem(R.id.data_usage_menu_roaming); 428 mMenuDataRoaming.setVisible(hasMobileRadio(context) && !appDetailMode); 429 mMenuDataRoaming.setChecked(getDataRoaming()); 430 431 mMenuRestrictBackground = menu.findItem(R.id.data_usage_menu_restrict_background); 432 mMenuRestrictBackground.setVisible(!appDetailMode); 433 mMenuRestrictBackground.setChecked(getRestrictBackground()); 434 435 final MenuItem split4g = menu.findItem(R.id.data_usage_menu_split_4g); 436 split4g.setVisible(hasMobile4gRadio(context) && !appDetailMode); 437 split4g.setChecked(isMobilePolicySplit()); 438 439 final MenuItem showWifi = menu.findItem(R.id.data_usage_menu_show_wifi); 440 if (hasWifiRadio(context) && hasMobileRadio(context)) { 441 showWifi.setVisible(!appDetailMode); 442 showWifi.setChecked(mShowWifi); 443 } else { 444 showWifi.setVisible(false); 445 mShowWifi = true; 446 } 447 448 final MenuItem showEthernet = menu.findItem(R.id.data_usage_menu_show_ethernet); 449 if (hasEthernet(context) && hasMobileRadio(context)) { 450 showEthernet.setVisible(!appDetailMode); 451 showEthernet.setChecked(mShowEthernet); 452 } else { 453 showEthernet.setVisible(false); 454 mShowEthernet = true; 455 } 456 } 457 458 @Override onOptionsItemSelected(MenuItem item)459 public boolean onOptionsItemSelected(MenuItem item) { 460 switch (item.getItemId()) { 461 case R.id.data_usage_menu_roaming: { 462 final boolean dataRoaming = !item.isChecked(); 463 if (dataRoaming) { 464 ConfirmDataRoamingFragment.show(this); 465 } else { 466 // no confirmation to disable roaming 467 setDataRoaming(false); 468 } 469 return true; 470 } 471 case R.id.data_usage_menu_restrict_background: { 472 final boolean restrictBackground = !item.isChecked(); 473 if (restrictBackground) { 474 if (hasLimitedNetworks()) { 475 ConfirmRestrictFragment.show(this); 476 } else { 477 DeniedRestrictFragment.show(this); 478 } 479 } else { 480 // no confirmation to drop restriction 481 setRestrictBackground(false); 482 } 483 return true; 484 } 485 case R.id.data_usage_menu_split_4g: { 486 final boolean mobileSplit = !item.isChecked(); 487 setMobilePolicySplit(mobileSplit); 488 item.setChecked(isMobilePolicySplit()); 489 updateTabs(); 490 return true; 491 } 492 case R.id.data_usage_menu_show_wifi: { 493 mShowWifi = !item.isChecked(); 494 mPrefs.edit().putBoolean(PREF_SHOW_WIFI, mShowWifi).apply(); 495 item.setChecked(mShowWifi); 496 updateTabs(); 497 return true; 498 } 499 case R.id.data_usage_menu_show_ethernet: { 500 mShowEthernet = !item.isChecked(); 501 mPrefs.edit().putBoolean(PREF_SHOW_ETHERNET, mShowEthernet).apply(); 502 item.setChecked(mShowEthernet); 503 updateTabs(); 504 return true; 505 } 506 } 507 return false; 508 } 509 510 @Override onDestroyView()511 public void onDestroyView() { 512 super.onDestroyView(); 513 514 mDataEnabledView = null; 515 mDisableAtLimitView = null; 516 517 mUidDetailProvider.clearCache(); 518 mUidDetailProvider = null; 519 } 520 521 /** 522 * Listener to setup {@link LayoutTransition} after first layout pass. 523 */ 524 private OnGlobalLayoutListener mFirstLayoutListener = new OnGlobalLayoutListener() { 525 /** {@inheritDoc} */ 526 public void onGlobalLayout() { 527 mListView.getViewTreeObserver().removeGlobalOnLayoutListener(mFirstLayoutListener); 528 529 mTabsContainer.setLayoutTransition(buildLayoutTransition()); 530 mHeader.setLayoutTransition(buildLayoutTransition()); 531 mNetworkSwitchesContainer.setLayoutTransition(buildLayoutTransition()); 532 533 final LayoutTransition chartTransition = buildLayoutTransition(); 534 chartTransition.setStartDelay(LayoutTransition.APPEARING, 0); 535 chartTransition.setStartDelay(LayoutTransition.DISAPPEARING, 0); 536 chartTransition.setAnimator(LayoutTransition.APPEARING, null); 537 chartTransition.setAnimator(LayoutTransition.DISAPPEARING, null); 538 mChart.setLayoutTransition(chartTransition); 539 } 540 }; 541 buildLayoutTransition()542 private static LayoutTransition buildLayoutTransition() { 543 final LayoutTransition transition = new LayoutTransition(); 544 if (TEST_ANIM) { 545 transition.setDuration(1500); 546 } 547 transition.setAnimateParentHierarchy(false); 548 return transition; 549 } 550 551 /** 552 * Rebuild all tabs based on {@link NetworkPolicyEditor} and 553 * {@link #mShowWifi}, hiding the tabs entirely when applicable. Selects 554 * first tab, and kicks off a full rebind of body contents. 555 */ updateTabs()556 private void updateTabs() { 557 final Context context = getActivity(); 558 mTabHost.clearAllTabs(); 559 560 final boolean mobileSplit = isMobilePolicySplit(); 561 if (mobileSplit && hasMobile4gRadio(context)) { 562 mTabHost.addTab(buildTabSpec(TAB_3G, R.string.data_usage_tab_3g)); 563 mTabHost.addTab(buildTabSpec(TAB_4G, R.string.data_usage_tab_4g)); 564 } else if (hasMobileRadio(context)) { 565 mTabHost.addTab(buildTabSpec(TAB_MOBILE, R.string.data_usage_tab_mobile)); 566 } 567 if (mShowWifi && hasWifiRadio(context)) { 568 mTabHost.addTab(buildTabSpec(TAB_WIFI, R.string.data_usage_tab_wifi)); 569 } 570 if (mShowEthernet && hasEthernet(context)) { 571 mTabHost.addTab(buildTabSpec(TAB_ETHERNET, R.string.data_usage_tab_ethernet)); 572 } 573 574 final boolean multipleTabs = mTabWidget.getTabCount() > 1; 575 mTabWidget.setVisibility(multipleTabs ? View.VISIBLE : View.GONE); 576 if (mIntentTab != null) { 577 if (Objects.equal(mIntentTab, mTabHost.getCurrentTabTag())) { 578 // already hit updateBody() when added; ignore 579 updateBody(); 580 } else { 581 mTabHost.setCurrentTabByTag(mIntentTab); 582 } 583 mIntentTab = null; 584 } else { 585 // already hit updateBody() when added; ignore 586 } 587 } 588 589 /** 590 * Factory that provide empty {@link View} to make {@link TabHost} happy. 591 */ 592 private TabContentFactory mEmptyTabContent = new TabContentFactory() { 593 /** {@inheritDoc} */ 594 public View createTabContent(String tag) { 595 return new View(mTabHost.getContext()); 596 } 597 }; 598 599 /** 600 * Build {@link TabSpec} with thin indicator, and empty content. 601 */ buildTabSpec(String tag, int titleRes)602 private TabSpec buildTabSpec(String tag, int titleRes) { 603 return mTabHost.newTabSpec(tag).setIndicator(getText(titleRes)).setContent( 604 mEmptyTabContent); 605 } 606 607 private OnTabChangeListener mTabListener = new OnTabChangeListener() { 608 /** {@inheritDoc} */ 609 public void onTabChanged(String tabId) { 610 // user changed tab; update body 611 updateBody(); 612 } 613 }; 614 615 /** 616 * Update body content based on current tab. Loads 617 * {@link NetworkStatsHistory} and {@link NetworkPolicy} from system, and 618 * binds them to visible controls. 619 */ updateBody()620 private void updateBody() { 621 mBinding = true; 622 if (!isAdded()) return; 623 624 final Context context = getActivity(); 625 final String currentTab = mTabHost.getCurrentTabTag(); 626 627 if (currentTab == null) { 628 Log.w(TAG, "no tab selected; hiding body"); 629 mListView.setVisibility(View.GONE); 630 return; 631 } else { 632 mListView.setVisibility(View.VISIBLE); 633 } 634 635 final boolean tabChanged = !currentTab.equals(mCurrentTab); 636 mCurrentTab = currentTab; 637 638 if (LOGD) Log.d(TAG, "updateBody() with currentTab=" + currentTab); 639 640 mDataEnabledView.setVisibility(View.VISIBLE); 641 642 if (TAB_MOBILE.equals(currentTab)) { 643 setPreferenceTitle(mDataEnabledView, R.string.data_usage_enable_mobile); 644 setPreferenceTitle(mDisableAtLimitView, R.string.data_usage_disable_mobile_limit); 645 mTemplate = buildTemplateMobileAll(getActiveSubscriberId(context)); 646 647 } else if (TAB_3G.equals(currentTab)) { 648 setPreferenceTitle(mDataEnabledView, R.string.data_usage_enable_3g); 649 setPreferenceTitle(mDisableAtLimitView, R.string.data_usage_disable_3g_limit); 650 // TODO: bind mDataEnabled to 3G radio state 651 mTemplate = buildTemplateMobile3gLower(getActiveSubscriberId(context)); 652 653 } else if (TAB_4G.equals(currentTab)) { 654 setPreferenceTitle(mDataEnabledView, R.string.data_usage_enable_4g); 655 setPreferenceTitle(mDisableAtLimitView, R.string.data_usage_disable_4g_limit); 656 // TODO: bind mDataEnabled to 4G radio state 657 mTemplate = buildTemplateMobile4g(getActiveSubscriberId(context)); 658 659 } else if (TAB_WIFI.equals(currentTab)) { 660 // wifi doesn't have any controls 661 mDataEnabledView.setVisibility(View.GONE); 662 mDisableAtLimitView.setVisibility(View.GONE); 663 mTemplate = buildTemplateWifi(); 664 665 } else if (TAB_ETHERNET.equals(currentTab)) { 666 // ethernet doesn't have any controls 667 mDataEnabledView.setVisibility(View.GONE); 668 mDisableAtLimitView.setVisibility(View.GONE); 669 mTemplate = buildTemplateEthernet(); 670 671 } else { 672 throw new IllegalStateException("unknown tab: " + currentTab); 673 } 674 675 // kick off loader for network history 676 // TODO: consider chaining two loaders together instead of reloading 677 // network history when showing app detail. 678 getLoaderManager().restartLoader(LOADER_CHART_DATA, 679 ChartDataLoader.buildArgs(mTemplate, mAppDetailUids), mChartDataCallbacks); 680 681 // detail mode can change visible menus, invalidate 682 getActivity().invalidateOptionsMenu(); 683 684 mBinding = false; 685 } 686 isAppDetailMode()687 private boolean isAppDetailMode() { 688 return mAppDetailUids != null; 689 } 690 getAppDetailPrimaryUid()691 private int getAppDetailPrimaryUid() { 692 return mAppDetailUids[0]; 693 } 694 695 /** 696 * Update UID details panels to match {@link #mAppDetailUids}, showing or 697 * hiding them depending on {@link #isAppDetailMode()}. 698 */ updateAppDetail()699 private void updateAppDetail() { 700 final Context context = getActivity(); 701 final PackageManager pm = context.getPackageManager(); 702 final LayoutInflater inflater = getActivity().getLayoutInflater(); 703 704 if (isAppDetailMode()) { 705 mAppDetail.setVisibility(View.VISIBLE); 706 mCycleAdapter.setChangeVisible(false); 707 } else { 708 mAppDetail.setVisibility(View.GONE); 709 mCycleAdapter.setChangeVisible(true); 710 711 // hide detail stats when not in detail mode 712 mChart.bindDetailNetworkStats(null); 713 return; 714 } 715 716 // remove warning/limit sweeps while in detail mode 717 mChart.bindNetworkPolicy(null); 718 719 // show icon and all labels appearing under this app 720 final int primaryUid = getAppDetailPrimaryUid(); 721 final UidDetail detail = mUidDetailProvider.getUidDetail(primaryUid, true); 722 mAppIcon.setImageDrawable(detail.icon); 723 724 mAppTitles.removeAllViews(); 725 if (detail.detailLabels != null) { 726 for (CharSequence label : detail.detailLabels) { 727 mAppTitles.addView(inflateAppTitle(inflater, mAppTitles, label)); 728 } 729 } else { 730 mAppTitles.addView(inflateAppTitle(inflater, mAppTitles, detail.label)); 731 } 732 733 // enable settings button when package provides it 734 // TODO: target torwards entire UID instead of just first package 735 final String[] packageNames = pm.getPackagesForUid(primaryUid); 736 if (packageNames != null && packageNames.length > 0) { 737 mAppSettingsIntent = new Intent(Intent.ACTION_MANAGE_NETWORK_USAGE); 738 mAppSettingsIntent.setPackage(packageNames[0]); 739 mAppSettingsIntent.addCategory(Intent.CATEGORY_DEFAULT); 740 741 final boolean matchFound = pm.resolveActivity(mAppSettingsIntent, 0) != null; 742 mAppSettings.setEnabled(matchFound); 743 744 } else { 745 mAppSettingsIntent = null; 746 mAppSettings.setEnabled(false); 747 } 748 749 updateDetailData(); 750 751 if (NetworkPolicyManager.isUidValidForPolicy(context, primaryUid) 752 && !getRestrictBackground() && isBandwidthControlEnabled()) { 753 setPreferenceTitle(mAppRestrictView, R.string.data_usage_app_restrict_background); 754 if (hasLimitedNetworks()) { 755 setPreferenceSummary(mAppRestrictView, 756 getString(R.string.data_usage_app_restrict_background_summary)); 757 } else { 758 setPreferenceSummary(mAppRestrictView, 759 getString(R.string.data_usage_app_restrict_background_summary_disabled)); 760 } 761 762 mAppRestrictView.setVisibility(View.VISIBLE); 763 mAppRestrict.setChecked(getAppRestrictBackground()); 764 765 } else { 766 mAppRestrictView.setVisibility(View.GONE); 767 } 768 } 769 setPolicyWarningBytes(long warningBytes)770 private void setPolicyWarningBytes(long warningBytes) { 771 if (LOGD) Log.d(TAG, "setPolicyWarningBytes()"); 772 mPolicyEditor.setPolicyWarningBytes(mTemplate, warningBytes); 773 updatePolicy(false); 774 } 775 setPolicyLimitBytes(long limitBytes)776 private void setPolicyLimitBytes(long limitBytes) { 777 if (LOGD) Log.d(TAG, "setPolicyLimitBytes()"); 778 mPolicyEditor.setPolicyLimitBytes(mTemplate, limitBytes); 779 updatePolicy(false); 780 } 781 782 /** 783 * Local cache of value, used to work around delay when 784 * {@link ConnectivityManager#setMobileDataEnabled(boolean)} is async. 785 */ 786 private Boolean mMobileDataEnabled; 787 isMobileDataEnabled()788 private boolean isMobileDataEnabled() { 789 if (mMobileDataEnabled != null) { 790 // TODO: deprecate and remove this once enabled flag is on policy 791 return mMobileDataEnabled; 792 } else { 793 return mConnService.getMobileDataEnabled(); 794 } 795 } 796 setMobileDataEnabled(boolean enabled)797 private void setMobileDataEnabled(boolean enabled) { 798 if (LOGD) Log.d(TAG, "setMobileDataEnabled()"); 799 mConnService.setMobileDataEnabled(enabled); 800 mMobileDataEnabled = enabled; 801 updatePolicy(false); 802 } 803 isNetworkPolicyModifiable(NetworkPolicy policy)804 private boolean isNetworkPolicyModifiable(NetworkPolicy policy) { 805 return policy != null && isBandwidthControlEnabled() && mDataEnabled.isChecked(); 806 } 807 isBandwidthControlEnabled()808 private boolean isBandwidthControlEnabled() { 809 try { 810 return mNetworkService.isBandwidthControlEnabled(); 811 } catch (RemoteException e) { 812 Log.w(TAG, "problem talking with INetworkManagementService: " + e); 813 return false; 814 } 815 } 816 getDataRoaming()817 private boolean getDataRoaming() { 818 final ContentResolver resolver = getActivity().getContentResolver(); 819 return Settings.Secure.getInt(resolver, Settings.Secure.DATA_ROAMING, 0) != 0; 820 } 821 setDataRoaming(boolean enabled)822 private void setDataRoaming(boolean enabled) { 823 // TODO: teach telephony DataConnectionTracker to watch and apply 824 // updates when changed. 825 final ContentResolver resolver = getActivity().getContentResolver(); 826 Settings.Secure.putInt(resolver, Settings.Secure.DATA_ROAMING, enabled ? 1 : 0); 827 mMenuDataRoaming.setChecked(enabled); 828 } 829 getRestrictBackground()830 private boolean getRestrictBackground() { 831 try { 832 return mPolicyService.getRestrictBackground(); 833 } catch (RemoteException e) { 834 Log.w(TAG, "problem talking with policy service: " + e); 835 return false; 836 } 837 } 838 setRestrictBackground(boolean restrictBackground)839 private void setRestrictBackground(boolean restrictBackground) { 840 if (LOGD) Log.d(TAG, "setRestrictBackground()"); 841 try { 842 mPolicyService.setRestrictBackground(restrictBackground); 843 mMenuRestrictBackground.setChecked(restrictBackground); 844 } catch (RemoteException e) { 845 Log.w(TAG, "problem talking with policy service: " + e); 846 } 847 } 848 getAppRestrictBackground()849 private boolean getAppRestrictBackground() { 850 final int primaryUid = getAppDetailPrimaryUid(); 851 final int uidPolicy; 852 try { 853 uidPolicy = mPolicyService.getUidPolicy(primaryUid); 854 } catch (RemoteException e) { 855 // since we can't do much without policy, we bail hard. 856 throw new RuntimeException("problem reading network policy", e); 857 } 858 859 return (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0; 860 } 861 setAppRestrictBackground(boolean restrictBackground)862 private void setAppRestrictBackground(boolean restrictBackground) { 863 if (LOGD) Log.d(TAG, "setAppRestrictBackground()"); 864 final int primaryUid = getAppDetailPrimaryUid(); 865 try { 866 mPolicyService.setUidPolicy(primaryUid, 867 restrictBackground ? POLICY_REJECT_METERED_BACKGROUND : POLICY_NONE); 868 } catch (RemoteException e) { 869 throw new RuntimeException("unable to save policy", e); 870 } 871 872 mAppRestrict.setChecked(restrictBackground); 873 } 874 875 /** 876 * Update chart sweeps and cycle list to reflect {@link NetworkPolicy} for 877 * current {@link #mTemplate}. 878 */ updatePolicy(boolean refreshCycle)879 private void updatePolicy(boolean refreshCycle) { 880 if (isAppDetailMode()) { 881 mNetworkSwitches.setVisibility(View.GONE); 882 } else { 883 mNetworkSwitches.setVisibility(View.VISIBLE); 884 } 885 886 // TODO: move enabled state directly into policy 887 if (TAB_MOBILE.equals(mCurrentTab)) { 888 mBinding = true; 889 mDataEnabled.setChecked(isMobileDataEnabled()); 890 mBinding = false; 891 } 892 893 final NetworkPolicy policy = mPolicyEditor.getPolicy(mTemplate); 894 if (isNetworkPolicyModifiable(policy)) { 895 mDisableAtLimitView.setVisibility(View.VISIBLE); 896 mDisableAtLimit.setChecked(policy != null && policy.limitBytes != LIMIT_DISABLED); 897 if (!isAppDetailMode()) { 898 mChart.bindNetworkPolicy(policy); 899 } 900 901 } else { 902 // controls are disabled; don't bind warning/limit sweeps 903 mDisableAtLimitView.setVisibility(View.GONE); 904 mChart.bindNetworkPolicy(null); 905 } 906 907 if (refreshCycle) { 908 // generate cycle list based on policy and available history 909 updateCycleList(policy); 910 } 911 } 912 913 /** 914 * Rebuild {@link #mCycleAdapter} based on {@link NetworkPolicy#cycleDay} 915 * and available {@link NetworkStatsHistory} data. Always selects the newest 916 * item, updating the inspection range on {@link #mChart}. 917 */ updateCycleList(NetworkPolicy policy)918 private void updateCycleList(NetworkPolicy policy) { 919 // stash away currently selected cycle to try restoring below 920 final CycleItem previousItem = (CycleItem) mCycleSpinner.getSelectedItem(); 921 mCycleAdapter.clear(); 922 923 final Context context = mCycleSpinner.getContext(); 924 925 long historyStart = Long.MAX_VALUE; 926 long historyEnd = Long.MIN_VALUE; 927 if (mChartData != null) { 928 historyStart = mChartData.network.getStart(); 929 historyEnd = mChartData.network.getEnd(); 930 } 931 932 final long now = System.currentTimeMillis(); 933 if (historyStart == Long.MAX_VALUE) historyStart = now; 934 if (historyEnd == Long.MIN_VALUE) historyEnd = now + 1; 935 936 boolean hasCycles = false; 937 if (policy != null) { 938 // find the next cycle boundary 939 long cycleEnd = computeNextCycleBoundary(historyEnd, policy); 940 941 // walk backwards, generating all valid cycle ranges 942 while (cycleEnd > historyStart) { 943 final long cycleStart = computeLastCycleBoundary(cycleEnd, policy); 944 Log.d(TAG, "generating cs=" + cycleStart + " to ce=" + cycleEnd + " waiting for hs=" 945 + historyStart); 946 mCycleAdapter.add(new CycleItem(context, cycleStart, cycleEnd)); 947 cycleEnd = cycleStart; 948 hasCycles = true; 949 } 950 951 // one last cycle entry to modify policy cycle day 952 mCycleAdapter.setChangePossible(isNetworkPolicyModifiable(policy)); 953 } 954 955 if (!hasCycles) { 956 // no policy defined cycles; show entry for each four-week period 957 long cycleEnd = historyEnd; 958 while (cycleEnd > historyStart) { 959 final long cycleStart = cycleEnd - (DateUtils.WEEK_IN_MILLIS * 4); 960 mCycleAdapter.add(new CycleItem(context, cycleStart, cycleEnd)); 961 cycleEnd = cycleStart; 962 } 963 964 mCycleAdapter.setChangePossible(false); 965 } 966 967 // force pick the current cycle (first item) 968 if (mCycleAdapter.getCount() > 0) { 969 final int position = mCycleAdapter.findNearestPosition(previousItem); 970 mCycleSpinner.setSelection(position); 971 972 // only force-update cycle when changed; skipping preserves any 973 // user-defined inspection region. 974 final CycleItem selectedItem = mCycleAdapter.getItem(position); 975 if (!Objects.equal(selectedItem, previousItem)) { 976 mCycleListener.onItemSelected(mCycleSpinner, null, position, 0); 977 } else { 978 // but still kick off loader for detailed list 979 updateDetailData(); 980 } 981 } else { 982 updateDetailData(); 983 } 984 } 985 986 private OnCheckedChangeListener mDataEnabledListener = new OnCheckedChangeListener() { 987 /** {@inheritDoc} */ 988 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 989 if (mBinding) return; 990 991 final boolean dataEnabled = isChecked; 992 final String currentTab = mCurrentTab; 993 if (TAB_MOBILE.equals(currentTab)) { 994 if (dataEnabled) { 995 setMobileDataEnabled(true); 996 } else { 997 // disabling data; show confirmation dialog which eventually 998 // calls setMobileDataEnabled() once user confirms. 999 ConfirmDataDisableFragment.show(DataUsageSummary.this); 1000 } 1001 } 1002 1003 updatePolicy(false); 1004 } 1005 }; 1006 1007 private View.OnClickListener mDisableAtLimitListener = new View.OnClickListener() { 1008 /** {@inheritDoc} */ 1009 public void onClick(View v) { 1010 final boolean disableAtLimit = !mDisableAtLimit.isChecked(); 1011 if (disableAtLimit) { 1012 // enabling limit; show confirmation dialog which eventually 1013 // calls setPolicyLimitBytes() once user confirms. 1014 ConfirmLimitFragment.show(DataUsageSummary.this); 1015 } else { 1016 setPolicyLimitBytes(LIMIT_DISABLED); 1017 } 1018 } 1019 }; 1020 1021 private View.OnClickListener mAppRestrictListener = new View.OnClickListener() { 1022 /** {@inheritDoc} */ 1023 public void onClick(View v) { 1024 final boolean restrictBackground = !mAppRestrict.isChecked(); 1025 1026 if (restrictBackground) { 1027 if (hasLimitedNetworks()) { 1028 // enabling restriction; show confirmation dialog which 1029 // eventually calls setRestrictBackground() once user 1030 // confirms. 1031 ConfirmAppRestrictFragment.show(DataUsageSummary.this); 1032 } else { 1033 // no limited networks; show dialog to guide user towards 1034 // setting a network limit. doesn't mutate restrict state. 1035 DeniedRestrictFragment.show(DataUsageSummary.this); 1036 } 1037 } else { 1038 setAppRestrictBackground(false); 1039 } 1040 } 1041 }; 1042 1043 private OnClickListener mAppSettingsListener = new OnClickListener() { 1044 /** {@inheritDoc} */ 1045 public void onClick(View v) { 1046 // TODO: target torwards entire UID instead of just first package 1047 startActivity(mAppSettingsIntent); 1048 } 1049 }; 1050 1051 private OnItemClickListener mListListener = new OnItemClickListener() { 1052 /** {@inheritDoc} */ 1053 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 1054 final Context context = view.getContext(); 1055 final AppUsageItem app = (AppUsageItem) parent.getItemAtPosition(position); 1056 final UidDetail detail = mUidDetailProvider.getUidDetail(app.uids[0], true); 1057 AppDetailsFragment.show(DataUsageSummary.this, app.uids, detail.label); 1058 } 1059 }; 1060 1061 private OnItemSelectedListener mCycleListener = new OnItemSelectedListener() { 1062 /** {@inheritDoc} */ 1063 public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { 1064 final CycleItem cycle = (CycleItem) parent.getItemAtPosition(position); 1065 if (cycle instanceof CycleChangeItem) { 1066 // show cycle editor; will eventually call setPolicyCycleDay() 1067 // when user finishes editing. 1068 CycleEditorFragment.show(DataUsageSummary.this); 1069 1070 // reset spinner to something other than "change cycle..." 1071 mCycleSpinner.setSelection(0); 1072 1073 } else { 1074 if (LOGD) { 1075 Log.d(TAG, "showing cycle " + cycle + ", start=" + cycle.start + ", end=" 1076 + cycle.end + "]"); 1077 } 1078 1079 // update chart to show selected cycle, and update detail data 1080 // to match updated sweep bounds. 1081 mChart.setVisibleRange(cycle.start, cycle.end); 1082 1083 updateDetailData(); 1084 } 1085 } 1086 1087 /** {@inheritDoc} */ 1088 public void onNothingSelected(AdapterView<?> parent) { 1089 // ignored 1090 } 1091 }; 1092 1093 /** 1094 * Update details based on {@link #mChart} inspection range depending on 1095 * current mode. In network mode, updates {@link #mAdapter} with sorted list 1096 * of applications data usage, and when {@link #isAppDetailMode()} update 1097 * app details. 1098 */ updateDetailData()1099 private void updateDetailData() { 1100 if (LOGD) Log.d(TAG, "updateDetailData()"); 1101 1102 final long start = mChart.getInspectStart(); 1103 final long end = mChart.getInspectEnd(); 1104 final long now = System.currentTimeMillis(); 1105 1106 final Context context = getActivity(); 1107 1108 NetworkStatsHistory.Entry entry = null; 1109 if (isAppDetailMode() && mChartData != null && mChartData.detail != null) { 1110 // bind foreground/background to piechart and labels 1111 entry = mChartData.detailDefault.getValues(start, end, now, entry); 1112 final long defaultBytes = entry.rxBytes + entry.txBytes; 1113 entry = mChartData.detailForeground.getValues(start, end, now, entry); 1114 final long foregroundBytes = entry.rxBytes + entry.txBytes; 1115 1116 mAppPieChart.setOriginAngle(175); 1117 1118 mAppPieChart.removeAllSlices(); 1119 mAppPieChart.addSlice(foregroundBytes, Color.parseColor("#d88d3a")); 1120 mAppPieChart.addSlice(defaultBytes, Color.parseColor("#666666")); 1121 1122 mAppPieChart.generatePath(); 1123 1124 mAppBackground.setText(Formatter.formatFileSize(context, defaultBytes)); 1125 mAppForeground.setText(Formatter.formatFileSize(context, foregroundBytes)); 1126 1127 // and finally leave with summary data for label below 1128 entry = mChartData.detail.getValues(start, end, now, null); 1129 1130 getLoaderManager().destroyLoader(LOADER_SUMMARY); 1131 1132 } else { 1133 if (mChartData != null) { 1134 entry = mChartData.network.getValues(start, end, now, null); 1135 } 1136 1137 // kick off loader for detailed stats 1138 getLoaderManager().restartLoader(LOADER_SUMMARY, 1139 SummaryForAllUidLoader.buildArgs(mTemplate, start, end), mSummaryCallbacks); 1140 } 1141 1142 final long totalBytes = entry != null ? entry.rxBytes + entry.txBytes : 0; 1143 final String totalPhrase = Formatter.formatFileSize(context, totalBytes); 1144 final String rangePhrase = formatDateRange(context, start, end, false); 1145 1146 mUsageSummary.setText( 1147 getString(R.string.data_usage_total_during_range, totalPhrase, rangePhrase)); 1148 } 1149 1150 private final LoaderCallbacks<ChartData> mChartDataCallbacks = new LoaderCallbacks< 1151 ChartData>() { 1152 /** {@inheritDoc} */ 1153 public Loader<ChartData> onCreateLoader(int id, Bundle args) { 1154 return new ChartDataLoader(getActivity(), mStatsService, args); 1155 } 1156 1157 /** {@inheritDoc} */ 1158 public void onLoadFinished(Loader<ChartData> loader, ChartData data) { 1159 mChartData = data; 1160 mChart.bindNetworkStats(mChartData.network); 1161 mChart.bindDetailNetworkStats(mChartData.detail); 1162 1163 // calcuate policy cycles based on available data 1164 updatePolicy(true); 1165 updateAppDetail(); 1166 1167 // force scroll to top of body when showing detail 1168 if (mChartData.detail != null) { 1169 mListView.smoothScrollToPosition(0); 1170 } 1171 } 1172 1173 /** {@inheritDoc} */ 1174 public void onLoaderReset(Loader<ChartData> loader) { 1175 mChartData = null; 1176 mChart.bindNetworkStats(null); 1177 mChart.bindDetailNetworkStats(null); 1178 } 1179 }; 1180 1181 private final LoaderCallbacks<NetworkStats> mSummaryCallbacks = new LoaderCallbacks< 1182 NetworkStats>() { 1183 /** {@inheritDoc} */ 1184 public Loader<NetworkStats> onCreateLoader(int id, Bundle args) { 1185 return new SummaryForAllUidLoader(getActivity(), mStatsService, args); 1186 } 1187 1188 /** {@inheritDoc} */ 1189 public void onLoadFinished(Loader<NetworkStats> loader, NetworkStats data) { 1190 mAdapter.bindStats(data); 1191 updateEmptyVisible(); 1192 } 1193 1194 /** {@inheritDoc} */ 1195 public void onLoaderReset(Loader<NetworkStats> loader) { 1196 mAdapter.bindStats(null); 1197 updateEmptyVisible(); 1198 } 1199 1200 private void updateEmptyVisible() { 1201 final boolean isEmpty = mAdapter.isEmpty() && !isAppDetailMode(); 1202 mEmpty.setVisibility(isEmpty ? View.VISIBLE : View.GONE); 1203 } 1204 }; 1205 isMobilePolicySplit()1206 private boolean isMobilePolicySplit() { 1207 final Context context = getActivity(); 1208 if (hasMobileRadio(context)) { 1209 final String subscriberId = getActiveSubscriberId(context); 1210 return mPolicyEditor.isMobilePolicySplit(subscriberId); 1211 } else { 1212 return false; 1213 } 1214 } 1215 setMobilePolicySplit(boolean split)1216 private void setMobilePolicySplit(boolean split) { 1217 final String subscriberId = getActiveSubscriberId(getActivity()); 1218 mPolicyEditor.setMobilePolicySplit(subscriberId, split); 1219 } 1220 getActiveSubscriberId(Context context)1221 private static String getActiveSubscriberId(Context context) { 1222 final TelephonyManager telephony = (TelephonyManager) context.getSystemService( 1223 Context.TELEPHONY_SERVICE); 1224 return telephony.getSubscriberId(); 1225 } 1226 1227 private DataUsageChartListener mChartListener = new DataUsageChartListener() { 1228 /** {@inheritDoc} */ 1229 public void onInspectRangeChanged() { 1230 if (LOGD) Log.d(TAG, "onInspectRangeChanged()"); 1231 updateDetailData(); 1232 } 1233 1234 /** {@inheritDoc} */ 1235 public void onWarningChanged() { 1236 setPolicyWarningBytes(mChart.getWarningBytes()); 1237 } 1238 1239 /** {@inheritDoc} */ 1240 public void onLimitChanged() { 1241 setPolicyLimitBytes(mChart.getLimitBytes()); 1242 } 1243 1244 /** {@inheritDoc} */ 1245 public void requestWarningEdit() { 1246 WarningEditorFragment.show(DataUsageSummary.this); 1247 } 1248 1249 /** {@inheritDoc} */ 1250 public void requestLimitEdit() { 1251 LimitEditorFragment.show(DataUsageSummary.this); 1252 } 1253 }; 1254 1255 /** 1256 * List item that reflects a specific data usage cycle. 1257 */ 1258 public static class CycleItem implements Comparable<CycleItem> { 1259 public CharSequence label; 1260 public long start; 1261 public long end; 1262 CycleItem(CharSequence label)1263 CycleItem(CharSequence label) { 1264 this.label = label; 1265 } 1266 CycleItem(Context context, long start, long end)1267 public CycleItem(Context context, long start, long end) { 1268 this.label = formatDateRange(context, start, end, true); 1269 this.start = start; 1270 this.end = end; 1271 } 1272 1273 @Override toString()1274 public String toString() { 1275 return label.toString(); 1276 } 1277 1278 @Override equals(Object o)1279 public boolean equals(Object o) { 1280 if (o instanceof CycleItem) { 1281 final CycleItem another = (CycleItem) o; 1282 return start == another.start && end == another.end; 1283 } 1284 return false; 1285 } 1286 1287 /** {@inheritDoc} */ compareTo(CycleItem another)1288 public int compareTo(CycleItem another) { 1289 return Long.compare(start, another.start); 1290 } 1291 } 1292 1293 private static final StringBuilder sBuilder = new StringBuilder(50); 1294 private static final java.util.Formatter sFormatter = new java.util.Formatter( 1295 sBuilder, Locale.getDefault()); 1296 formatDateRange(Context context, long start, long end, boolean utcTime)1297 public static String formatDateRange(Context context, long start, long end, boolean utcTime) { 1298 final int flags = FORMAT_SHOW_DATE | FORMAT_ABBREV_MONTH; 1299 final String timezone = utcTime ? TIMEZONE_UTC : null; 1300 1301 synchronized (sBuilder) { 1302 sBuilder.setLength(0); 1303 return DateUtils 1304 .formatDateRange(context, sFormatter, start, end, flags, timezone).toString(); 1305 } 1306 } 1307 1308 /** 1309 * Special-case data usage cycle that triggers dialog to change 1310 * {@link NetworkPolicy#cycleDay}. 1311 */ 1312 public static class CycleChangeItem extends CycleItem { CycleChangeItem(Context context)1313 public CycleChangeItem(Context context) { 1314 super(context.getString(R.string.data_usage_change_cycle)); 1315 } 1316 } 1317 1318 public static class CycleAdapter extends ArrayAdapter<CycleItem> { 1319 private boolean mChangePossible = false; 1320 private boolean mChangeVisible = false; 1321 1322 private final CycleChangeItem mChangeItem; 1323 CycleAdapter(Context context)1324 public CycleAdapter(Context context) { 1325 super(context, android.R.layout.simple_spinner_item); 1326 setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 1327 mChangeItem = new CycleChangeItem(context); 1328 } 1329 setChangePossible(boolean possible)1330 public void setChangePossible(boolean possible) { 1331 mChangePossible = possible; 1332 updateChange(); 1333 } 1334 setChangeVisible(boolean visible)1335 public void setChangeVisible(boolean visible) { 1336 mChangeVisible = visible; 1337 updateChange(); 1338 } 1339 updateChange()1340 private void updateChange() { 1341 remove(mChangeItem); 1342 if (mChangePossible && mChangeVisible) { 1343 add(mChangeItem); 1344 } 1345 } 1346 1347 /** 1348 * Find position of {@link CycleItem} in this adapter which is nearest 1349 * the given {@link CycleItem}. 1350 */ findNearestPosition(CycleItem target)1351 public int findNearestPosition(CycleItem target) { 1352 if (target != null) { 1353 final int count = getCount(); 1354 for (int i = count - 1; i >= 0; i--) { 1355 final CycleItem item = getItem(i); 1356 if (item instanceof CycleChangeItem) { 1357 continue; 1358 } else if (item.compareTo(target) >= 0) { 1359 return i; 1360 } 1361 } 1362 } 1363 return 0; 1364 } 1365 } 1366 1367 private static class AppUsageItem implements Comparable<AppUsageItem> { 1368 public int[] uids; 1369 public long total; 1370 AppUsageItem(int uid)1371 public AppUsageItem(int uid) { 1372 uids = new int[] { uid }; 1373 } 1374 addUid(int uid)1375 public void addUid(int uid) { 1376 if (contains(uids, uid)) return; 1377 final int length = uids.length; 1378 uids = Arrays.copyOf(uids, length + 1); 1379 uids[length] = uid; 1380 } 1381 1382 /** {@inheritDoc} */ compareTo(AppUsageItem another)1383 public int compareTo(AppUsageItem another) { 1384 return Long.compare(another.total, total); 1385 } 1386 } 1387 1388 /** 1389 * Adapter of applications, sorted by total usage descending. 1390 */ 1391 public static class DataUsageAdapter extends BaseAdapter { 1392 private final UidDetailProvider mProvider; 1393 private final int mInsetSide; 1394 1395 private ArrayList<AppUsageItem> mItems = Lists.newArrayList(); 1396 private long mLargest; 1397 DataUsageAdapter(UidDetailProvider provider, int insetSide)1398 public DataUsageAdapter(UidDetailProvider provider, int insetSide) { 1399 mProvider = checkNotNull(provider); 1400 mInsetSide = insetSide; 1401 } 1402 1403 /** 1404 * Bind the given {@link NetworkStats}, or {@code null} to clear list. 1405 */ bindStats(NetworkStats stats)1406 public void bindStats(NetworkStats stats) { 1407 mItems.clear(); 1408 1409 final AppUsageItem systemItem = new AppUsageItem(android.os.Process.SYSTEM_UID); 1410 final SparseArray<AppUsageItem> knownUids = new SparseArray<AppUsageItem>(); 1411 1412 NetworkStats.Entry entry = null; 1413 final int size = stats != null ? stats.size() : 0; 1414 for (int i = 0; i < size; i++) { 1415 entry = stats.getValues(i, entry); 1416 1417 final int uid = entry.uid; 1418 final boolean isApp = uid >= android.os.Process.FIRST_APPLICATION_UID 1419 && uid <= android.os.Process.LAST_APPLICATION_UID; 1420 if (isApp || uid == UID_REMOVED || uid == UID_TETHERING) { 1421 AppUsageItem item = knownUids.get(uid); 1422 if (item == null) { 1423 item = new AppUsageItem(uid); 1424 knownUids.put(uid, item); 1425 mItems.add(item); 1426 } 1427 1428 item.total += entry.rxBytes + entry.txBytes; 1429 } else { 1430 systemItem.total += entry.rxBytes + entry.txBytes; 1431 systemItem.addUid(uid); 1432 } 1433 } 1434 1435 if (systemItem.total > 0) { 1436 mItems.add(systemItem); 1437 } 1438 1439 Collections.sort(mItems); 1440 mLargest = (mItems.size() > 0) ? mItems.get(0).total : 0; 1441 notifyDataSetChanged(); 1442 } 1443 1444 @Override getCount()1445 public int getCount() { 1446 return mItems.size(); 1447 } 1448 1449 @Override getItem(int position)1450 public Object getItem(int position) { 1451 return mItems.get(position); 1452 } 1453 1454 @Override getItemId(int position)1455 public long getItemId(int position) { 1456 return mItems.get(position).uids[0]; 1457 } 1458 1459 @Override getView(int position, View convertView, ViewGroup parent)1460 public View getView(int position, View convertView, ViewGroup parent) { 1461 if (convertView == null) { 1462 convertView = LayoutInflater.from(parent.getContext()).inflate( 1463 R.layout.data_usage_item, parent, false); 1464 1465 if (mInsetSide > 0) { 1466 convertView.setPadding(mInsetSide, 0, mInsetSide, 0); 1467 } 1468 } 1469 1470 final Context context = parent.getContext(); 1471 1472 final TextView text1 = (TextView) convertView.findViewById(android.R.id.text1); 1473 final ProgressBar progress = (ProgressBar) convertView.findViewById( 1474 android.R.id.progress); 1475 1476 // kick off async load of app details 1477 final AppUsageItem item = mItems.get(position); 1478 UidDetailTask.bindView(mProvider, item, convertView); 1479 1480 text1.setText(Formatter.formatFileSize(context, item.total)); 1481 1482 final int percentTotal = mLargest != 0 ? (int) (item.total * 100 / mLargest) : 0; 1483 progress.setProgress(percentTotal); 1484 1485 return convertView; 1486 } 1487 } 1488 1489 /** 1490 * Empty {@link Fragment} that controls display of UID details in 1491 * {@link DataUsageSummary}. 1492 */ 1493 public static class AppDetailsFragment extends Fragment { 1494 private static final String EXTRA_UIDS = "uids"; 1495 show(DataUsageSummary parent, int[] uids, CharSequence label)1496 public static void show(DataUsageSummary parent, int[] uids, CharSequence label) { 1497 if (!parent.isAdded()) return; 1498 1499 final Bundle args = new Bundle(); 1500 args.putIntArray(EXTRA_UIDS, uids); 1501 1502 final AppDetailsFragment fragment = new AppDetailsFragment(); 1503 fragment.setArguments(args); 1504 fragment.setTargetFragment(parent, 0); 1505 1506 final FragmentTransaction ft = parent.getFragmentManager().beginTransaction(); 1507 ft.add(fragment, TAG_APP_DETAILS); 1508 ft.addToBackStack(TAG_APP_DETAILS); 1509 ft.setBreadCrumbTitle(label); 1510 ft.commit(); 1511 } 1512 1513 @Override onStart()1514 public void onStart() { 1515 super.onStart(); 1516 final DataUsageSummary target = (DataUsageSummary) getTargetFragment(); 1517 target.mAppDetailUids = getArguments().getIntArray(EXTRA_UIDS); 1518 target.updateBody(); 1519 } 1520 1521 @Override onStop()1522 public void onStop() { 1523 super.onStop(); 1524 final DataUsageSummary target = (DataUsageSummary) getTargetFragment(); 1525 target.mAppDetailUids = null; 1526 target.updateBody(); 1527 } 1528 } 1529 1530 /** 1531 * Dialog to request user confirmation before setting 1532 * {@link NetworkPolicy#limitBytes}. 1533 */ 1534 public static class ConfirmLimitFragment extends DialogFragment { 1535 private static final String EXTRA_MESSAGE = "message"; 1536 private static final String EXTRA_LIMIT_BYTES = "limitBytes"; 1537 show(DataUsageSummary parent)1538 public static void show(DataUsageSummary parent) { 1539 if (!parent.isAdded()) return; 1540 1541 final Resources res = parent.getResources(); 1542 final CharSequence message; 1543 final long limitBytes; 1544 1545 // TODO: customize default limits based on network template 1546 final String currentTab = parent.mCurrentTab; 1547 if (TAB_3G.equals(currentTab)) { 1548 message = buildDialogMessage(res, R.string.data_usage_tab_3g); 1549 limitBytes = 5 * GB_IN_BYTES; 1550 } else if (TAB_4G.equals(currentTab)) { 1551 message = buildDialogMessage(res, R.string.data_usage_tab_4g); 1552 limitBytes = 5 * GB_IN_BYTES; 1553 } else if (TAB_MOBILE.equals(currentTab)) { 1554 message = buildDialogMessage(res, R.string.data_usage_list_mobile); 1555 limitBytes = 5 * GB_IN_BYTES; 1556 } else if (TAB_WIFI.equals(currentTab)) { 1557 message = buildDialogMessage(res, R.string.data_usage_tab_wifi); 1558 limitBytes = 5 * GB_IN_BYTES; 1559 } else { 1560 throw new IllegalArgumentException("unknown current tab: " + currentTab); 1561 } 1562 1563 final Bundle args = new Bundle(); 1564 args.putCharSequence(EXTRA_MESSAGE, message); 1565 args.putLong(EXTRA_LIMIT_BYTES, limitBytes); 1566 1567 final ConfirmLimitFragment dialog = new ConfirmLimitFragment(); 1568 dialog.setArguments(args); 1569 dialog.setTargetFragment(parent, 0); 1570 dialog.show(parent.getFragmentManager(), TAG_CONFIRM_LIMIT); 1571 } 1572 buildDialogMessage(Resources res, int networkResId)1573 private static CharSequence buildDialogMessage(Resources res, int networkResId) { 1574 return res.getString(R.string.data_usage_limit_dialog, res.getString(networkResId)); 1575 } 1576 1577 @Override onCreateDialog(Bundle savedInstanceState)1578 public Dialog onCreateDialog(Bundle savedInstanceState) { 1579 final Context context = getActivity(); 1580 1581 final CharSequence message = getArguments().getCharSequence(EXTRA_MESSAGE); 1582 final long limitBytes = getArguments().getLong(EXTRA_LIMIT_BYTES); 1583 1584 final AlertDialog.Builder builder = new AlertDialog.Builder(context); 1585 builder.setTitle(R.string.data_usage_limit_dialog_title); 1586 builder.setMessage(message); 1587 1588 builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 1589 public void onClick(DialogInterface dialog, int which) { 1590 final DataUsageSummary target = (DataUsageSummary) getTargetFragment(); 1591 if (target != null) { 1592 target.setPolicyLimitBytes(limitBytes); 1593 } 1594 } 1595 }); 1596 1597 return builder.create(); 1598 } 1599 } 1600 1601 /** 1602 * Dialog to edit {@link NetworkPolicy#cycleDay}. 1603 */ 1604 public static class CycleEditorFragment extends DialogFragment { 1605 private static final String EXTRA_TEMPLATE = "template"; 1606 show(DataUsageSummary parent)1607 public static void show(DataUsageSummary parent) { 1608 if (!parent.isAdded()) return; 1609 1610 final Bundle args = new Bundle(); 1611 args.putParcelable(EXTRA_TEMPLATE, parent.mTemplate); 1612 1613 final CycleEditorFragment dialog = new CycleEditorFragment(); 1614 dialog.setArguments(args); 1615 dialog.setTargetFragment(parent, 0); 1616 dialog.show(parent.getFragmentManager(), TAG_CYCLE_EDITOR); 1617 } 1618 1619 @Override onCreateDialog(Bundle savedInstanceState)1620 public Dialog onCreateDialog(Bundle savedInstanceState) { 1621 final Context context = getActivity(); 1622 final DataUsageSummary target = (DataUsageSummary) getTargetFragment(); 1623 final NetworkPolicyEditor editor = target.mPolicyEditor; 1624 1625 final AlertDialog.Builder builder = new AlertDialog.Builder(context); 1626 final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext()); 1627 1628 final View view = dialogInflater.inflate(R.layout.data_usage_cycle_editor, null, false); 1629 final NumberPicker cycleDayPicker = (NumberPicker) view.findViewById(R.id.cycle_day); 1630 1631 final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE); 1632 final int cycleDay = editor.getPolicyCycleDay(template); 1633 1634 cycleDayPicker.setMinValue(1); 1635 cycleDayPicker.setMaxValue(31); 1636 cycleDayPicker.setValue(cycleDay); 1637 cycleDayPicker.setWrapSelectorWheel(true); 1638 1639 builder.setTitle(R.string.data_usage_cycle_editor_title); 1640 builder.setView(view); 1641 1642 builder.setPositiveButton(R.string.data_usage_cycle_editor_positive, 1643 new DialogInterface.OnClickListener() { 1644 public void onClick(DialogInterface dialog, int which) { 1645 final int cycleDay = cycleDayPicker.getValue(); 1646 editor.setPolicyCycleDay(template, cycleDay); 1647 target.updatePolicy(true); 1648 } 1649 }); 1650 1651 return builder.create(); 1652 } 1653 } 1654 1655 /** 1656 * Dialog to edit {@link NetworkPolicy#warningBytes}. 1657 */ 1658 public static class WarningEditorFragment extends DialogFragment { 1659 private static final String EXTRA_TEMPLATE = "template"; 1660 show(DataUsageSummary parent)1661 public static void show(DataUsageSummary parent) { 1662 if (!parent.isAdded()) return; 1663 1664 final Bundle args = new Bundle(); 1665 args.putParcelable(EXTRA_TEMPLATE, parent.mTemplate); 1666 1667 final WarningEditorFragment dialog = new WarningEditorFragment(); 1668 dialog.setArguments(args); 1669 dialog.setTargetFragment(parent, 0); 1670 dialog.show(parent.getFragmentManager(), TAG_WARNING_EDITOR); 1671 } 1672 1673 @Override onCreateDialog(Bundle savedInstanceState)1674 public Dialog onCreateDialog(Bundle savedInstanceState) { 1675 final Context context = getActivity(); 1676 final DataUsageSummary target = (DataUsageSummary) getTargetFragment(); 1677 final NetworkPolicyEditor editor = target.mPolicyEditor; 1678 1679 final AlertDialog.Builder builder = new AlertDialog.Builder(context); 1680 final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext()); 1681 1682 final View view = dialogInflater.inflate(R.layout.data_usage_bytes_editor, null, false); 1683 final NumberPicker bytesPicker = (NumberPicker) view.findViewById(R.id.bytes); 1684 1685 final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE); 1686 final long warningBytes = editor.getPolicyWarningBytes(template); 1687 final long limitBytes = editor.getPolicyLimitBytes(template); 1688 1689 bytesPicker.setMinValue(0); 1690 if (limitBytes != LIMIT_DISABLED) { 1691 bytesPicker.setMaxValue((int) (limitBytes / MB_IN_BYTES) - 1); 1692 } else { 1693 bytesPicker.setMaxValue(Integer.MAX_VALUE); 1694 } 1695 bytesPicker.setValue((int) (warningBytes / MB_IN_BYTES)); 1696 bytesPicker.setWrapSelectorWheel(false); 1697 1698 builder.setTitle(R.string.data_usage_warning_editor_title); 1699 builder.setView(view); 1700 1701 builder.setPositiveButton(R.string.data_usage_cycle_editor_positive, 1702 new DialogInterface.OnClickListener() { 1703 public void onClick(DialogInterface dialog, int which) { 1704 // clear focus to finish pending text edits 1705 bytesPicker.clearFocus(); 1706 1707 final long bytes = bytesPicker.getValue() * MB_IN_BYTES; 1708 editor.setPolicyWarningBytes(template, bytes); 1709 target.updatePolicy(false); 1710 } 1711 }); 1712 1713 return builder.create(); 1714 } 1715 } 1716 1717 /** 1718 * Dialog to edit {@link NetworkPolicy#limitBytes}. 1719 */ 1720 public static class LimitEditorFragment extends DialogFragment { 1721 private static final String EXTRA_TEMPLATE = "template"; 1722 show(DataUsageSummary parent)1723 public static void show(DataUsageSummary parent) { 1724 if (!parent.isAdded()) return; 1725 1726 final Bundle args = new Bundle(); 1727 args.putParcelable(EXTRA_TEMPLATE, parent.mTemplate); 1728 1729 final LimitEditorFragment dialog = new LimitEditorFragment(); 1730 dialog.setArguments(args); 1731 dialog.setTargetFragment(parent, 0); 1732 dialog.show(parent.getFragmentManager(), TAG_LIMIT_EDITOR); 1733 } 1734 1735 @Override onCreateDialog(Bundle savedInstanceState)1736 public Dialog onCreateDialog(Bundle savedInstanceState) { 1737 final Context context = getActivity(); 1738 final DataUsageSummary target = (DataUsageSummary) getTargetFragment(); 1739 final NetworkPolicyEditor editor = target.mPolicyEditor; 1740 1741 final AlertDialog.Builder builder = new AlertDialog.Builder(context); 1742 final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext()); 1743 1744 final View view = dialogInflater.inflate(R.layout.data_usage_bytes_editor, null, false); 1745 final NumberPicker bytesPicker = (NumberPicker) view.findViewById(R.id.bytes); 1746 1747 final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE); 1748 final long warningBytes = editor.getPolicyWarningBytes(template); 1749 final long limitBytes = editor.getPolicyLimitBytes(template); 1750 1751 bytesPicker.setMaxValue(Integer.MAX_VALUE); 1752 if (warningBytes != WARNING_DISABLED) { 1753 bytesPicker.setMinValue((int) (warningBytes / MB_IN_BYTES) + 1); 1754 } else { 1755 bytesPicker.setMinValue(0); 1756 } 1757 bytesPicker.setValue((int) (limitBytes / MB_IN_BYTES)); 1758 bytesPicker.setWrapSelectorWheel(false); 1759 1760 builder.setTitle(R.string.data_usage_limit_editor_title); 1761 builder.setView(view); 1762 1763 builder.setPositiveButton(R.string.data_usage_cycle_editor_positive, 1764 new DialogInterface.OnClickListener() { 1765 public void onClick(DialogInterface dialog, int which) { 1766 // clear focus to finish pending text edits 1767 bytesPicker.clearFocus(); 1768 1769 final long bytes = bytesPicker.getValue() * MB_IN_BYTES; 1770 editor.setPolicyLimitBytes(template, bytes); 1771 target.updatePolicy(false); 1772 } 1773 }); 1774 1775 return builder.create(); 1776 } 1777 } 1778 /** 1779 * Dialog to request user confirmation before disabling data. 1780 */ 1781 public static class ConfirmDataDisableFragment extends DialogFragment { show(DataUsageSummary parent)1782 public static void show(DataUsageSummary parent) { 1783 if (!parent.isAdded()) return; 1784 1785 final ConfirmDataDisableFragment dialog = new ConfirmDataDisableFragment(); 1786 dialog.setTargetFragment(parent, 0); 1787 dialog.show(parent.getFragmentManager(), TAG_CONFIRM_DATA_DISABLE); 1788 } 1789 1790 @Override onCreateDialog(Bundle savedInstanceState)1791 public Dialog onCreateDialog(Bundle savedInstanceState) { 1792 final Context context = getActivity(); 1793 1794 final AlertDialog.Builder builder = new AlertDialog.Builder(context); 1795 builder.setMessage(R.string.data_usage_disable_mobile); 1796 1797 builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 1798 public void onClick(DialogInterface dialog, int which) { 1799 final DataUsageSummary target = (DataUsageSummary) getTargetFragment(); 1800 if (target != null) { 1801 // TODO: extend to modify policy enabled flag. 1802 target.setMobileDataEnabled(false); 1803 } 1804 } 1805 }); 1806 builder.setNegativeButton(android.R.string.cancel, null); 1807 1808 return builder.create(); 1809 } 1810 } 1811 1812 /** 1813 * Dialog to request user confirmation before setting 1814 * {@link Settings.Secure#DATA_ROAMING}. 1815 */ 1816 public static class ConfirmDataRoamingFragment extends DialogFragment { show(DataUsageSummary parent)1817 public static void show(DataUsageSummary parent) { 1818 if (!parent.isAdded()) return; 1819 1820 final ConfirmDataRoamingFragment dialog = new ConfirmDataRoamingFragment(); 1821 dialog.setTargetFragment(parent, 0); 1822 dialog.show(parent.getFragmentManager(), TAG_CONFIRM_DATA_ROAMING); 1823 } 1824 1825 @Override onCreateDialog(Bundle savedInstanceState)1826 public Dialog onCreateDialog(Bundle savedInstanceState) { 1827 final Context context = getActivity(); 1828 1829 final AlertDialog.Builder builder = new AlertDialog.Builder(context); 1830 builder.setTitle(R.string.roaming_reenable_title); 1831 builder.setMessage(R.string.roaming_warning); 1832 1833 builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 1834 public void onClick(DialogInterface dialog, int which) { 1835 final DataUsageSummary target = (DataUsageSummary) getTargetFragment(); 1836 if (target != null) { 1837 target.setDataRoaming(true); 1838 } 1839 } 1840 }); 1841 builder.setNegativeButton(android.R.string.cancel, null); 1842 1843 return builder.create(); 1844 } 1845 } 1846 1847 /** 1848 * Dialog to request user confirmation before setting 1849 * {@link INetworkPolicyManager#setRestrictBackground(boolean)}. 1850 */ 1851 public static class ConfirmRestrictFragment extends DialogFragment { show(DataUsageSummary parent)1852 public static void show(DataUsageSummary parent) { 1853 if (!parent.isAdded()) return; 1854 1855 final ConfirmRestrictFragment dialog = new ConfirmRestrictFragment(); 1856 dialog.setTargetFragment(parent, 0); 1857 dialog.show(parent.getFragmentManager(), TAG_CONFIRM_RESTRICT); 1858 } 1859 1860 @Override onCreateDialog(Bundle savedInstanceState)1861 public Dialog onCreateDialog(Bundle savedInstanceState) { 1862 final Context context = getActivity(); 1863 1864 final AlertDialog.Builder builder = new AlertDialog.Builder(context); 1865 builder.setTitle(R.string.data_usage_restrict_background_title); 1866 builder.setMessage(getString(R.string.data_usage_restrict_background)); 1867 1868 builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 1869 public void onClick(DialogInterface dialog, int which) { 1870 final DataUsageSummary target = (DataUsageSummary) getTargetFragment(); 1871 if (target != null) { 1872 target.setRestrictBackground(true); 1873 } 1874 } 1875 }); 1876 builder.setNegativeButton(android.R.string.cancel, null); 1877 1878 return builder.create(); 1879 } 1880 } 1881 1882 /** 1883 * Dialog to inform user that {@link #POLICY_REJECT_METERED_BACKGROUND} 1884 * change has been denied, usually based on 1885 * {@link DataUsageSummary#hasLimitedNetworks()}. 1886 */ 1887 public static class DeniedRestrictFragment extends DialogFragment { show(DataUsageSummary parent)1888 public static void show(DataUsageSummary parent) { 1889 if (!parent.isAdded()) return; 1890 1891 final DeniedRestrictFragment dialog = new DeniedRestrictFragment(); 1892 dialog.setTargetFragment(parent, 0); 1893 dialog.show(parent.getFragmentManager(), TAG_DENIED_RESTRICT); 1894 } 1895 1896 @Override onCreateDialog(Bundle savedInstanceState)1897 public Dialog onCreateDialog(Bundle savedInstanceState) { 1898 final Context context = getActivity(); 1899 1900 final AlertDialog.Builder builder = new AlertDialog.Builder(context); 1901 builder.setTitle(R.string.data_usage_app_restrict_background); 1902 builder.setMessage(R.string.data_usage_restrict_denied_dialog); 1903 builder.setPositiveButton(android.R.string.ok, null); 1904 1905 return builder.create(); 1906 } 1907 } 1908 1909 /** 1910 * Dialog to request user confirmation before setting 1911 * {@link #POLICY_REJECT_METERED_BACKGROUND}. 1912 */ 1913 public static class ConfirmAppRestrictFragment extends DialogFragment { show(DataUsageSummary parent)1914 public static void show(DataUsageSummary parent) { 1915 if (!parent.isAdded()) return; 1916 1917 final ConfirmAppRestrictFragment dialog = new ConfirmAppRestrictFragment(); 1918 dialog.setTargetFragment(parent, 0); 1919 dialog.show(parent.getFragmentManager(), TAG_CONFIRM_APP_RESTRICT); 1920 } 1921 1922 @Override onCreateDialog(Bundle savedInstanceState)1923 public Dialog onCreateDialog(Bundle savedInstanceState) { 1924 final Context context = getActivity(); 1925 1926 final AlertDialog.Builder builder = new AlertDialog.Builder(context); 1927 builder.setTitle(R.string.data_usage_app_restrict_dialog_title); 1928 builder.setMessage(R.string.data_usage_app_restrict_dialog); 1929 1930 builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 1931 public void onClick(DialogInterface dialog, int which) { 1932 final DataUsageSummary target = (DataUsageSummary) getTargetFragment(); 1933 if (target != null) { 1934 target.setAppRestrictBackground(true); 1935 } 1936 } 1937 }); 1938 builder.setNegativeButton(android.R.string.cancel, null); 1939 1940 return builder.create(); 1941 } 1942 } 1943 1944 /** 1945 * Compute default tab that should be selected, based on 1946 * {@link NetworkPolicyManager#EXTRA_NETWORK_TEMPLATE} extra. 1947 */ computeTabFromIntent(Intent intent)1948 private static String computeTabFromIntent(Intent intent) { 1949 final NetworkTemplate template = intent.getParcelableExtra(EXTRA_NETWORK_TEMPLATE); 1950 if (template == null) return null; 1951 1952 switch (template.getMatchRule()) { 1953 case MATCH_MOBILE_3G_LOWER: 1954 return TAB_3G; 1955 case MATCH_MOBILE_4G: 1956 return TAB_4G; 1957 case MATCH_MOBILE_ALL: 1958 return TAB_MOBILE; 1959 case MATCH_WIFI: 1960 return TAB_WIFI; 1961 default: 1962 return null; 1963 } 1964 } 1965 1966 /** 1967 * Background task that loads {@link UidDetail}, binding to 1968 * {@link DataUsageAdapter} row item when finished. 1969 */ 1970 private static class UidDetailTask extends AsyncTask<Void, Void, UidDetail> { 1971 private final UidDetailProvider mProvider; 1972 private final AppUsageItem mItem; 1973 private final View mTarget; 1974 UidDetailTask(UidDetailProvider provider, AppUsageItem item, View target)1975 private UidDetailTask(UidDetailProvider provider, AppUsageItem item, View target) { 1976 mProvider = checkNotNull(provider); 1977 mItem = checkNotNull(item); 1978 mTarget = checkNotNull(target); 1979 } 1980 bindView( UidDetailProvider provider, AppUsageItem item, View target)1981 public static void bindView( 1982 UidDetailProvider provider, AppUsageItem item, View target) { 1983 final UidDetailTask existing = (UidDetailTask) target.getTag(); 1984 if (existing != null) { 1985 existing.cancel(false); 1986 } 1987 1988 final UidDetail cachedDetail = provider.getUidDetail(item.uids[0], false); 1989 if (cachedDetail != null) { 1990 bindView(cachedDetail, target); 1991 } else { 1992 target.setTag(new UidDetailTask(provider, item, target).executeOnExecutor( 1993 AsyncTask.THREAD_POOL_EXECUTOR)); 1994 } 1995 } 1996 bindView(UidDetail detail, View target)1997 private static void bindView(UidDetail detail, View target) { 1998 final ImageView icon = (ImageView) target.findViewById(android.R.id.icon); 1999 final TextView title = (TextView) target.findViewById(android.R.id.title); 2000 2001 if (detail != null) { 2002 icon.setImageDrawable(detail.icon); 2003 title.setText(detail.label); 2004 } else { 2005 icon.setImageDrawable(null); 2006 title.setText(null); 2007 } 2008 } 2009 2010 @Override onPreExecute()2011 protected void onPreExecute() { 2012 bindView(null, mTarget); 2013 } 2014 2015 @Override doInBackground(Void... params)2016 protected UidDetail doInBackground(Void... params) { 2017 return mProvider.getUidDetail(mItem.uids[0], true); 2018 } 2019 2020 @Override onPostExecute(UidDetail result)2021 protected void onPostExecute(UidDetail result) { 2022 bindView(result, mTarget); 2023 } 2024 } 2025 2026 /** 2027 * Test if device has a mobile data radio. 2028 */ hasMobileRadio(Context context)2029 private static boolean hasMobileRadio(Context context) { 2030 if (TEST_RADIOS) { 2031 return SystemProperties.get(TEST_RADIOS_PROP).contains("mobile"); 2032 } 2033 2034 final ConnectivityManager conn = (ConnectivityManager) context.getSystemService( 2035 Context.CONNECTIVITY_SERVICE); 2036 2037 // mobile devices should have MOBILE network tracker regardless of 2038 // connection status. 2039 return conn.getNetworkInfo(TYPE_MOBILE) != null; 2040 } 2041 2042 /** 2043 * Test if device has a mobile 4G data radio. 2044 */ hasMobile4gRadio(Context context)2045 private static boolean hasMobile4gRadio(Context context) { 2046 if (!NetworkPolicyEditor.ENABLE_SPLIT_POLICIES) { 2047 return false; 2048 } 2049 if (TEST_RADIOS) { 2050 return SystemProperties.get(TEST_RADIOS_PROP).contains("4g"); 2051 } 2052 2053 final ConnectivityManager conn = (ConnectivityManager) context.getSystemService( 2054 Context.CONNECTIVITY_SERVICE); 2055 final TelephonyManager telephony = (TelephonyManager) context.getSystemService( 2056 Context.TELEPHONY_SERVICE); 2057 2058 // WiMAX devices should have WiMAX network tracker regardless of 2059 // connection status. 2060 final boolean hasWimax = conn.getNetworkInfo(TYPE_WIMAX) != null; 2061 final boolean hasLte = telephony.getLteOnCdmaMode() == Phone.LTE_ON_CDMA_TRUE; 2062 return hasWimax || hasLte; 2063 } 2064 2065 /** 2066 * Test if device has a Wi-Fi data radio. 2067 */ hasWifiRadio(Context context)2068 private static boolean hasWifiRadio(Context context) { 2069 if (TEST_RADIOS) { 2070 return SystemProperties.get(TEST_RADIOS_PROP).contains("wifi"); 2071 } 2072 2073 return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI); 2074 } 2075 2076 /** 2077 * Test if device has an ethernet network connection. 2078 */ hasEthernet(Context context)2079 private static boolean hasEthernet(Context context) { 2080 if (TEST_RADIOS) { 2081 return SystemProperties.get(TEST_RADIOS_PROP).contains("ethernet"); 2082 } 2083 2084 final ConnectivityManager conn = (ConnectivityManager) context.getSystemService( 2085 Context.CONNECTIVITY_SERVICE); 2086 return conn.getNetworkInfo(TYPE_ETHERNET) != null; 2087 } 2088 2089 /** 2090 * Inflate a {@link Preference} style layout, adding the given {@link View} 2091 * widget into {@link android.R.id#widget_frame}. 2092 */ inflatePreference(LayoutInflater inflater, ViewGroup root, View widget)2093 private static View inflatePreference(LayoutInflater inflater, ViewGroup root, View widget) { 2094 final View view = inflater.inflate(R.layout.preference, root, false); 2095 final LinearLayout widgetFrame = (LinearLayout) view.findViewById( 2096 android.R.id.widget_frame); 2097 widgetFrame.addView(widget, new LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); 2098 return view; 2099 } 2100 inflateAppTitle( LayoutInflater inflater, ViewGroup root, CharSequence label)2101 private static View inflateAppTitle( 2102 LayoutInflater inflater, ViewGroup root, CharSequence label) { 2103 final TextView view = (TextView) inflater.inflate( 2104 R.layout.data_usage_app_title, root, false); 2105 view.setText(label); 2106 return view; 2107 } 2108 2109 /** 2110 * Test if any networks are currently limited. 2111 */ hasLimitedNetworks()2112 private boolean hasLimitedNetworks() { 2113 return !buildLimitedNetworksList().isEmpty(); 2114 } 2115 2116 /** 2117 * Build string describing currently limited networks, which defines when 2118 * background data is restricted. 2119 */ buildLimitedNetworksString()2120 private CharSequence buildLimitedNetworksString() { 2121 final List<CharSequence> limited = buildLimitedNetworksList(); 2122 2123 // handle case where no networks limited 2124 if (limited.isEmpty()) { 2125 limited.add(getText(R.string.data_usage_list_none)); 2126 } 2127 2128 return TextUtils.join(limited); 2129 } 2130 2131 /** 2132 * Build list of currently limited networks, which defines when background 2133 * data is restricted. 2134 */ buildLimitedNetworksList()2135 private List<CharSequence> buildLimitedNetworksList() { 2136 final Context context = getActivity(); 2137 final String subscriberId = getActiveSubscriberId(context); 2138 2139 // build combined list of all limited networks 2140 final ArrayList<CharSequence> limited = Lists.newArrayList(); 2141 if (mPolicyEditor.hasLimitedPolicy(buildTemplateMobileAll(subscriberId))) { 2142 limited.add(getText(R.string.data_usage_list_mobile)); 2143 } 2144 if (mPolicyEditor.hasLimitedPolicy(buildTemplateMobile3gLower(subscriberId))) { 2145 limited.add(getText(R.string.data_usage_tab_3g)); 2146 } 2147 if (mPolicyEditor.hasLimitedPolicy(buildTemplateMobile4g(subscriberId))) { 2148 limited.add(getText(R.string.data_usage_tab_4g)); 2149 } 2150 if (mPolicyEditor.hasLimitedPolicy(buildTemplateWifi())) { 2151 limited.add(getText(R.string.data_usage_tab_wifi)); 2152 } 2153 if (mPolicyEditor.hasLimitedPolicy(buildTemplateEthernet())) { 2154 limited.add(getText(R.string.data_usage_tab_ethernet)); 2155 } 2156 2157 return limited; 2158 } 2159 2160 /** 2161 * Inset both selector and divider {@link Drawable} on the given 2162 * {@link ListView} by the requested dimensions. 2163 */ insetListViewDrawables(ListView view, int insetSide)2164 private static void insetListViewDrawables(ListView view, int insetSide) { 2165 final Drawable selector = view.getSelector(); 2166 final Drawable divider = view.getDivider(); 2167 2168 // fully unregister these drawables so callbacks can be maintained after 2169 // wrapping below. 2170 final Drawable stub = new ColorDrawable(Color.TRANSPARENT); 2171 view.setSelector(stub); 2172 view.setDivider(stub); 2173 2174 view.setSelector(new InsetBoundsDrawable(selector, insetSide)); 2175 view.setDivider(new InsetBoundsDrawable(divider, insetSide)); 2176 } 2177 2178 /** 2179 * Set {@link android.R.id#title} for a preference view inflated with 2180 * {@link #inflatePreference(LayoutInflater, ViewGroup, View)}. 2181 */ setPreferenceTitle(View parent, int resId)2182 private static void setPreferenceTitle(View parent, int resId) { 2183 final TextView title = (TextView) parent.findViewById(android.R.id.title); 2184 title.setText(resId); 2185 } 2186 2187 /** 2188 * Set {@link android.R.id#summary} for a preference view inflated with 2189 * {@link #inflatePreference(LayoutInflater, ViewGroup, View)}. 2190 */ setPreferenceSummary(View parent, CharSequence string)2191 private static void setPreferenceSummary(View parent, CharSequence string) { 2192 final TextView summary = (TextView) parent.findViewById(android.R.id.summary); 2193 summary.setVisibility(View.VISIBLE); 2194 summary.setText(string); 2195 } 2196 contains(int[] haystack, int needle)2197 private static boolean contains(int[] haystack, int needle) { 2198 for (int value : haystack) { 2199 if (value == needle) { 2200 return true; 2201 } 2202 } 2203 return false; 2204 } 2205 } 2206