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