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