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