1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings; 18 19 import static android.net.ConnectivityManager.TYPE_ETHERNET; 20 import static android.net.ConnectivityManager.TYPE_MOBILE; 21 import static android.net.ConnectivityManager.TYPE_WIFI; 22 import static android.net.ConnectivityManager.TYPE_WIMAX; 23 import static android.net.NetworkPolicy.LIMIT_DISABLED; 24 import static android.net.NetworkPolicy.WARNING_DISABLED; 25 import static android.net.NetworkPolicyManager.EXTRA_NETWORK_TEMPLATE; 26 import static android.net.NetworkPolicyManager.POLICY_NONE; 27 import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND; 28 import static android.net.NetworkPolicyManager.computeLastCycleBoundary; 29 import static android.net.NetworkPolicyManager.computeNextCycleBoundary; 30 import static android.net.NetworkTemplate.MATCH_MOBILE_3G_LOWER; 31 import static android.net.NetworkTemplate.MATCH_MOBILE_4G; 32 import static android.net.NetworkTemplate.MATCH_MOBILE_ALL; 33 import static android.net.NetworkTemplate.MATCH_WIFI; 34 import static android.net.NetworkTemplate.buildTemplateEthernet; 35 import static android.net.NetworkTemplate.buildTemplateMobile3gLower; 36 import static android.net.NetworkTemplate.buildTemplateMobile4g; 37 import static android.net.NetworkTemplate.buildTemplateMobileAll; 38 import static android.net.NetworkTemplate.buildTemplateWifiWildcard; 39 import static android.net.TrafficStats.GB_IN_BYTES; 40 import static android.net.TrafficStats.MB_IN_BYTES; 41 import static android.net.TrafficStats.UID_REMOVED; 42 import static android.net.TrafficStats.UID_TETHERING; 43 import static android.telephony.TelephonyManager.SIM_STATE_READY; 44 import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH; 45 import static android.text.format.DateUtils.FORMAT_SHOW_DATE; 46 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; 47 import static com.android.internal.util.Preconditions.checkNotNull; 48 import static com.android.settings.Utils.prepareCustomPreferencesList; 49 50 import android.animation.LayoutTransition; 51 import android.app.ActivityManager; 52 import android.app.AlertDialog; 53 import android.app.Dialog; 54 import android.app.DialogFragment; 55 import android.app.Fragment; 56 import android.app.FragmentTransaction; 57 import android.app.LoaderManager.LoaderCallbacks; 58 import android.content.ComponentName; 59 import android.content.Context; 60 import android.content.DialogInterface; 61 import android.content.Intent; 62 import android.content.Loader; 63 import android.content.SharedPreferences; 64 import android.content.pm.PackageManager; 65 import android.content.pm.UserInfo; 66 import android.content.res.Resources; 67 import android.content.res.TypedArray; 68 import android.graphics.Color; 69 import android.graphics.drawable.ColorDrawable; 70 import android.graphics.drawable.Drawable; 71 import android.net.ConnectivityManager; 72 import android.net.INetworkPolicyManager; 73 import android.net.INetworkStatsService; 74 import android.net.INetworkStatsSession; 75 import android.net.NetworkPolicy; 76 import android.net.NetworkPolicyManager; 77 import android.net.NetworkStats; 78 import android.net.NetworkStatsHistory; 79 import android.net.NetworkTemplate; 80 import android.net.TrafficStats; 81 import android.os.AsyncTask; 82 import android.os.Bundle; 83 import android.os.INetworkManagementService; 84 import android.os.Parcel; 85 import android.os.Parcelable; 86 import android.os.RemoteException; 87 import android.os.ServiceManager; 88 import android.os.SystemProperties; 89 import android.os.UserHandle; 90 import android.os.UserManager; 91 import android.preference.Preference; 92 import android.telephony.SubscriptionInfo; 93 import android.telephony.SubscriptionManager; 94 import android.telephony.TelephonyManager; 95 import android.text.TextUtils; 96 import android.text.format.DateUtils; 97 import android.text.format.Formatter; 98 import android.text.format.Time; 99 import android.util.Log; 100 import android.util.SparseArray; 101 import android.util.SparseBooleanArray; 102 import android.view.LayoutInflater; 103 import android.view.Menu; 104 import android.view.MenuInflater; 105 import android.view.MenuItem; 106 import android.view.View; 107 import android.view.View.OnClickListener; 108 import android.view.ViewGroup; 109 import android.widget.AdapterView; 110 import android.widget.AdapterView.OnItemClickListener; 111 import android.widget.AdapterView.OnItemSelectedListener; 112 import android.widget.ArrayAdapter; 113 import android.widget.BaseAdapter; 114 import android.widget.Button; 115 import android.widget.ImageView; 116 import android.widget.LinearLayout; 117 import android.widget.ListView; 118 import android.widget.NumberPicker; 119 import android.widget.ProgressBar; 120 import android.widget.Spinner; 121 import android.widget.Switch; 122 import android.widget.TabHost; 123 import android.widget.TabHost.OnTabChangeListener; 124 import android.widget.TabHost.TabContentFactory; 125 import android.widget.TabHost.TabSpec; 126 import android.widget.TabWidget; 127 import android.widget.TextView; 128 129 import com.android.internal.telephony.PhoneConstants; 130 import com.android.settings.drawable.InsetBoundsDrawable; 131 import com.android.settings.net.ChartData; 132 import com.android.settings.net.ChartDataLoader; 133 import com.android.settings.net.DataUsageMeteredSettings; 134 import com.android.settings.net.NetworkPolicyEditor; 135 import com.android.settings.net.SummaryForAllUidLoader; 136 import com.android.settings.net.UidDetail; 137 import com.android.settings.net.UidDetailProvider; 138 import com.android.settings.search.BaseSearchIndexProvider; 139 import com.android.settings.search.Indexable; 140 import com.android.settings.search.SearchIndexableRaw; 141 import com.android.settings.sim.SimSettings; 142 import com.android.settings.widget.ChartDataUsageView; 143 import com.android.settings.widget.ChartDataUsageView.DataUsageChartListener; 144 import com.android.settings.widget.ChartNetworkSeriesView; 145 146 import com.google.android.collect.Lists; 147 148 import libcore.util.Objects; 149 150 import java.util.ArrayList; 151 import java.util.Collections; 152 import java.util.HashMap; 153 import java.util.List; 154 import java.util.Locale; 155 import java.util.Map; 156 import java.util.Set; 157 158 /** 159 * Panel showing data usage history across various networks, including options 160 * to inspect based on usage cycle and control through {@link NetworkPolicy}. 161 */ 162 public class DataUsageSummary extends HighlightingFragment implements Indexable { 163 private static final String TAG = "DataUsage"; 164 private static final boolean LOGD = false; 165 166 // TODO: remove this testing code 167 private static final boolean TEST_ANIM = false; 168 private static final boolean TEST_RADIOS = false; 169 170 private static final String TEST_RADIOS_PROP = "test.radios"; 171 private static final String TEST_SUBSCRIBER_PROP = "test.subscriberid"; 172 173 private static final String TAB_3G = "3g"; 174 private static final String TAB_4G = "4g"; 175 private static final String TAB_MOBILE = "mobile"; 176 private static final String TAB_WIFI = "wifi"; 177 private static final String TAB_ETHERNET = "ethernet"; 178 179 private static final String TAG_CONFIRM_DATA_DISABLE = "confirmDataDisable"; 180 private static final String TAG_CONFIRM_LIMIT = "confirmLimit"; 181 private static final String TAG_CYCLE_EDITOR = "cycleEditor"; 182 private static final String TAG_WARNING_EDITOR = "warningEditor"; 183 private static final String TAG_LIMIT_EDITOR = "limitEditor"; 184 private static final String TAG_CONFIRM_RESTRICT = "confirmRestrict"; 185 private static final String TAG_DENIED_RESTRICT = "deniedRestrict"; 186 private static final String TAG_CONFIRM_APP_RESTRICT = "confirmAppRestrict"; 187 private static final String TAG_APP_DETAILS = "appDetails"; 188 189 private static final String DATA_USAGE_ENABLE_MOBILE_KEY = "data_usage_enable_mobile"; 190 private static final String DATA_USAGE_DISABLE_MOBILE_LIMIT_KEY = 191 "data_usage_disable_mobile_limit"; 192 private static final String DATA_USAGE_CYCLE_KEY = "data_usage_cycle"; 193 194 private static final int LOADER_CHART_DATA = 2; 195 private static final int LOADER_SUMMARY = 3; 196 197 private INetworkManagementService mNetworkService; 198 private INetworkStatsService mStatsService; 199 private NetworkPolicyManager mPolicyManager; 200 private TelephonyManager mTelephonyManager; 201 private SubscriptionManager mSubscriptionManager; 202 203 private INetworkStatsSession mStatsSession; 204 205 private static final String PREF_FILE = "data_usage"; 206 private static final String PREF_SHOW_WIFI = "show_wifi"; 207 private static final String PREF_SHOW_ETHERNET = "show_ethernet"; 208 209 private SharedPreferences mPrefs; 210 211 private TabHost mTabHost; 212 private ViewGroup mTabsContainer; 213 private TabWidget mTabWidget; 214 private ListView mListView; 215 private ChartNetworkSeriesView mSeries; 216 private ChartNetworkSeriesView mDetailedSeries; 217 private DataUsageAdapter mAdapter; 218 219 /** Distance to inset content from sides, when needed. */ 220 private int mInsetSide = 0; 221 222 private ViewGroup mHeader; 223 224 private ViewGroup mNetworkSwitchesContainer; 225 private LinearLayout mNetworkSwitches; 226 private boolean mDataEnabledSupported; 227 private Switch mDataEnabled; 228 private View mDataEnabledView; 229 private boolean mDisableAtLimitSupported; 230 private Switch mDisableAtLimit; 231 private View mDisableAtLimitView; 232 233 private View mCycleView; 234 private Spinner mCycleSpinner; 235 private CycleAdapter mCycleAdapter; 236 private TextView mCycleSummary; 237 238 private ChartDataUsageView mChart; 239 private View mDisclaimer; 240 private TextView mEmpty; 241 private View mStupidPadding; 242 243 private View mAppDetail; 244 private ImageView mAppIcon; 245 private ViewGroup mAppTitles; 246 private TextView mAppTotal; 247 private TextView mAppForeground; 248 private TextView mAppBackground; 249 private Button mAppSettings; 250 251 private LinearLayout mAppSwitches; 252 private Switch mAppRestrict; 253 private View mAppRestrictView; 254 255 private boolean mShowWifi = false; 256 private boolean mShowEthernet = false; 257 258 private NetworkTemplate mTemplate; 259 private ChartData mChartData; 260 261 private AppItem mCurrentApp = null; 262 263 private Intent mAppSettingsIntent; 264 265 private NetworkPolicyEditor mPolicyEditor; 266 267 private String mCurrentTab = null; 268 private String mIntentTab = null; 269 270 private MenuItem mMenuRestrictBackground; 271 private MenuItem mMenuShowWifi; 272 private MenuItem mMenuShowEthernet; 273 private MenuItem mMenuSimCards; 274 private MenuItem mMenuCellularNetworks; 275 276 private List<SubscriptionInfo> mSubInfoList; 277 private Map<Integer,String> mMobileTagMap; 278 279 /** Flag used to ignore listeners during binding. */ 280 private boolean mBinding; 281 282 private UidDetailProvider mUidDetailProvider; 283 284 /** 285 * Local cache of data enabled for subId, used to work around delays. 286 */ 287 private final Map<String, Boolean> mMobileDataEnabled = new HashMap<String, Boolean>(); 288 289 @Override onCreate(Bundle savedInstanceState)290 public void onCreate(Bundle savedInstanceState) { 291 super.onCreate(savedInstanceState); 292 final Context context = getActivity(); 293 294 mNetworkService = INetworkManagementService.Stub.asInterface( 295 ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)); 296 mStatsService = INetworkStatsService.Stub.asInterface( 297 ServiceManager.getService(Context.NETWORK_STATS_SERVICE)); 298 mPolicyManager = NetworkPolicyManager.from(context); 299 mTelephonyManager = TelephonyManager.from(context); 300 mSubscriptionManager = SubscriptionManager.from(context); 301 302 mPrefs = getActivity().getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE); 303 304 mPolicyEditor = new NetworkPolicyEditor(mPolicyManager); 305 mPolicyEditor.read(); 306 307 mSubInfoList = mSubscriptionManager.getActiveSubscriptionInfoList(); 308 mMobileTagMap = initMobileTabTag(mSubInfoList); 309 310 try { 311 if (!mNetworkService.isBandwidthControlEnabled()) { 312 Log.w(TAG, "No bandwidth control; leaving"); 313 getActivity().finish(); 314 } 315 } catch (RemoteException e) { 316 Log.w(TAG, "No bandwidth control; leaving"); 317 getActivity().finish(); 318 } 319 320 try { 321 mStatsSession = mStatsService.openSession(); 322 } catch (RemoteException e) { 323 throw new RuntimeException(e); 324 } 325 326 mShowWifi = mPrefs.getBoolean(PREF_SHOW_WIFI, false); 327 mShowEthernet = mPrefs.getBoolean(PREF_SHOW_ETHERNET, false); 328 329 // override preferences when no mobile radio 330 if (!hasReadyMobileRadio(context)) { 331 mShowWifi = true; 332 mShowEthernet = true; 333 } 334 335 setHasOptionsMenu(true); 336 } 337 338 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)339 public View onCreateView(LayoutInflater inflater, ViewGroup container, 340 Bundle savedInstanceState) { 341 342 final Context context = inflater.getContext(); 343 final View view = inflater.inflate(R.layout.data_usage_summary, container, false); 344 345 mUidDetailProvider = new UidDetailProvider(context); 346 347 mTabHost = (TabHost) view.findViewById(android.R.id.tabhost); 348 mTabsContainer = (ViewGroup) view.findViewById(R.id.tabs_container); 349 mTabWidget = (TabWidget) view.findViewById(android.R.id.tabs); 350 mListView = (ListView) view.findViewById(android.R.id.list); 351 352 // decide if we need to manually inset our content, or if we should rely 353 // on parent container for inset. 354 final boolean shouldInset = mListView.getScrollBarStyle() 355 == View.SCROLLBARS_OUTSIDE_OVERLAY; 356 mInsetSide = 0; 357 358 // adjust padding around tabwidget as needed 359 prepareCustomPreferencesList(container, view, mListView, false); 360 361 mTabHost.setup(); 362 mTabHost.setOnTabChangedListener(mTabListener); 363 364 mHeader = (ViewGroup) inflater.inflate(R.layout.data_usage_header, mListView, false); 365 mHeader.setClickable(true); 366 367 mListView.addHeaderView(new View(context), null, true); 368 mListView.addHeaderView(mHeader, null, true); 369 mListView.setItemsCanFocus(true); 370 371 if (mInsetSide > 0) { 372 // inset selector and divider drawables 373 insetListViewDrawables(mListView, mInsetSide); 374 mHeader.setPaddingRelative(mInsetSide, 0, mInsetSide, 0); 375 } 376 377 { 378 // bind network switches 379 mNetworkSwitchesContainer = (ViewGroup) mHeader.findViewById( 380 R.id.network_switches_container); 381 mNetworkSwitches = (LinearLayout) mHeader.findViewById(R.id.network_switches); 382 383 mDataEnabled = new Switch(inflater.getContext()); 384 mDataEnabled.setClickable(false); 385 mDataEnabled.setFocusable(false); 386 mDataEnabledView = inflatePreference(inflater, mNetworkSwitches, mDataEnabled); 387 mDataEnabledView.setTag(R.id.preference_highlight_key, 388 DATA_USAGE_ENABLE_MOBILE_KEY); 389 mDataEnabledView.setClickable(true); 390 mDataEnabledView.setFocusable(true); 391 mDataEnabledView.setOnClickListener(mDataEnabledListener); 392 mNetworkSwitches.addView(mDataEnabledView); 393 394 mDisableAtLimit = new Switch(inflater.getContext()); 395 mDisableAtLimit.setClickable(false); 396 mDisableAtLimit.setFocusable(false); 397 mDisableAtLimitView = inflatePreference(inflater, mNetworkSwitches, mDisableAtLimit); 398 mDisableAtLimitView.setTag(R.id.preference_highlight_key, 399 DATA_USAGE_DISABLE_MOBILE_LIMIT_KEY); 400 mDisableAtLimitView.setClickable(true); 401 mDisableAtLimitView.setFocusable(true); 402 mDisableAtLimitView.setOnClickListener(mDisableAtLimitListener); 403 mNetworkSwitches.addView(mDisableAtLimitView); 404 405 mCycleView = inflater.inflate(R.layout.data_usage_cycles, mNetworkSwitches, false); 406 mCycleView.setTag(R.id.preference_highlight_key, DATA_USAGE_CYCLE_KEY); 407 mCycleSpinner = (Spinner) mCycleView.findViewById(R.id.cycles_spinner); 408 mCycleAdapter = new CycleAdapter(context); 409 mCycleSpinner.setAdapter(mCycleAdapter); 410 mCycleSpinner.setOnItemSelectedListener(mCycleListener); 411 mCycleSummary = (TextView) mCycleView.findViewById(R.id.cycle_summary); 412 mNetworkSwitches.addView(mCycleView); 413 mSeries = (ChartNetworkSeriesView)view.findViewById(R.id.series); 414 mDetailedSeries = (ChartNetworkSeriesView)view.findViewById(R.id.detail_series); 415 } 416 417 mChart = (ChartDataUsageView) mHeader.findViewById(R.id.chart); 418 mChart.setListener(mChartListener); 419 mChart.bindNetworkPolicy(null); 420 421 { 422 // bind app detail controls 423 mAppDetail = mHeader.findViewById(R.id.app_detail); 424 mAppIcon = (ImageView) mAppDetail.findViewById(R.id.app_icon); 425 mAppTitles = (ViewGroup) mAppDetail.findViewById(R.id.app_titles); 426 mAppForeground = (TextView) mAppDetail.findViewById(R.id.app_foreground); 427 mAppBackground = (TextView) mAppDetail.findViewById(R.id.app_background); 428 mAppSwitches = (LinearLayout) mAppDetail.findViewById(R.id.app_switches); 429 430 mAppSettings = (Button) mAppDetail.findViewById(R.id.app_settings); 431 432 mAppRestrict = new Switch(inflater.getContext()); 433 mAppRestrict.setClickable(false); 434 mAppRestrict.setFocusable(false); 435 mAppRestrictView = inflatePreference(inflater, mAppSwitches, mAppRestrict); 436 mAppRestrictView.setClickable(true); 437 mAppRestrictView.setFocusable(true); 438 mAppRestrictView.setOnClickListener(mAppRestrictListener); 439 mAppSwitches.addView(mAppRestrictView); 440 } 441 442 mDisclaimer = mHeader.findViewById(R.id.disclaimer); 443 mEmpty = (TextView) mHeader.findViewById(android.R.id.empty); 444 mStupidPadding = mHeader.findViewById(R.id.stupid_padding); 445 446 final UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE); 447 mAdapter = new DataUsageAdapter(um, mUidDetailProvider, mInsetSide); 448 mListView.setOnItemClickListener(mListListener); 449 mListView.setAdapter(mAdapter); 450 451 return view; 452 } 453 454 @Override onViewStateRestored(Bundle savedInstanceState)455 public void onViewStateRestored(Bundle savedInstanceState) { 456 super.onViewStateRestored(savedInstanceState); 457 458 // pick default tab based on incoming intent 459 final Intent intent = getActivity().getIntent(); 460 mIntentTab = computeTabFromIntent(intent); 461 462 // this kicks off chain reaction which creates tabs, binds the body to 463 // selected network, and binds chart, cycles and detail list. 464 updateTabs(); 465 } 466 467 @Override onResume()468 public void onResume() { 469 super.onResume(); 470 471 getView().post(new Runnable() { 472 @Override 473 public void run() { 474 highlightViewIfNeeded(); 475 } 476 }); 477 478 // kick off background task to update stats 479 new AsyncTask<Void, Void, Void>() { 480 @Override 481 protected Void doInBackground(Void... params) { 482 try { 483 // wait a few seconds before kicking off 484 Thread.sleep(2 * DateUtils.SECOND_IN_MILLIS); 485 mStatsService.forceUpdate(); 486 } catch (InterruptedException e) { 487 } catch (RemoteException e) { 488 } 489 return null; 490 } 491 492 @Override 493 protected void onPostExecute(Void result) { 494 if (isAdded()) { 495 updateBody(); 496 } 497 } 498 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 499 } 500 501 @Override onCreateOptionsMenu(Menu menu, MenuInflater inflater)502 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 503 inflater.inflate(R.menu.data_usage, menu); 504 } 505 506 @Override onPrepareOptionsMenu(Menu menu)507 public void onPrepareOptionsMenu(Menu menu) { 508 final Context context = getActivity(); 509 final boolean appDetailMode = isAppDetailMode(); 510 final boolean isOwner = ActivityManager.getCurrentUser() == UserHandle.USER_OWNER; 511 512 mMenuShowWifi = menu.findItem(R.id.data_usage_menu_show_wifi); 513 if (hasWifiRadio(context) && hasReadyMobileRadio(context)) { 514 mMenuShowWifi.setVisible(!appDetailMode); 515 } else { 516 mMenuShowWifi.setVisible(false); 517 } 518 519 mMenuShowEthernet = menu.findItem(R.id.data_usage_menu_show_ethernet); 520 if (hasEthernet(context) && hasReadyMobileRadio(context)) { 521 mMenuShowEthernet.setVisible(!appDetailMode); 522 } else { 523 mMenuShowEthernet.setVisible(false); 524 } 525 526 mMenuRestrictBackground = menu.findItem(R.id.data_usage_menu_restrict_background); 527 mMenuRestrictBackground.setVisible( 528 hasReadyMobileRadio(context) && isOwner && !appDetailMode); 529 530 final MenuItem metered = menu.findItem(R.id.data_usage_menu_metered); 531 if (hasReadyMobileRadio(context) || hasWifiRadio(context)) { 532 metered.setVisible(!appDetailMode); 533 } else { 534 metered.setVisible(false); 535 } 536 537 // TODO: show when multiple sims available 538 mMenuSimCards = menu.findItem(R.id.data_usage_menu_sim_cards); 539 mMenuSimCards.setVisible(false); 540 541 mMenuCellularNetworks = menu.findItem(R.id.data_usage_menu_cellular_networks); 542 mMenuCellularNetworks.setVisible(hasReadyMobileRadio(context) 543 && !appDetailMode && isOwner); 544 545 final MenuItem help = menu.findItem(R.id.data_usage_menu_help); 546 String helpUrl; 547 if (!TextUtils.isEmpty(helpUrl = getResources().getString(R.string.help_url_data_usage))) { 548 HelpUtils.prepareHelpMenuItem(context, help, helpUrl); 549 } else { 550 help.setVisible(false); 551 } 552 553 updateMenuTitles(); 554 } 555 updateMenuTitles()556 private void updateMenuTitles() { 557 if (mPolicyManager.getRestrictBackground()) { 558 mMenuRestrictBackground.setTitle(R.string.data_usage_menu_allow_background); 559 } else { 560 mMenuRestrictBackground.setTitle(R.string.data_usage_menu_restrict_background); 561 } 562 563 if (mShowWifi) { 564 mMenuShowWifi.setTitle(R.string.data_usage_menu_hide_wifi); 565 } else { 566 mMenuShowWifi.setTitle(R.string.data_usage_menu_show_wifi); 567 } 568 569 if (mShowEthernet) { 570 mMenuShowEthernet.setTitle(R.string.data_usage_menu_hide_ethernet); 571 } else { 572 mMenuShowEthernet.setTitle(R.string.data_usage_menu_show_ethernet); 573 } 574 } 575 576 @Override onOptionsItemSelected(MenuItem item)577 public boolean onOptionsItemSelected(MenuItem item) { 578 switch (item.getItemId()) { 579 case R.id.data_usage_menu_restrict_background: { 580 final boolean restrictBackground = !mPolicyManager.getRestrictBackground(); 581 if (restrictBackground) { 582 ConfirmRestrictFragment.show(this); 583 } else { 584 // no confirmation to drop restriction 585 setRestrictBackground(false); 586 } 587 return true; 588 } 589 case R.id.data_usage_menu_show_wifi: { 590 mShowWifi = !mShowWifi; 591 mPrefs.edit().putBoolean(PREF_SHOW_WIFI, mShowWifi).apply(); 592 updateMenuTitles(); 593 updateTabs(); 594 return true; 595 } 596 case R.id.data_usage_menu_show_ethernet: { 597 mShowEthernet = !mShowEthernet; 598 mPrefs.edit().putBoolean(PREF_SHOW_ETHERNET, mShowEthernet).apply(); 599 updateMenuTitles(); 600 updateTabs(); 601 return true; 602 } 603 case R.id.data_usage_menu_sim_cards: { 604 // TODO: hook up to sim cards 605 return true; 606 } 607 case R.id.data_usage_menu_cellular_networks: { 608 final Intent intent = new Intent(Intent.ACTION_MAIN); 609 intent.setComponent(new ComponentName("com.android.phone", 610 "com.android.phone.MobileNetworkSettings")); 611 startActivity(intent); 612 return true; 613 } 614 case R.id.data_usage_menu_metered: { 615 final SettingsActivity sa = (SettingsActivity) getActivity(); 616 sa.startPreferencePanel(DataUsageMeteredSettings.class.getCanonicalName(), null, 617 R.string.data_usage_metered_title, null, this, 0); 618 return true; 619 } 620 } 621 return false; 622 } 623 624 @Override onDestroy()625 public void onDestroy() { 626 mDataEnabledView = null; 627 mDisableAtLimitView = null; 628 629 mUidDetailProvider.clearCache(); 630 mUidDetailProvider = null; 631 632 TrafficStats.closeQuietly(mStatsSession); 633 634 super.onDestroy(); 635 } 636 637 /** 638 * Build and assign {@link LayoutTransition} to various containers. Should 639 * only be assigned after initial layout is complete. 640 */ ensureLayoutTransitions()641 private void ensureLayoutTransitions() { 642 // skip when already setup 643 if (mChart.getLayoutTransition() != null) return; 644 645 mTabsContainer.setLayoutTransition(buildLayoutTransition()); 646 mHeader.setLayoutTransition(buildLayoutTransition()); 647 mNetworkSwitchesContainer.setLayoutTransition(buildLayoutTransition()); 648 649 final LayoutTransition chartTransition = buildLayoutTransition(); 650 chartTransition.disableTransitionType(LayoutTransition.APPEARING); 651 chartTransition.disableTransitionType(LayoutTransition.DISAPPEARING); 652 mChart.setLayoutTransition(chartTransition); 653 } 654 buildLayoutTransition()655 private static LayoutTransition buildLayoutTransition() { 656 final LayoutTransition transition = new LayoutTransition(); 657 if (TEST_ANIM) { 658 transition.setDuration(1500); 659 } 660 transition.setAnimateParentHierarchy(false); 661 return transition; 662 } 663 664 /** 665 * Rebuild all tabs based on {@link NetworkPolicyEditor} and 666 * {@link #mShowWifi}, hiding the tabs entirely when applicable. Selects 667 * first tab, and kicks off a full rebind of body contents. 668 */ updateTabs()669 private void updateTabs() { 670 final Context context = getActivity(); 671 mTabHost.clearAllTabs(); 672 673 int simCount = mTelephonyManager.getSimCount(); 674 675 for (int i = 0; i < simCount; i++) { 676 final SubscriptionInfo sir = Utils.findRecordBySlotId(context, i); 677 if (sir != null) { 678 addMobileTab(context, sir, (simCount > 1)); 679 } 680 } 681 682 if (mShowWifi && hasWifiRadio(context)) { 683 mTabHost.addTab(buildTabSpec(TAB_WIFI, R.string.data_usage_tab_wifi)); 684 } 685 686 if (mShowEthernet && hasEthernet(context)) { 687 mTabHost.addTab(buildTabSpec(TAB_ETHERNET, R.string.data_usage_tab_ethernet)); 688 } 689 690 final boolean noTabs = mTabWidget.getTabCount() == 0; 691 final boolean multipleTabs = mTabWidget.getTabCount() > 1; 692 mTabWidget.setVisibility(multipleTabs ? View.VISIBLE : View.GONE); 693 if (mIntentTab != null) { 694 if (Objects.equal(mIntentTab, mTabHost.getCurrentTabTag())) { 695 // already hit updateBody() when added; ignore 696 updateBody(); 697 } else { 698 mTabHost.setCurrentTabByTag(mIntentTab); 699 } 700 mIntentTab = null; 701 } else if (noTabs) { 702 // no usable tabs, so hide body 703 updateBody(); 704 } else { 705 // already hit updateBody() when added; ignore 706 } 707 } 708 709 /** 710 * Factory that provide empty {@link View} to make {@link TabHost} happy. 711 */ 712 private TabContentFactory mEmptyTabContent = new TabContentFactory() { 713 @Override 714 public View createTabContent(String tag) { 715 return new View(mTabHost.getContext()); 716 } 717 }; 718 719 /** 720 * Build {@link TabSpec} with thin indicator, and empty content. 721 */ buildTabSpec(String tag, int titleRes)722 private TabSpec buildTabSpec(String tag, int titleRes) { 723 return mTabHost.newTabSpec(tag).setIndicator(getText(titleRes)).setContent( 724 mEmptyTabContent); 725 } 726 727 /** 728 * Build {@link TabSpec} with thin indicator, and empty content. 729 */ buildTabSpec(String tag, CharSequence title)730 private TabSpec buildTabSpec(String tag, CharSequence title) { 731 return mTabHost.newTabSpec(tag).setIndicator(title).setContent( 732 mEmptyTabContent); 733 } 734 735 736 private OnTabChangeListener mTabListener = new OnTabChangeListener() { 737 @Override 738 public void onTabChanged(String tabId) { 739 // user changed tab; update body 740 updateBody(); 741 } 742 }; 743 744 /** 745 * Update body content based on current tab. Loads 746 * {@link NetworkStatsHistory} and {@link NetworkPolicy} from system, and 747 * binds them to visible controls. 748 */ updateBody()749 private void updateBody() { 750 mBinding = true; 751 if (!isAdded()) return; 752 753 final Context context = getActivity(); 754 final Resources resources = context.getResources(); 755 final String currentTab = mTabHost.getCurrentTabTag(); 756 final boolean isOwner = ActivityManager.getCurrentUser() == UserHandle.USER_OWNER; 757 758 if (currentTab == null) { 759 Log.w(TAG, "no tab selected; hiding body"); 760 mListView.setVisibility(View.GONE); 761 return; 762 } else { 763 mListView.setVisibility(View.VISIBLE); 764 } 765 766 mCurrentTab = currentTab; 767 768 if (LOGD) Log.d(TAG, "updateBody() with currentTab=" + currentTab); 769 770 mDataEnabledSupported = isOwner; 771 mDisableAtLimitSupported = true; 772 773 // TODO: remove mobile tabs when SIM isn't ready probably by 774 // TODO: using SubscriptionManager.getActiveSubscriptionInfoList. 775 if (LOGD) Log.d(TAG, "updateBody() isMobileTab=" + isMobileTab(currentTab)); 776 777 if (isMobileTab(currentTab)) { 778 if (LOGD) Log.d(TAG, "updateBody() mobile tab"); 779 setPreferenceTitle(mDataEnabledView, R.string.data_usage_enable_mobile); 780 setPreferenceTitle(mDisableAtLimitView, R.string.data_usage_disable_mobile_limit); 781 mDataEnabledSupported = isMobileDataAvailable(getSubId(currentTab)); 782 783 // Match mobile traffic for this subscriber, but normalize it to 784 // catch any other merged subscribers. 785 mTemplate = buildTemplateMobileAll( 786 getActiveSubscriberId(context, getSubId(currentTab))); 787 mTemplate = NetworkTemplate.normalize(mTemplate, 788 mTelephonyManager.getMergedSubscriberIds()); 789 790 } else if (TAB_3G.equals(currentTab)) { 791 if (LOGD) Log.d(TAG, "updateBody() 3g tab"); 792 setPreferenceTitle(mDataEnabledView, R.string.data_usage_enable_3g); 793 setPreferenceTitle(mDisableAtLimitView, R.string.data_usage_disable_3g_limit); 794 // TODO: bind mDataEnabled to 3G radio state 795 mTemplate = buildTemplateMobile3gLower(getActiveSubscriberId(context)); 796 797 } else if (TAB_4G.equals(currentTab)) { 798 if (LOGD) Log.d(TAG, "updateBody() 4g tab"); 799 setPreferenceTitle(mDataEnabledView, R.string.data_usage_enable_4g); 800 setPreferenceTitle(mDisableAtLimitView, R.string.data_usage_disable_4g_limit); 801 // TODO: bind mDataEnabled to 4G radio state 802 mTemplate = buildTemplateMobile4g(getActiveSubscriberId(context)); 803 804 } else if (TAB_WIFI.equals(currentTab)) { 805 // wifi doesn't have any controls 806 if (LOGD) Log.d(TAG, "updateBody() wifi tab"); 807 mDataEnabledSupported = false; 808 mDisableAtLimitSupported = false; 809 mTemplate = buildTemplateWifiWildcard(); 810 811 } else if (TAB_ETHERNET.equals(currentTab)) { 812 // ethernet doesn't have any controls 813 if (LOGD) Log.d(TAG, "updateBody() ethernet tab"); 814 mDataEnabledSupported = false; 815 mDisableAtLimitSupported = false; 816 mTemplate = buildTemplateEthernet(); 817 818 } else { 819 if (LOGD) Log.d(TAG, "updateBody() unknown tab"); 820 throw new IllegalStateException("unknown tab: " + currentTab); 821 } 822 823 // kick off loader for network history 824 // TODO: consider chaining two loaders together instead of reloading 825 // network history when showing app detail. 826 getLoaderManager().restartLoader(LOADER_CHART_DATA, 827 ChartDataLoader.buildArgs(mTemplate, mCurrentApp), mChartDataCallbacks); 828 829 // detail mode can change visible menus, invalidate 830 getActivity().invalidateOptionsMenu(); 831 832 mBinding = false; 833 834 int seriesColor = resources.getColor(R.color.sim_noitification); 835 if (mCurrentTab != null && mCurrentTab.length() > TAB_MOBILE.length() ){ 836 final int slotId = Integer.parseInt(mCurrentTab.substring(TAB_MOBILE.length(), 837 mCurrentTab.length())); 838 final SubscriptionInfo sir = com.android.settings.Utils.findRecordBySlotId(context, 839 slotId); 840 841 if (sir != null) { 842 seriesColor = sir.getIconTint(); 843 } 844 } 845 846 final int secondaryColor = Color.argb(127, Color.red(seriesColor), Color.green(seriesColor), 847 Color.blue(seriesColor)); 848 mSeries.setChartColor(Color.BLACK, seriesColor, secondaryColor); 849 mDetailedSeries.setChartColor(Color.BLACK, seriesColor, secondaryColor); 850 } 851 isAppDetailMode()852 private boolean isAppDetailMode() { 853 return mCurrentApp != null; 854 } 855 856 /** 857 * Update UID details panels to match {@link #mCurrentApp}, showing or 858 * hiding them depending on {@link #isAppDetailMode()}. 859 */ updateAppDetail()860 private void updateAppDetail() { 861 final Context context = getActivity(); 862 final PackageManager pm = context.getPackageManager(); 863 final LayoutInflater inflater = getActivity().getLayoutInflater(); 864 865 if (isAppDetailMode()) { 866 mAppDetail.setVisibility(View.VISIBLE); 867 mCycleAdapter.setChangeVisible(false); 868 } else { 869 mAppDetail.setVisibility(View.GONE); 870 mCycleAdapter.setChangeVisible(true); 871 872 // hide detail stats when not in detail mode 873 mChart.bindDetailNetworkStats(null); 874 return; 875 } 876 877 // remove warning/limit sweeps while in detail mode 878 mChart.bindNetworkPolicy(null); 879 880 // show icon and all labels appearing under this app 881 final int uid = mCurrentApp.key; 882 final UidDetail detail = mUidDetailProvider.getUidDetail(uid, true); 883 mAppIcon.setImageDrawable(detail.icon); 884 885 mAppTitles.removeAllViews(); 886 887 View title = null; 888 if (detail.detailLabels != null) { 889 final int n = detail.detailLabels.length; 890 for (int i = 0; i < n; ++i) { 891 CharSequence label = detail.detailLabels[i]; 892 CharSequence contentDescription = detail.detailContentDescriptions[i]; 893 title = inflater.inflate(R.layout.data_usage_app_title, mAppTitles, false); 894 TextView appTitle = (TextView) title.findViewById(R.id.app_title); 895 appTitle.setText(label); 896 appTitle.setContentDescription(contentDescription); 897 mAppTitles.addView(title); 898 } 899 } else { 900 title = inflater.inflate(R.layout.data_usage_app_title, mAppTitles, false); 901 TextView appTitle = (TextView) title.findViewById(R.id.app_title); 902 appTitle.setText(detail.label); 903 appTitle.setContentDescription(detail.contentDescription); 904 mAppTitles.addView(title); 905 } 906 907 // Remember last slot for summary 908 if (title != null) { 909 mAppTotal = (TextView) title.findViewById(R.id.app_summary); 910 } else { 911 mAppTotal = null; 912 } 913 914 // enable settings button when package provides it 915 final String[] packageNames = pm.getPackagesForUid(uid); 916 if (packageNames != null && packageNames.length > 0) { 917 mAppSettingsIntent = new Intent(Intent.ACTION_MANAGE_NETWORK_USAGE); 918 mAppSettingsIntent.addCategory(Intent.CATEGORY_DEFAULT); 919 920 // Search for match across all packages 921 boolean matchFound = false; 922 for (String packageName : packageNames) { 923 mAppSettingsIntent.setPackage(packageName); 924 if (pm.resolveActivity(mAppSettingsIntent, 0) != null) { 925 matchFound = true; 926 break; 927 } 928 } 929 930 mAppSettings.setOnClickListener(new OnClickListener() { 931 @Override 932 public void onClick(View v) { 933 if (!isAdded()) { 934 return; 935 } 936 937 // TODO: target towards entire UID instead of just first package 938 getActivity().startActivityAsUser(mAppSettingsIntent, 939 new UserHandle(UserHandle.getUserId(uid))); 940 } 941 }); 942 mAppSettings.setEnabled(matchFound); 943 mAppSettings.setVisibility(View.VISIBLE); 944 945 } else { 946 mAppSettingsIntent = null; 947 mAppSettings.setOnClickListener(null); 948 mAppSettings.setVisibility(View.GONE); 949 } 950 951 updateDetailData(); 952 953 if (UserHandle.isApp(uid) && !mPolicyManager.getRestrictBackground() 954 && isBandwidthControlEnabled() && hasReadyMobileRadio(context)) { 955 setPreferenceTitle(mAppRestrictView, R.string.data_usage_app_restrict_background); 956 setPreferenceSummary(mAppRestrictView, 957 getString(R.string.data_usage_app_restrict_background_summary)); 958 959 mAppRestrictView.setVisibility(View.VISIBLE); 960 mAppRestrict.setChecked(getAppRestrictBackground()); 961 962 } else { 963 mAppRestrictView.setVisibility(View.GONE); 964 } 965 } 966 setPolicyWarningBytes(long warningBytes)967 private void setPolicyWarningBytes(long warningBytes) { 968 if (LOGD) Log.d(TAG, "setPolicyWarningBytes()"); 969 mPolicyEditor.setPolicyWarningBytes(mTemplate, warningBytes); 970 updatePolicy(false); 971 } 972 setPolicyLimitBytes(long limitBytes)973 private void setPolicyLimitBytes(long limitBytes) { 974 if (LOGD) Log.d(TAG, "setPolicyLimitBytes()"); 975 mPolicyEditor.setPolicyLimitBytes(mTemplate, limitBytes); 976 updatePolicy(false); 977 } 978 isMobileDataEnabled(int subId)979 private boolean isMobileDataEnabled(int subId) { 980 if (LOGD) Log.d(TAG, "isMobileDataEnabled:+ subId=" + subId); 981 boolean isEnable = false; 982 if (mMobileDataEnabled.get(String.valueOf(subId)) != null) { 983 //TODO: deprecate and remove this once enabled flag is on policy 984 //Multiple Subscriptions, the value need to be reseted 985 isEnable = mMobileDataEnabled.get(String.valueOf(subId)).booleanValue(); 986 if (LOGD) { 987 Log.d(TAG, "isMobileDataEnabled: != null, subId=" + subId 988 + " isEnable=" + isEnable); 989 } 990 mMobileDataEnabled.put(String.valueOf(subId), null); 991 } else { 992 // SUB SELECT 993 isEnable = mTelephonyManager.getDataEnabled(subId); 994 if (LOGD) { 995 Log.d(TAG, "isMobileDataEnabled: == null, subId=" + subId 996 + " isEnable=" + isEnable); 997 } 998 } 999 return isEnable; 1000 } 1001 setMobileDataEnabled(int subId, boolean enabled)1002 private void setMobileDataEnabled(int subId, boolean enabled) { 1003 if (LOGD) Log.d(TAG, "setMobileDataEnabled()"); 1004 mTelephonyManager.setDataEnabled(subId, enabled); 1005 mMobileDataEnabled.put(String.valueOf(subId), enabled); 1006 updatePolicy(false); 1007 } 1008 isNetworkPolicyModifiable(NetworkPolicy policy)1009 private boolean isNetworkPolicyModifiable(NetworkPolicy policy) { 1010 return policy != null && isBandwidthControlEnabled() && mDataEnabled.isChecked() 1011 && ActivityManager.getCurrentUser() == UserHandle.USER_OWNER; 1012 } 1013 isBandwidthControlEnabled()1014 private boolean isBandwidthControlEnabled() { 1015 try { 1016 return mNetworkService.isBandwidthControlEnabled(); 1017 } catch (RemoteException e) { 1018 Log.w(TAG, "problem talking with INetworkManagementService: " + e); 1019 return false; 1020 } 1021 } 1022 setRestrictBackground(boolean restrictBackground)1023 public void setRestrictBackground(boolean restrictBackground) { 1024 mPolicyManager.setRestrictBackground(restrictBackground); 1025 updateMenuTitles(); 1026 } 1027 getAppRestrictBackground()1028 private boolean getAppRestrictBackground() { 1029 final int uid = mCurrentApp.key; 1030 final int uidPolicy = mPolicyManager.getUidPolicy(uid); 1031 return (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0; 1032 } 1033 setAppRestrictBackground(boolean restrictBackground)1034 private void setAppRestrictBackground(boolean restrictBackground) { 1035 if (LOGD) Log.d(TAG, "setAppRestrictBackground()"); 1036 final int uid = mCurrentApp.key; 1037 mPolicyManager.setUidPolicy( 1038 uid, restrictBackground ? POLICY_REJECT_METERED_BACKGROUND : POLICY_NONE); 1039 mAppRestrict.setChecked(restrictBackground); 1040 } 1041 1042 /** 1043 * Update chart sweeps and cycle list to reflect {@link NetworkPolicy} for 1044 * current {@link #mTemplate}. 1045 */ updatePolicy(boolean refreshCycle)1046 private void updatePolicy(boolean refreshCycle) { 1047 boolean dataEnabledVisible = mDataEnabledSupported; 1048 boolean disableAtLimitVisible = mDisableAtLimitSupported; 1049 1050 if (isAppDetailMode()) { 1051 dataEnabledVisible = false; 1052 disableAtLimitVisible = false; 1053 } 1054 1055 // TODO: move enabled state directly into policy 1056 if (isMobileTab(mCurrentTab)) { 1057 mBinding = true; 1058 mDataEnabled.setChecked(isMobileDataEnabled(getSubId(mCurrentTab))); 1059 mBinding = false; 1060 } 1061 1062 final NetworkPolicy policy = mPolicyEditor.getPolicy(mTemplate); 1063 //SUB SELECT 1064 if (isNetworkPolicyModifiable(policy) && isMobileDataAvailable(getSubId(mCurrentTab))) { 1065 mDisableAtLimit.setChecked(policy != null && policy.limitBytes != LIMIT_DISABLED); 1066 if (!isAppDetailMode()) { 1067 mChart.bindNetworkPolicy(policy); 1068 } 1069 1070 } else { 1071 // controls are disabled; don't bind warning/limit sweeps 1072 disableAtLimitVisible = false; 1073 mChart.bindNetworkPolicy(null); 1074 } 1075 1076 mDataEnabledView.setVisibility(dataEnabledVisible ? View.VISIBLE : View.GONE); 1077 mDisableAtLimitView.setVisibility(disableAtLimitVisible ? View.VISIBLE : View.GONE); 1078 1079 if (refreshCycle) { 1080 // generate cycle list based on policy and available history 1081 updateCycleList(policy); 1082 } 1083 } 1084 1085 /** 1086 * Rebuild {@link #mCycleAdapter} based on {@link NetworkPolicy#cycleDay} 1087 * and available {@link NetworkStatsHistory} data. Always selects the newest 1088 * item, updating the inspection range on {@link #mChart}. 1089 */ updateCycleList(NetworkPolicy policy)1090 private void updateCycleList(NetworkPolicy policy) { 1091 // stash away currently selected cycle to try restoring below 1092 final CycleItem previousItem = (CycleItem) mCycleSpinner.getSelectedItem(); 1093 mCycleAdapter.clear(); 1094 1095 final Context context = mCycleSpinner.getContext(); 1096 1097 long historyStart = Long.MAX_VALUE; 1098 long historyEnd = Long.MIN_VALUE; 1099 if (mChartData != null) { 1100 historyStart = mChartData.network.getStart(); 1101 historyEnd = mChartData.network.getEnd(); 1102 } 1103 1104 final long now = System.currentTimeMillis(); 1105 if (historyStart == Long.MAX_VALUE) historyStart = now; 1106 if (historyEnd == Long.MIN_VALUE) historyEnd = now + 1; 1107 1108 boolean hasCycles = false; 1109 if (policy != null) { 1110 // find the next cycle boundary 1111 long cycleEnd = computeNextCycleBoundary(historyEnd, policy); 1112 1113 // walk backwards, generating all valid cycle ranges 1114 while (cycleEnd > historyStart) { 1115 final long cycleStart = computeLastCycleBoundary(cycleEnd, policy); 1116 Log.d(TAG, "generating cs=" + cycleStart + " to ce=" + cycleEnd + " waiting for hs=" 1117 + historyStart); 1118 mCycleAdapter.add(new CycleItem(context, cycleStart, cycleEnd)); 1119 cycleEnd = cycleStart; 1120 hasCycles = true; 1121 } 1122 1123 // one last cycle entry to modify policy cycle day 1124 mCycleAdapter.setChangePossible(isNetworkPolicyModifiable(policy)); 1125 } 1126 1127 if (!hasCycles) { 1128 // no policy defined cycles; show entry for each four-week period 1129 long cycleEnd = historyEnd; 1130 while (cycleEnd > historyStart) { 1131 final long cycleStart = cycleEnd - (DateUtils.WEEK_IN_MILLIS * 4); 1132 mCycleAdapter.add(new CycleItem(context, cycleStart, cycleEnd)); 1133 cycleEnd = cycleStart; 1134 } 1135 1136 mCycleAdapter.setChangePossible(false); 1137 } 1138 1139 // force pick the current cycle (first item) 1140 if (mCycleAdapter.getCount() > 0) { 1141 final int position = mCycleAdapter.findNearestPosition(previousItem); 1142 mCycleSpinner.setSelection(position); 1143 1144 // only force-update cycle when changed; skipping preserves any 1145 // user-defined inspection region. 1146 final CycleItem selectedItem = mCycleAdapter.getItem(position); 1147 if (!Objects.equal(selectedItem, previousItem)) { 1148 mCycleListener.onItemSelected(mCycleSpinner, null, position, 0); 1149 } else { 1150 // but still kick off loader for detailed list 1151 updateDetailData(); 1152 } 1153 } else { 1154 updateDetailData(); 1155 } 1156 } 1157 disableDataForOtherSubscriptions(SubscriptionInfo currentSir)1158 private void disableDataForOtherSubscriptions(SubscriptionInfo currentSir) { 1159 if (mSubInfoList != null) { 1160 for (SubscriptionInfo subInfo : mSubInfoList) { 1161 if (subInfo.getSubscriptionId() != currentSir.getSubscriptionId()) { 1162 setMobileDataEnabled(subInfo.getSubscriptionId(), false); 1163 } 1164 } 1165 } 1166 } 1167 1168 private View.OnClickListener mDataEnabledListener = new View.OnClickListener() { 1169 @Override 1170 public void onClick(View v) { 1171 if (mBinding) return; 1172 1173 final boolean dataEnabled = !mDataEnabled.isChecked(); 1174 final String currentTab = mCurrentTab; 1175 if (isMobileTab(currentTab)) { 1176 if (dataEnabled) { 1177 // If we are showing the Sim Card tile then we are a Multi-Sim device. 1178 if (Utils.showSimCardTile(getActivity())) { 1179 handleMultiSimDataDialog(); 1180 } else { 1181 setMobileDataEnabled(getSubId(currentTab), true); 1182 } 1183 } else { 1184 // disabling data; show confirmation dialog which eventually 1185 // calls setMobileDataEnabled() once user confirms. 1186 ConfirmDataDisableFragment.show(DataUsageSummary.this, getSubId(mCurrentTab)); 1187 } 1188 } 1189 1190 updatePolicy(false); 1191 } 1192 }; 1193 handleMultiSimDataDialog()1194 private void handleMultiSimDataDialog() { 1195 final Context context = getActivity(); 1196 final SubscriptionInfo currentSir = getCurrentTabSubInfo(context); 1197 1198 //If sim has not loaded after toggling data switch, return. 1199 if (currentSir == null) { 1200 return; 1201 } 1202 1203 final SubscriptionInfo nextSir = mSubscriptionManager.getActiveSubscriptionInfo( 1204 mSubscriptionManager.getDefaultDataSubId()); 1205 1206 // If the device is single SIM or is enabling data on the active data SIM then forgo 1207 // the pop-up. 1208 if (!Utils.showSimCardTile(context) || 1209 (nextSir != null && currentSir != null && 1210 currentSir.getSubscriptionId() == nextSir.getSubscriptionId())) { 1211 setMobileDataEnabled(currentSir.getSubscriptionId(), true); 1212 if (nextSir != null && currentSir != null && 1213 currentSir.getSubscriptionId() == nextSir.getSubscriptionId()) { 1214 disableDataForOtherSubscriptions(currentSir); 1215 } 1216 updateBody(); 1217 return; 1218 } 1219 1220 final String previousName = (nextSir == null) 1221 ? context.getResources().getString(R.string.sim_selection_required_pref) 1222 : nextSir.getDisplayName().toString(); 1223 1224 AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); 1225 1226 builder.setTitle(R.string.sim_change_data_title); 1227 builder.setMessage(getActivity().getResources().getString(R.string.sim_change_data_message, 1228 currentSir.getDisplayName(), previousName)); 1229 1230 builder.setPositiveButton(R.string.okay, new DialogInterface.OnClickListener() { 1231 @Override 1232 public void onClick(DialogInterface dialog, int id) { 1233 mSubscriptionManager.setDefaultDataSubId(currentSir.getSubscriptionId()); 1234 setMobileDataEnabled(currentSir.getSubscriptionId(), true); 1235 disableDataForOtherSubscriptions(currentSir); 1236 updateBody(); 1237 } 1238 }); 1239 builder.setNegativeButton(R.string.cancel, null); 1240 1241 builder.create().show(); 1242 } 1243 1244 private View.OnClickListener mDisableAtLimitListener = new View.OnClickListener() { 1245 @Override 1246 public void onClick(View v) { 1247 final boolean disableAtLimit = !mDisableAtLimit.isChecked(); 1248 if (disableAtLimit) { 1249 // enabling limit; show confirmation dialog which eventually 1250 // calls setPolicyLimitBytes() once user confirms. 1251 ConfirmLimitFragment.show(DataUsageSummary.this); 1252 } else { 1253 setPolicyLimitBytes(LIMIT_DISABLED); 1254 } 1255 } 1256 }; 1257 1258 private View.OnClickListener mAppRestrictListener = new View.OnClickListener() { 1259 @Override 1260 public void onClick(View v) { 1261 final boolean restrictBackground = !mAppRestrict.isChecked(); 1262 1263 if (restrictBackground) { 1264 // enabling restriction; show confirmation dialog which 1265 // eventually calls setRestrictBackground() once user 1266 // confirms. 1267 ConfirmAppRestrictFragment.show(DataUsageSummary.this); 1268 } else { 1269 setAppRestrictBackground(false); 1270 } 1271 } 1272 }; 1273 1274 private OnItemClickListener mListListener = new OnItemClickListener() { 1275 @Override 1276 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 1277 final Context context = view.getContext(); 1278 final AppItem app = (AppItem) parent.getItemAtPosition(position); 1279 1280 // TODO: sigh, remove this hack once we understand 6450986 1281 if (mUidDetailProvider == null || app == null) return; 1282 1283 final UidDetail detail = mUidDetailProvider.getUidDetail(app.key, true); 1284 AppDetailsFragment.show(DataUsageSummary.this, app, detail.label); 1285 } 1286 }; 1287 1288 private OnItemSelectedListener mCycleListener = new OnItemSelectedListener() { 1289 @Override 1290 public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { 1291 final CycleItem cycle = (CycleItem) parent.getItemAtPosition(position); 1292 if (cycle instanceof CycleChangeItem) { 1293 // show cycle editor; will eventually call setPolicyCycleDay() 1294 // when user finishes editing. 1295 CycleEditorFragment.show(DataUsageSummary.this); 1296 1297 // reset spinner to something other than "change cycle..." 1298 mCycleSpinner.setSelection(0); 1299 1300 } else { 1301 if (LOGD) { 1302 Log.d(TAG, "showing cycle " + cycle + ", start=" + cycle.start + ", end=" 1303 + cycle.end + "]"); 1304 } 1305 1306 // update chart to show selected cycle, and update detail data 1307 // to match updated sweep bounds. 1308 mChart.setVisibleRange(cycle.start, cycle.end); 1309 1310 updateDetailData(); 1311 } 1312 } 1313 1314 @Override 1315 public void onNothingSelected(AdapterView<?> parent) { 1316 // ignored 1317 } 1318 }; 1319 1320 /** 1321 * Update details based on {@link #mChart} inspection range depending on 1322 * current mode. In network mode, updates {@link #mAdapter} with sorted list 1323 * of applications data usage, and when {@link #isAppDetailMode()} update 1324 * app details. 1325 */ updateDetailData()1326 private void updateDetailData() { 1327 if (LOGD) Log.d(TAG, "updateDetailData()"); 1328 1329 final long start = mChart.getInspectStart(); 1330 final long end = mChart.getInspectEnd(); 1331 final long now = System.currentTimeMillis(); 1332 1333 final Context context = getActivity(); 1334 1335 NetworkStatsHistory.Entry entry = null; 1336 if (isAppDetailMode() && mChartData != null && mChartData.detail != null) { 1337 // bind foreground/background to piechart and labels 1338 entry = mChartData.detailDefault.getValues(start, end, now, entry); 1339 final long defaultBytes = entry.rxBytes + entry.txBytes; 1340 entry = mChartData.detailForeground.getValues(start, end, now, entry); 1341 final long foregroundBytes = entry.rxBytes + entry.txBytes; 1342 final long totalBytes = defaultBytes + foregroundBytes; 1343 1344 if (mAppTotal != null) { 1345 mAppTotal.setText(Formatter.formatFileSize(context, totalBytes)); 1346 } 1347 mAppBackground.setText(Formatter.formatFileSize(context, defaultBytes)); 1348 mAppForeground.setText(Formatter.formatFileSize(context, foregroundBytes)); 1349 1350 // and finally leave with summary data for label below 1351 entry = mChartData.detail.getValues(start, end, now, null); 1352 1353 getLoaderManager().destroyLoader(LOADER_SUMMARY); 1354 1355 mCycleSummary.setVisibility(View.GONE); 1356 1357 } else { 1358 if (mChartData != null) { 1359 entry = mChartData.network.getValues(start, end, now, null); 1360 } 1361 1362 mCycleSummary.setVisibility(View.VISIBLE); 1363 1364 // kick off loader for detailed stats 1365 getLoaderManager().restartLoader(LOADER_SUMMARY, 1366 SummaryForAllUidLoader.buildArgs(mTemplate, start, end), mSummaryCallbacks); 1367 } 1368 1369 final long totalBytes = entry != null ? entry.rxBytes + entry.txBytes : 0; 1370 final String totalPhrase = Formatter.formatFileSize(context, totalBytes); 1371 mCycleSummary.setText(totalPhrase); 1372 1373 if (isMobileTab(mCurrentTab) || TAB_3G.equals(mCurrentTab) 1374 || TAB_4G.equals(mCurrentTab)) { 1375 if (isAppDetailMode()) { 1376 mDisclaimer.setVisibility(View.GONE); 1377 } else { 1378 mDisclaimer.setVisibility(View.VISIBLE); 1379 } 1380 } else { 1381 mDisclaimer.setVisibility(View.GONE); 1382 } 1383 1384 // initial layout is finished above, ensure we have transitions 1385 ensureLayoutTransitions(); 1386 } 1387 1388 private final LoaderCallbacks<ChartData> mChartDataCallbacks = new LoaderCallbacks< 1389 ChartData>() { 1390 @Override 1391 public Loader<ChartData> onCreateLoader(int id, Bundle args) { 1392 return new ChartDataLoader(getActivity(), mStatsSession, args); 1393 } 1394 1395 @Override 1396 public void onLoadFinished(Loader<ChartData> loader, ChartData data) { 1397 mChartData = data; 1398 mChart.bindNetworkStats(mChartData.network); 1399 mChart.bindDetailNetworkStats(mChartData.detail); 1400 1401 // calcuate policy cycles based on available data 1402 updatePolicy(true); 1403 updateAppDetail(); 1404 1405 // force scroll to top of body when showing detail 1406 if (mChartData.detail != null) { 1407 mListView.smoothScrollToPosition(0); 1408 } 1409 } 1410 1411 @Override 1412 public void onLoaderReset(Loader<ChartData> loader) { 1413 mChartData = null; 1414 mChart.bindNetworkStats(null); 1415 mChart.bindDetailNetworkStats(null); 1416 } 1417 }; 1418 1419 private final LoaderCallbacks<NetworkStats> mSummaryCallbacks = new LoaderCallbacks< 1420 NetworkStats>() { 1421 @Override 1422 public Loader<NetworkStats> onCreateLoader(int id, Bundle args) { 1423 return new SummaryForAllUidLoader(getActivity(), mStatsSession, args); 1424 } 1425 1426 @Override 1427 public void onLoadFinished(Loader<NetworkStats> loader, NetworkStats data) { 1428 final int[] restrictedUids = mPolicyManager.getUidsWithPolicy( 1429 POLICY_REJECT_METERED_BACKGROUND); 1430 mAdapter.bindStats(data, restrictedUids); 1431 updateEmptyVisible(); 1432 } 1433 1434 @Override 1435 public void onLoaderReset(Loader<NetworkStats> loader) { 1436 mAdapter.bindStats(null, new int[0]); 1437 updateEmptyVisible(); 1438 } 1439 1440 private void updateEmptyVisible() { 1441 final boolean isEmpty = mAdapter.isEmpty() && !isAppDetailMode(); 1442 mEmpty.setVisibility(isEmpty ? View.VISIBLE : View.GONE); 1443 mStupidPadding.setVisibility(isEmpty ? View.VISIBLE : View.GONE); 1444 } 1445 }; 1446 getActiveSubscriberId(Context context)1447 private static String getActiveSubscriberId(Context context) { 1448 final TelephonyManager tele = TelephonyManager.from(context); 1449 final String actualSubscriberId = tele.getSubscriberId(); 1450 String retVal = SystemProperties.get(TEST_SUBSCRIBER_PROP, actualSubscriberId); 1451 if (LOGD) Log.d(TAG, "getActiveSubscriberId=" + retVal + " actualSubscriberId=" + actualSubscriberId); 1452 return retVal; 1453 } 1454 getActiveSubscriberId(Context context, int subId)1455 private static String getActiveSubscriberId(Context context, int subId) { 1456 final TelephonyManager tele = TelephonyManager.from(context); 1457 String retVal = tele.getSubscriberId(subId); 1458 if (LOGD) Log.d(TAG, "getActiveSubscriberId=" + retVal + " subId=" + subId); 1459 return retVal; 1460 } 1461 1462 private DataUsageChartListener mChartListener = new DataUsageChartListener() { 1463 @Override 1464 public void onWarningChanged() { 1465 setPolicyWarningBytes(mChart.getWarningBytes()); 1466 } 1467 1468 @Override 1469 public void onLimitChanged() { 1470 setPolicyLimitBytes(mChart.getLimitBytes()); 1471 } 1472 1473 @Override 1474 public void requestWarningEdit() { 1475 WarningEditorFragment.show(DataUsageSummary.this); 1476 } 1477 1478 @Override 1479 public void requestLimitEdit() { 1480 LimitEditorFragment.show(DataUsageSummary.this); 1481 } 1482 }; 1483 1484 /** 1485 * List item that reflects a specific data usage cycle. 1486 */ 1487 public static class CycleItem implements Comparable<CycleItem> { 1488 public CharSequence label; 1489 public long start; 1490 public long end; 1491 CycleItem(CharSequence label)1492 CycleItem(CharSequence label) { 1493 this.label = label; 1494 } 1495 CycleItem(Context context, long start, long end)1496 public CycleItem(Context context, long start, long end) { 1497 this.label = formatDateRange(context, start, end); 1498 this.start = start; 1499 this.end = end; 1500 } 1501 1502 @Override toString()1503 public String toString() { 1504 return label.toString(); 1505 } 1506 1507 @Override equals(Object o)1508 public boolean equals(Object o) { 1509 if (o instanceof CycleItem) { 1510 final CycleItem another = (CycleItem) o; 1511 return start == another.start && end == another.end; 1512 } 1513 return false; 1514 } 1515 1516 @Override compareTo(CycleItem another)1517 public int compareTo(CycleItem another) { 1518 return Long.compare(start, another.start); 1519 } 1520 } 1521 1522 private static final StringBuilder sBuilder = new StringBuilder(50); 1523 private static final java.util.Formatter sFormatter = new java.util.Formatter( 1524 sBuilder, Locale.getDefault()); 1525 formatDateRange(Context context, long start, long end)1526 public static String formatDateRange(Context context, long start, long end) { 1527 final int flags = FORMAT_SHOW_DATE | FORMAT_ABBREV_MONTH; 1528 1529 synchronized (sBuilder) { 1530 sBuilder.setLength(0); 1531 return DateUtils.formatDateRange(context, sFormatter, start, end, flags, null) 1532 .toString(); 1533 } 1534 } 1535 1536 /** 1537 * Special-case data usage cycle that triggers dialog to change 1538 * {@link NetworkPolicy#cycleDay}. 1539 */ 1540 public static class CycleChangeItem extends CycleItem { CycleChangeItem(Context context)1541 public CycleChangeItem(Context context) { 1542 super(context.getString(R.string.data_usage_change_cycle)); 1543 } 1544 } 1545 1546 public static class CycleAdapter extends ArrayAdapter<CycleItem> { 1547 private boolean mChangePossible = false; 1548 private boolean mChangeVisible = false; 1549 1550 private final CycleChangeItem mChangeItem; 1551 CycleAdapter(Context context)1552 public CycleAdapter(Context context) { 1553 super(context, R.layout.data_usage_cycle_item); 1554 setDropDownViewResource(R.layout.data_usage_cycle_item_dropdown); 1555 mChangeItem = new CycleChangeItem(context); 1556 } 1557 setChangePossible(boolean possible)1558 public void setChangePossible(boolean possible) { 1559 mChangePossible = possible; 1560 updateChange(); 1561 } 1562 setChangeVisible(boolean visible)1563 public void setChangeVisible(boolean visible) { 1564 mChangeVisible = visible; 1565 updateChange(); 1566 } 1567 updateChange()1568 private void updateChange() { 1569 remove(mChangeItem); 1570 if (mChangePossible && mChangeVisible) { 1571 add(mChangeItem); 1572 } 1573 } 1574 1575 /** 1576 * Find position of {@link CycleItem} in this adapter which is nearest 1577 * the given {@link CycleItem}. 1578 */ findNearestPosition(CycleItem target)1579 public int findNearestPosition(CycleItem target) { 1580 if (target != null) { 1581 final int count = getCount(); 1582 for (int i = count - 1; i >= 0; i--) { 1583 final CycleItem item = getItem(i); 1584 if (item instanceof CycleChangeItem) { 1585 continue; 1586 } else if (item.compareTo(target) >= 0) { 1587 return i; 1588 } 1589 } 1590 } 1591 return 0; 1592 } 1593 } 1594 1595 public static class AppItem implements Comparable<AppItem>, Parcelable { 1596 public static final int CATEGORY_USER = 0; 1597 public static final int CATEGORY_APP_TITLE = 1; 1598 public static final int CATEGORY_APP = 2; 1599 1600 public final int key; 1601 public boolean restricted; 1602 public int category; 1603 1604 public SparseBooleanArray uids = new SparseBooleanArray(); 1605 public long total; 1606 AppItem()1607 public AppItem() { 1608 this.key = 0; 1609 } 1610 AppItem(int key)1611 public AppItem(int key) { 1612 this.key = key; 1613 } 1614 AppItem(Parcel parcel)1615 public AppItem(Parcel parcel) { 1616 key = parcel.readInt(); 1617 uids = parcel.readSparseBooleanArray(); 1618 total = parcel.readLong(); 1619 } 1620 addUid(int uid)1621 public void addUid(int uid) { 1622 uids.put(uid, true); 1623 } 1624 1625 @Override writeToParcel(Parcel dest, int flags)1626 public void writeToParcel(Parcel dest, int flags) { 1627 dest.writeInt(key); 1628 dest.writeSparseBooleanArray(uids); 1629 dest.writeLong(total); 1630 } 1631 1632 @Override describeContents()1633 public int describeContents() { 1634 return 0; 1635 } 1636 1637 @Override compareTo(AppItem another)1638 public int compareTo(AppItem another) { 1639 int comparison = Integer.compare(category, another.category); 1640 if (comparison == 0) { 1641 comparison = Long.compare(another.total, total); 1642 } 1643 return comparison; 1644 } 1645 1646 public static final Creator<AppItem> CREATOR = new Creator<AppItem>() { 1647 @Override 1648 public AppItem createFromParcel(Parcel in) { 1649 return new AppItem(in); 1650 } 1651 1652 @Override 1653 public AppItem[] newArray(int size) { 1654 return new AppItem[size]; 1655 } 1656 }; 1657 } 1658 1659 /** 1660 * Adapter of applications, sorted by total usage descending. 1661 */ 1662 public static class DataUsageAdapter extends BaseAdapter { 1663 private final UidDetailProvider mProvider; 1664 private final int mInsetSide; 1665 private final UserManager mUm; 1666 1667 private ArrayList<AppItem> mItems = Lists.newArrayList(); 1668 private long mLargest; 1669 DataUsageAdapter(final UserManager userManager, UidDetailProvider provider, int insetSide)1670 public DataUsageAdapter(final UserManager userManager, UidDetailProvider provider, int insetSide) { 1671 mProvider = checkNotNull(provider); 1672 mInsetSide = insetSide; 1673 mUm = userManager; 1674 } 1675 1676 /** 1677 * Bind the given {@link NetworkStats}, or {@code null} to clear list. 1678 */ bindStats(NetworkStats stats, int[] restrictedUids)1679 public void bindStats(NetworkStats stats, int[] restrictedUids) { 1680 mItems.clear(); 1681 mLargest = 0; 1682 1683 final int currentUserId = ActivityManager.getCurrentUser(); 1684 final List<UserHandle> profiles = mUm.getUserProfiles(); 1685 final SparseArray<AppItem> knownItems = new SparseArray<AppItem>(); 1686 1687 NetworkStats.Entry entry = null; 1688 final int size = stats != null ? stats.size() : 0; 1689 for (int i = 0; i < size; i++) { 1690 entry = stats.getValues(i, entry); 1691 1692 // Decide how to collapse items together 1693 final int uid = entry.uid; 1694 1695 final int collapseKey; 1696 final int category; 1697 final int userId = UserHandle.getUserId(uid); 1698 if (UserHandle.isApp(uid)) { 1699 if (profiles.contains(new UserHandle(userId))) { 1700 if (userId != currentUserId) { 1701 // Add to a managed user item. 1702 final int managedKey = UidDetailProvider.buildKeyForUser(userId); 1703 accumulate(managedKey, knownItems, entry, 1704 AppItem.CATEGORY_USER); 1705 } 1706 // Add to app item. 1707 collapseKey = uid; 1708 category = AppItem.CATEGORY_APP; 1709 } else { 1710 // If it is a removed user add it to the removed users' key 1711 final UserInfo info = mUm.getUserInfo(userId); 1712 if (info == null) { 1713 collapseKey = UID_REMOVED; 1714 category = AppItem.CATEGORY_APP; 1715 } else { 1716 // Add to other user item. 1717 collapseKey = UidDetailProvider.buildKeyForUser(userId); 1718 category = AppItem.CATEGORY_USER; 1719 } 1720 } 1721 } else if (uid == UID_REMOVED || uid == UID_TETHERING) { 1722 collapseKey = uid; 1723 category = AppItem.CATEGORY_APP; 1724 } else { 1725 collapseKey = android.os.Process.SYSTEM_UID; 1726 category = AppItem.CATEGORY_APP; 1727 } 1728 accumulate(collapseKey, knownItems, entry, category); 1729 } 1730 1731 final int restrictedUidsMax = restrictedUids.length; 1732 for (int i = 0; i < restrictedUidsMax; ++i) { 1733 final int uid = restrictedUids[i]; 1734 // Only splice in restricted state for current user or managed users 1735 if (!profiles.contains(new UserHandle(UserHandle.getUserId(uid)))) { 1736 continue; 1737 } 1738 1739 AppItem item = knownItems.get(uid); 1740 if (item == null) { 1741 item = new AppItem(uid); 1742 item.total = -1; 1743 mItems.add(item); 1744 knownItems.put(item.key, item); 1745 } 1746 item.restricted = true; 1747 } 1748 1749 if (!mItems.isEmpty()) { 1750 final AppItem title = new AppItem(); 1751 title.category = AppItem.CATEGORY_APP_TITLE; 1752 mItems.add(title); 1753 } 1754 1755 Collections.sort(mItems); 1756 notifyDataSetChanged(); 1757 } 1758 1759 /** 1760 * Accumulate data usage of a network stats entry for the item mapped by the collapse key. 1761 * Creates the item if needed. 1762 * 1763 * @param collapseKey the collapse key used to map the item. 1764 * @param knownItems collection of known (already existing) items. 1765 * @param entry the network stats entry to extract data usage from. 1766 * @param itemCategory the item is categorized on the list view by this category. Must be 1767 * either AppItem.APP_ITEM_CATEGORY or AppItem.MANAGED_USER_ITEM_CATEGORY 1768 */ accumulate(int collapseKey, final SparseArray<AppItem> knownItems, NetworkStats.Entry entry, int itemCategory)1769 private void accumulate(int collapseKey, final SparseArray<AppItem> knownItems, 1770 NetworkStats.Entry entry, int itemCategory) { 1771 final int uid = entry.uid; 1772 AppItem item = knownItems.get(collapseKey); 1773 if (item == null) { 1774 item = new AppItem(collapseKey); 1775 item.category = itemCategory; 1776 mItems.add(item); 1777 knownItems.put(item.key, item); 1778 } 1779 item.addUid(uid); 1780 item.total += entry.rxBytes + entry.txBytes; 1781 if (mLargest < item.total) { 1782 mLargest = item.total; 1783 } 1784 } 1785 1786 @Override getCount()1787 public int getCount() { 1788 return mItems.size(); 1789 } 1790 1791 @Override getItem(int position)1792 public Object getItem(int position) { 1793 return mItems.get(position); 1794 } 1795 1796 @Override getItemId(int position)1797 public long getItemId(int position) { 1798 return mItems.get(position).key; 1799 } 1800 1801 /** 1802 * See {@link #getItemViewType} for the view types. 1803 */ 1804 @Override getViewTypeCount()1805 public int getViewTypeCount() { 1806 return 2; 1807 } 1808 1809 /** 1810 * Returns 1 for separator items and 0 for anything else. 1811 */ 1812 @Override getItemViewType(int position)1813 public int getItemViewType(int position) { 1814 final AppItem item = mItems.get(position); 1815 if (item.category == AppItem.CATEGORY_APP_TITLE) { 1816 return 1; 1817 } else { 1818 return 0; 1819 } 1820 } 1821 1822 @Override areAllItemsEnabled()1823 public boolean areAllItemsEnabled() { 1824 return false; 1825 } 1826 1827 @Override isEnabled(int position)1828 public boolean isEnabled(int position) { 1829 if (position > mItems.size()) { 1830 throw new ArrayIndexOutOfBoundsException(); 1831 } 1832 return getItemViewType(position) == 0; 1833 } 1834 1835 @Override getView(int position, View convertView, ViewGroup parent)1836 public View getView(int position, View convertView, ViewGroup parent) { 1837 final AppItem item = mItems.get(position); 1838 if (getItemViewType(position) == 1) { 1839 if (convertView == null) { 1840 convertView = inflateCategoryHeader(LayoutInflater.from(parent.getContext()), 1841 parent); 1842 } 1843 1844 final TextView title = (TextView) convertView.findViewById(android.R.id.title); 1845 title.setText(R.string.data_usage_app); 1846 1847 } else { 1848 if (convertView == null) { 1849 convertView = LayoutInflater.from(parent.getContext()).inflate( 1850 R.layout.data_usage_item, parent, false); 1851 1852 if (mInsetSide > 0) { 1853 convertView.setPaddingRelative(mInsetSide, 0, mInsetSide, 0); 1854 } 1855 } 1856 1857 final Context context = parent.getContext(); 1858 1859 final TextView text1 = (TextView) convertView.findViewById(android.R.id.text1); 1860 final ProgressBar progress = (ProgressBar) convertView.findViewById( 1861 android.R.id.progress); 1862 1863 // kick off async load of app details 1864 UidDetailTask.bindView(mProvider, item, convertView); 1865 1866 if (item.restricted && item.total <= 0) { 1867 text1.setText(R.string.data_usage_app_restricted); 1868 progress.setVisibility(View.GONE); 1869 } else { 1870 text1.setText(Formatter.formatFileSize(context, item.total)); 1871 progress.setVisibility(View.VISIBLE); 1872 } 1873 1874 final int percentTotal = mLargest != 0 ? (int) (item.total * 100 / mLargest) : 0; 1875 progress.setProgress(percentTotal); 1876 } 1877 1878 return convertView; 1879 } 1880 } 1881 1882 /** 1883 * Empty {@link Fragment} that controls display of UID details in 1884 * {@link DataUsageSummary}. 1885 */ 1886 public static class AppDetailsFragment extends Fragment { 1887 private static final String EXTRA_APP = "app"; 1888 show(DataUsageSummary parent, AppItem app, CharSequence label)1889 public static void show(DataUsageSummary parent, AppItem app, CharSequence label) { 1890 if (!parent.isAdded()) return; 1891 1892 final Bundle args = new Bundle(); 1893 args.putParcelable(EXTRA_APP, app); 1894 1895 final AppDetailsFragment fragment = new AppDetailsFragment(); 1896 fragment.setArguments(args); 1897 fragment.setTargetFragment(parent, 0); 1898 final FragmentTransaction ft = parent.getFragmentManager().beginTransaction(); 1899 ft.add(fragment, TAG_APP_DETAILS); 1900 ft.addToBackStack(TAG_APP_DETAILS); 1901 ft.setBreadCrumbTitle( 1902 parent.getResources().getString(R.string.data_usage_app_summary_title)); 1903 ft.commitAllowingStateLoss(); 1904 } 1905 1906 @Override onStart()1907 public void onStart() { 1908 super.onStart(); 1909 final DataUsageSummary target = (DataUsageSummary) getTargetFragment(); 1910 target.mCurrentApp = getArguments().getParcelable(EXTRA_APP); 1911 target.updateBody(); 1912 } 1913 1914 @Override onStop()1915 public void onStop() { 1916 super.onStop(); 1917 final DataUsageSummary target = (DataUsageSummary) getTargetFragment(); 1918 target.mCurrentApp = null; 1919 target.updateBody(); 1920 } 1921 } 1922 1923 /** 1924 * Dialog to request user confirmation before setting 1925 * {@link NetworkPolicy#limitBytes}. 1926 */ 1927 public static class ConfirmLimitFragment extends DialogFragment { 1928 private static final String EXTRA_MESSAGE = "message"; 1929 private static final String EXTRA_LIMIT_BYTES = "limitBytes"; 1930 show(DataUsageSummary parent)1931 public static void show(DataUsageSummary parent) { 1932 if (!parent.isAdded()) return; 1933 1934 final NetworkPolicy policy = parent.mPolicyEditor.getPolicy(parent.mTemplate); 1935 if (policy == null) return; 1936 1937 final Resources res = parent.getResources(); 1938 final CharSequence message; 1939 final long minLimitBytes = (long) (policy.warningBytes * 1.2f); 1940 final long limitBytes; 1941 1942 // TODO: customize default limits based on network template 1943 final String currentTab = parent.mCurrentTab; 1944 if (TAB_3G.equals(currentTab)) { 1945 message = res.getString(R.string.data_usage_limit_dialog_mobile); 1946 limitBytes = Math.max(5 * GB_IN_BYTES, minLimitBytes); 1947 } else if (TAB_4G.equals(currentTab)) { 1948 message = res.getString(R.string.data_usage_limit_dialog_mobile); 1949 limitBytes = Math.max(5 * GB_IN_BYTES, minLimitBytes); 1950 } else if (isMobileTab(currentTab)) { 1951 message = res.getString(R.string.data_usage_limit_dialog_mobile); 1952 limitBytes = Math.max(5 * GB_IN_BYTES, minLimitBytes); 1953 } else { 1954 throw new IllegalArgumentException("unknown current tab: " + currentTab); 1955 } 1956 1957 final Bundle args = new Bundle(); 1958 args.putCharSequence(EXTRA_MESSAGE, message); 1959 args.putLong(EXTRA_LIMIT_BYTES, limitBytes); 1960 1961 final ConfirmLimitFragment dialog = new ConfirmLimitFragment(); 1962 dialog.setArguments(args); 1963 dialog.setTargetFragment(parent, 0); 1964 dialog.show(parent.getFragmentManager(), TAG_CONFIRM_LIMIT); 1965 } 1966 1967 @Override onCreateDialog(Bundle savedInstanceState)1968 public Dialog onCreateDialog(Bundle savedInstanceState) { 1969 final Context context = getActivity(); 1970 1971 final CharSequence message = getArguments().getCharSequence(EXTRA_MESSAGE); 1972 final long limitBytes = getArguments().getLong(EXTRA_LIMIT_BYTES); 1973 1974 final AlertDialog.Builder builder = new AlertDialog.Builder(context); 1975 builder.setTitle(R.string.data_usage_limit_dialog_title); 1976 builder.setMessage(message); 1977 1978 builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 1979 @Override 1980 public void onClick(DialogInterface dialog, int which) { 1981 final DataUsageSummary target = (DataUsageSummary) getTargetFragment(); 1982 if (target != null) { 1983 target.setPolicyLimitBytes(limitBytes); 1984 } 1985 } 1986 }); 1987 1988 return builder.create(); 1989 } 1990 } 1991 1992 /** 1993 * Dialog to edit {@link NetworkPolicy#cycleDay}. 1994 */ 1995 public static class CycleEditorFragment extends DialogFragment { 1996 private static final String EXTRA_TEMPLATE = "template"; 1997 show(DataUsageSummary parent)1998 public static void show(DataUsageSummary parent) { 1999 if (!parent.isAdded()) return; 2000 2001 final Bundle args = new Bundle(); 2002 args.putParcelable(EXTRA_TEMPLATE, parent.mTemplate); 2003 2004 final CycleEditorFragment dialog = new CycleEditorFragment(); 2005 dialog.setArguments(args); 2006 dialog.setTargetFragment(parent, 0); 2007 dialog.show(parent.getFragmentManager(), TAG_CYCLE_EDITOR); 2008 } 2009 2010 @Override onCreateDialog(Bundle savedInstanceState)2011 public Dialog onCreateDialog(Bundle savedInstanceState) { 2012 final Context context = getActivity(); 2013 final DataUsageSummary target = (DataUsageSummary) getTargetFragment(); 2014 final NetworkPolicyEditor editor = target.mPolicyEditor; 2015 2016 final AlertDialog.Builder builder = new AlertDialog.Builder(context); 2017 final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext()); 2018 2019 final View view = dialogInflater.inflate(R.layout.data_usage_cycle_editor, null, false); 2020 final NumberPicker cycleDayPicker = (NumberPicker) view.findViewById(R.id.cycle_day); 2021 2022 final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE); 2023 final int cycleDay = editor.getPolicyCycleDay(template); 2024 2025 cycleDayPicker.setMinValue(1); 2026 cycleDayPicker.setMaxValue(31); 2027 cycleDayPicker.setValue(cycleDay); 2028 cycleDayPicker.setWrapSelectorWheel(true); 2029 2030 builder.setTitle(R.string.data_usage_cycle_editor_title); 2031 builder.setView(view); 2032 2033 builder.setPositiveButton(R.string.data_usage_cycle_editor_positive, 2034 new DialogInterface.OnClickListener() { 2035 @Override 2036 public void onClick(DialogInterface dialog, int which) { 2037 // clear focus to finish pending text edits 2038 cycleDayPicker.clearFocus(); 2039 2040 final int cycleDay = cycleDayPicker.getValue(); 2041 final String cycleTimezone = new Time().timezone; 2042 editor.setPolicyCycleDay(template, cycleDay, cycleTimezone); 2043 target.updatePolicy(true); 2044 } 2045 }); 2046 2047 return builder.create(); 2048 } 2049 } 2050 2051 /** 2052 * Dialog to edit {@link NetworkPolicy#warningBytes}. 2053 */ 2054 public static class WarningEditorFragment extends DialogFragment { 2055 private static final String EXTRA_TEMPLATE = "template"; 2056 show(DataUsageSummary parent)2057 public static void show(DataUsageSummary parent) { 2058 if (!parent.isAdded()) return; 2059 2060 final Bundle args = new Bundle(); 2061 args.putParcelable(EXTRA_TEMPLATE, parent.mTemplate); 2062 2063 final WarningEditorFragment dialog = new WarningEditorFragment(); 2064 dialog.setArguments(args); 2065 dialog.setTargetFragment(parent, 0); 2066 dialog.show(parent.getFragmentManager(), TAG_WARNING_EDITOR); 2067 } 2068 2069 @Override onCreateDialog(Bundle savedInstanceState)2070 public Dialog onCreateDialog(Bundle savedInstanceState) { 2071 final Context context = getActivity(); 2072 final DataUsageSummary target = (DataUsageSummary) getTargetFragment(); 2073 final NetworkPolicyEditor editor = target.mPolicyEditor; 2074 2075 final AlertDialog.Builder builder = new AlertDialog.Builder(context); 2076 final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext()); 2077 2078 final View view = dialogInflater.inflate(R.layout.data_usage_bytes_editor, null, false); 2079 final NumberPicker bytesPicker = (NumberPicker) view.findViewById(R.id.bytes); 2080 2081 final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE); 2082 final long warningBytes = editor.getPolicyWarningBytes(template); 2083 final long limitBytes = editor.getPolicyLimitBytes(template); 2084 2085 bytesPicker.setMinValue(0); 2086 if (limitBytes != LIMIT_DISABLED) { 2087 bytesPicker.setMaxValue((int) (limitBytes / MB_IN_BYTES) - 1); 2088 } else { 2089 bytesPicker.setMaxValue(Integer.MAX_VALUE); 2090 } 2091 bytesPicker.setValue((int) (warningBytes / MB_IN_BYTES)); 2092 bytesPicker.setWrapSelectorWheel(false); 2093 2094 builder.setTitle(R.string.data_usage_warning_editor_title); 2095 builder.setView(view); 2096 2097 builder.setPositiveButton(R.string.data_usage_cycle_editor_positive, 2098 new DialogInterface.OnClickListener() { 2099 @Override 2100 public void onClick(DialogInterface dialog, int which) { 2101 // clear focus to finish pending text edits 2102 bytesPicker.clearFocus(); 2103 2104 final long bytes = bytesPicker.getValue() * MB_IN_BYTES; 2105 editor.setPolicyWarningBytes(template, bytes); 2106 target.updatePolicy(false); 2107 } 2108 }); 2109 2110 return builder.create(); 2111 } 2112 } 2113 2114 /** 2115 * Dialog to edit {@link NetworkPolicy#limitBytes}. 2116 */ 2117 public static class LimitEditorFragment extends DialogFragment { 2118 private static final String EXTRA_TEMPLATE = "template"; 2119 show(DataUsageSummary parent)2120 public static void show(DataUsageSummary parent) { 2121 if (!parent.isAdded()) return; 2122 2123 final Bundle args = new Bundle(); 2124 args.putParcelable(EXTRA_TEMPLATE, parent.mTemplate); 2125 2126 final LimitEditorFragment dialog = new LimitEditorFragment(); 2127 dialog.setArguments(args); 2128 dialog.setTargetFragment(parent, 0); 2129 dialog.show(parent.getFragmentManager(), TAG_LIMIT_EDITOR); 2130 } 2131 2132 @Override onCreateDialog(Bundle savedInstanceState)2133 public Dialog onCreateDialog(Bundle savedInstanceState) { 2134 final Context context = getActivity(); 2135 final DataUsageSummary target = (DataUsageSummary) getTargetFragment(); 2136 final NetworkPolicyEditor editor = target.mPolicyEditor; 2137 2138 final AlertDialog.Builder builder = new AlertDialog.Builder(context); 2139 final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext()); 2140 2141 final View view = dialogInflater.inflate(R.layout.data_usage_bytes_editor, null, false); 2142 final NumberPicker bytesPicker = (NumberPicker) view.findViewById(R.id.bytes); 2143 2144 final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE); 2145 final long warningBytes = editor.getPolicyWarningBytes(template); 2146 final long limitBytes = editor.getPolicyLimitBytes(template); 2147 2148 bytesPicker.setMaxValue(Integer.MAX_VALUE); 2149 if (warningBytes != WARNING_DISABLED && limitBytes > 0) { 2150 bytesPicker.setMinValue((int) (warningBytes / MB_IN_BYTES) + 1); 2151 } else { 2152 bytesPicker.setMinValue(0); 2153 } 2154 bytesPicker.setValue((int) (limitBytes / MB_IN_BYTES)); 2155 bytesPicker.setWrapSelectorWheel(false); 2156 2157 builder.setTitle(R.string.data_usage_limit_editor_title); 2158 builder.setView(view); 2159 2160 builder.setPositiveButton(R.string.data_usage_cycle_editor_positive, 2161 new DialogInterface.OnClickListener() { 2162 @Override 2163 public void onClick(DialogInterface dialog, int which) { 2164 // clear focus to finish pending text edits 2165 bytesPicker.clearFocus(); 2166 2167 final long bytes = bytesPicker.getValue() * MB_IN_BYTES; 2168 editor.setPolicyLimitBytes(template, bytes); 2169 target.updatePolicy(false); 2170 } 2171 }); 2172 2173 return builder.create(); 2174 } 2175 } 2176 /** 2177 * Dialog to request user confirmation before disabling data. 2178 */ 2179 public static class ConfirmDataDisableFragment extends DialogFragment { 2180 static int mSubId; show(DataUsageSummary parent, int subId)2181 public static void show(DataUsageSummary parent, int subId) { 2182 mSubId = subId; 2183 if (!parent.isAdded()) return; 2184 2185 final ConfirmDataDisableFragment dialog = new ConfirmDataDisableFragment(); 2186 dialog.setTargetFragment(parent, 0); 2187 dialog.show(parent.getFragmentManager(), TAG_CONFIRM_DATA_DISABLE); 2188 } 2189 2190 @Override onCreateDialog(Bundle savedInstanceState)2191 public Dialog onCreateDialog(Bundle savedInstanceState) { 2192 final Context context = getActivity(); 2193 2194 final AlertDialog.Builder builder = new AlertDialog.Builder(context); 2195 builder.setMessage(R.string.data_usage_disable_mobile); 2196 2197 builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 2198 @Override 2199 public void onClick(DialogInterface dialog, int which) { 2200 final DataUsageSummary target = (DataUsageSummary) getTargetFragment(); 2201 if (target != null) { 2202 // TODO: extend to modify policy enabled flag. 2203 target.setMobileDataEnabled(mSubId, false); 2204 } 2205 } 2206 }); 2207 builder.setNegativeButton(android.R.string.cancel, null); 2208 2209 return builder.create(); 2210 } 2211 } 2212 2213 /** 2214 * Dialog to request user confirmation before setting 2215 * {@link INetworkPolicyManager#setRestrictBackground(boolean)}. 2216 */ 2217 public static class ConfirmRestrictFragment extends DialogFragment { show(DataUsageSummary parent)2218 public static void show(DataUsageSummary parent) { 2219 if (!parent.isAdded()) return; 2220 2221 final ConfirmRestrictFragment dialog = new ConfirmRestrictFragment(); 2222 dialog.setTargetFragment(parent, 0); 2223 dialog.show(parent.getFragmentManager(), TAG_CONFIRM_RESTRICT); 2224 } 2225 2226 @Override onCreateDialog(Bundle savedInstanceState)2227 public Dialog onCreateDialog(Bundle savedInstanceState) { 2228 final Context context = getActivity(); 2229 2230 final AlertDialog.Builder builder = new AlertDialog.Builder(context); 2231 builder.setTitle(R.string.data_usage_restrict_background_title); 2232 if (Utils.hasMultipleUsers(context)) { 2233 builder.setMessage(R.string.data_usage_restrict_background_multiuser); 2234 } else { 2235 builder.setMessage(R.string.data_usage_restrict_background); 2236 } 2237 2238 builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 2239 @Override 2240 public void onClick(DialogInterface dialog, int which) { 2241 final DataUsageSummary target = (DataUsageSummary) getTargetFragment(); 2242 if (target != null) { 2243 target.setRestrictBackground(true); 2244 } 2245 } 2246 }); 2247 builder.setNegativeButton(android.R.string.cancel, null); 2248 2249 return builder.create(); 2250 } 2251 } 2252 2253 /** 2254 * Dialog to inform user that {@link #POLICY_REJECT_METERED_BACKGROUND} 2255 * change has been denied, usually based on 2256 * {@link DataUsageSummary#hasLimitedNetworks()}. 2257 */ 2258 public static class DeniedRestrictFragment extends DialogFragment { show(DataUsageSummary parent)2259 public static void show(DataUsageSummary parent) { 2260 if (!parent.isAdded()) return; 2261 2262 final DeniedRestrictFragment dialog = new DeniedRestrictFragment(); 2263 dialog.setTargetFragment(parent, 0); 2264 dialog.show(parent.getFragmentManager(), TAG_DENIED_RESTRICT); 2265 } 2266 2267 @Override onCreateDialog(Bundle savedInstanceState)2268 public Dialog onCreateDialog(Bundle savedInstanceState) { 2269 final Context context = getActivity(); 2270 2271 final AlertDialog.Builder builder = new AlertDialog.Builder(context); 2272 builder.setTitle(R.string.data_usage_app_restrict_background); 2273 builder.setMessage(R.string.data_usage_restrict_denied_dialog); 2274 builder.setPositiveButton(android.R.string.ok, null); 2275 2276 return builder.create(); 2277 } 2278 } 2279 2280 /** 2281 * Dialog to request user confirmation before setting 2282 * {@link #POLICY_REJECT_METERED_BACKGROUND}. 2283 */ 2284 public static class ConfirmAppRestrictFragment extends DialogFragment { show(DataUsageSummary parent)2285 public static void show(DataUsageSummary parent) { 2286 if (!parent.isAdded()) return; 2287 2288 final ConfirmAppRestrictFragment dialog = new ConfirmAppRestrictFragment(); 2289 dialog.setTargetFragment(parent, 0); 2290 dialog.show(parent.getFragmentManager(), TAG_CONFIRM_APP_RESTRICT); 2291 } 2292 2293 @Override onCreateDialog(Bundle savedInstanceState)2294 public Dialog onCreateDialog(Bundle savedInstanceState) { 2295 final Context context = getActivity(); 2296 2297 final AlertDialog.Builder builder = new AlertDialog.Builder(context); 2298 builder.setTitle(R.string.data_usage_app_restrict_dialog_title); 2299 builder.setMessage(R.string.data_usage_app_restrict_dialog); 2300 2301 builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 2302 @Override 2303 public void onClick(DialogInterface dialog, int which) { 2304 final DataUsageSummary target = (DataUsageSummary) getTargetFragment(); 2305 if (target != null) { 2306 target.setAppRestrictBackground(true); 2307 } 2308 } 2309 }); 2310 builder.setNegativeButton(android.R.string.cancel, null); 2311 2312 return builder.create(); 2313 } 2314 } 2315 2316 /** 2317 * Compute default tab that should be selected, based on 2318 * {@link NetworkPolicyManager#EXTRA_NETWORK_TEMPLATE} extra. 2319 */ computeTabFromIntent(Intent intent)2320 private static String computeTabFromIntent(Intent intent) { 2321 final NetworkTemplate template = intent.getParcelableExtra(EXTRA_NETWORK_TEMPLATE); 2322 if (template == null) { 2323 final int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, 2324 SubscriptionManager.INVALID_SUBSCRIPTION_ID); 2325 if (SubscriptionManager.isValidSubscriptionId(subId)) { 2326 return TAB_MOBILE + String.valueOf(subId); 2327 } 2328 return null; 2329 } 2330 2331 switch (template.getMatchRule()) { 2332 case MATCH_MOBILE_3G_LOWER: 2333 return TAB_3G; 2334 case MATCH_MOBILE_4G: 2335 return TAB_4G; 2336 case MATCH_MOBILE_ALL: 2337 return TAB_MOBILE; 2338 case MATCH_WIFI: 2339 return TAB_WIFI; 2340 default: 2341 return null; 2342 } 2343 } 2344 2345 /** 2346 * Background task that loads {@link UidDetail}, binding to 2347 * {@link DataUsageAdapter} row item when finished. 2348 */ 2349 private static class UidDetailTask extends AsyncTask<Void, Void, UidDetail> { 2350 private final UidDetailProvider mProvider; 2351 private final AppItem mItem; 2352 private final View mTarget; 2353 UidDetailTask(UidDetailProvider provider, AppItem item, View target)2354 private UidDetailTask(UidDetailProvider provider, AppItem item, View target) { 2355 mProvider = checkNotNull(provider); 2356 mItem = checkNotNull(item); 2357 mTarget = checkNotNull(target); 2358 } 2359 bindView( UidDetailProvider provider, AppItem item, View target)2360 public static void bindView( 2361 UidDetailProvider provider, AppItem item, View target) { 2362 final UidDetailTask existing = (UidDetailTask) target.getTag(); 2363 if (existing != null) { 2364 existing.cancel(false); 2365 } 2366 2367 final UidDetail cachedDetail = provider.getUidDetail(item.key, false); 2368 if (cachedDetail != null) { 2369 bindView(cachedDetail, target); 2370 } else { 2371 target.setTag(new UidDetailTask(provider, item, target).executeOnExecutor( 2372 AsyncTask.THREAD_POOL_EXECUTOR)); 2373 } 2374 } 2375 bindView(UidDetail detail, View target)2376 private static void bindView(UidDetail detail, View target) { 2377 final ImageView icon = (ImageView) target.findViewById(android.R.id.icon); 2378 final TextView title = (TextView) target.findViewById(android.R.id.title); 2379 2380 if (detail != null) { 2381 icon.setImageDrawable(detail.icon); 2382 title.setText(detail.label); 2383 title.setContentDescription(detail.contentDescription); 2384 } else { 2385 icon.setImageDrawable(null); 2386 title.setText(null); 2387 } 2388 } 2389 2390 @Override onPreExecute()2391 protected void onPreExecute() { 2392 bindView(null, mTarget); 2393 } 2394 2395 @Override doInBackground(Void... params)2396 protected UidDetail doInBackground(Void... params) { 2397 return mProvider.getUidDetail(mItem.key, true); 2398 } 2399 2400 @Override onPostExecute(UidDetail result)2401 protected void onPostExecute(UidDetail result) { 2402 bindView(result, mTarget); 2403 } 2404 } 2405 2406 /** 2407 * Test if device has a mobile data radio with SIM in ready state. 2408 */ hasReadyMobileRadio(Context context)2409 public static boolean hasReadyMobileRadio(Context context) { 2410 if (TEST_RADIOS) { 2411 return SystemProperties.get(TEST_RADIOS_PROP).contains("mobile"); 2412 } 2413 2414 final ConnectivityManager conn = ConnectivityManager.from(context); 2415 final TelephonyManager tele = TelephonyManager.from(context); 2416 2417 final List<SubscriptionInfo> subInfoList = 2418 SubscriptionManager.from(context).getActiveSubscriptionInfoList(); 2419 // No activated Subscriptions 2420 if (subInfoList == null) { 2421 if (LOGD) Log.d(TAG, "hasReadyMobileRadio: subInfoList=null"); 2422 return false; 2423 } 2424 // require both supported network and ready SIM 2425 boolean isReady = true; 2426 for (SubscriptionInfo subInfo : subInfoList) { 2427 isReady = isReady & tele.getSimState(subInfo.getSimSlotIndex()) == SIM_STATE_READY; 2428 if (LOGD) Log.d(TAG, "hasReadyMobileRadio: subInfo=" + subInfo); 2429 } 2430 boolean retVal = conn.isNetworkSupported(TYPE_MOBILE) && isReady; 2431 if (LOGD) { 2432 Log.d(TAG, "hasReadyMobileRadio:" 2433 + " conn.isNetworkSupported(TYPE_MOBILE)=" 2434 + conn.isNetworkSupported(TYPE_MOBILE) 2435 + " isReady=" + isReady); 2436 } 2437 return retVal; 2438 } 2439 2440 /* 2441 * TODO: consider adding to TelephonyManager or SubscritpionManager. 2442 */ hasReadyMobileRadio(Context context, int subId)2443 public static boolean hasReadyMobileRadio(Context context, int subId) { 2444 if (TEST_RADIOS) { 2445 return SystemProperties.get(TEST_RADIOS_PROP).contains("mobile"); 2446 } 2447 2448 final ConnectivityManager conn = ConnectivityManager.from(context); 2449 final TelephonyManager tele = TelephonyManager.from(context); 2450 final int slotId = SubscriptionManager.getSlotId(subId); 2451 final boolean isReady = tele.getSimState(slotId) == SIM_STATE_READY; 2452 2453 boolean retVal = conn.isNetworkSupported(TYPE_MOBILE) && isReady; 2454 if (LOGD) Log.d(TAG, "hasReadyMobileRadio: subId=" + subId 2455 + " conn.isNetworkSupported(TYPE_MOBILE)=" + conn.isNetworkSupported(TYPE_MOBILE) 2456 + " isReady=" + isReady); 2457 return retVal; 2458 } 2459 2460 /** 2461 * Test if device has a mobile 4G data radio. 2462 */ hasReadyMobile4gRadio(Context context)2463 public static boolean hasReadyMobile4gRadio(Context context) { 2464 if (!NetworkPolicyEditor.ENABLE_SPLIT_POLICIES) { 2465 return false; 2466 } 2467 if (TEST_RADIOS) { 2468 return SystemProperties.get(TEST_RADIOS_PROP).contains("4g"); 2469 } 2470 2471 final ConnectivityManager conn = ConnectivityManager.from(context); 2472 final TelephonyManager tele = TelephonyManager.from(context); 2473 2474 final boolean hasWimax = conn.isNetworkSupported(TYPE_WIMAX); 2475 final boolean hasLte = (tele.getLteOnCdmaMode() == PhoneConstants.LTE_ON_CDMA_TRUE) 2476 && hasReadyMobileRadio(context); 2477 return hasWimax || hasLte; 2478 } 2479 2480 /** 2481 * Test if device has a Wi-Fi data radio. 2482 */ hasWifiRadio(Context context)2483 public static boolean hasWifiRadio(Context context) { 2484 if (TEST_RADIOS) { 2485 return SystemProperties.get(TEST_RADIOS_PROP).contains("wifi"); 2486 } 2487 2488 final ConnectivityManager conn = ConnectivityManager.from(context); 2489 return conn.isNetworkSupported(TYPE_WIFI); 2490 } 2491 2492 /** 2493 * Test if device has an ethernet network connection. 2494 */ hasEthernet(Context context)2495 public boolean hasEthernet(Context context) { 2496 if (TEST_RADIOS) { 2497 return SystemProperties.get(TEST_RADIOS_PROP).contains("ethernet"); 2498 } 2499 2500 final ConnectivityManager conn = ConnectivityManager.from(context); 2501 final boolean hasEthernet = conn.isNetworkSupported(TYPE_ETHERNET); 2502 2503 final long ethernetBytes; 2504 if (mStatsSession != null) { 2505 try { 2506 ethernetBytes = mStatsSession.getSummaryForNetwork( 2507 NetworkTemplate.buildTemplateEthernet(), Long.MIN_VALUE, Long.MAX_VALUE) 2508 .getTotalBytes(); 2509 } catch (RemoteException e) { 2510 throw new RuntimeException(e); 2511 } 2512 } else { 2513 ethernetBytes = 0; 2514 } 2515 2516 // only show ethernet when both hardware present and traffic has occurred 2517 return hasEthernet && ethernetBytes > 0; 2518 } 2519 2520 /** 2521 * Inflate a {@link Preference} style layout, adding the given {@link View} 2522 * widget into {@link android.R.id#widget_frame}. 2523 */ inflatePreference(LayoutInflater inflater, ViewGroup root, View widget)2524 private static View inflatePreference(LayoutInflater inflater, ViewGroup root, View widget) { 2525 final View view = inflater.inflate(R.layout.preference, root, false); 2526 final LinearLayout widgetFrame = (LinearLayout) view.findViewById( 2527 android.R.id.widget_frame); 2528 widgetFrame.addView(widget, new LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); 2529 return view; 2530 } 2531 inflateCategoryHeader(LayoutInflater inflater, ViewGroup root)2532 private static View inflateCategoryHeader(LayoutInflater inflater, ViewGroup root) { 2533 final TypedArray a = inflater.getContext().obtainStyledAttributes(null, 2534 com.android.internal.R.styleable.Preference, 2535 com.android.internal.R.attr.preferenceCategoryStyle, 0); 2536 final int resId = a.getResourceId(com.android.internal.R.styleable.Preference_layout, 0); 2537 return inflater.inflate(resId, root, false); 2538 } 2539 2540 /** 2541 * Test if any networks are currently limited. 2542 */ hasLimitedNetworks()2543 private boolean hasLimitedNetworks() { 2544 return !buildLimitedNetworksList().isEmpty(); 2545 } 2546 2547 /** 2548 * Build string describing currently limited networks, which defines when 2549 * background data is restricted. 2550 */ 2551 @Deprecated buildLimitedNetworksString()2552 private CharSequence buildLimitedNetworksString() { 2553 final List<CharSequence> limited = buildLimitedNetworksList(); 2554 2555 // handle case where no networks limited 2556 if (limited.isEmpty()) { 2557 limited.add(getText(R.string.data_usage_list_none)); 2558 } 2559 2560 return TextUtils.join(limited); 2561 } 2562 2563 /** 2564 * Build list of currently limited networks, which defines when background 2565 * data is restricted. 2566 */ 2567 @Deprecated buildLimitedNetworksList()2568 private List<CharSequence> buildLimitedNetworksList() { 2569 final Context context = getActivity(); 2570 2571 // build combined list of all limited networks 2572 final ArrayList<CharSequence> limited = Lists.newArrayList(); 2573 2574 final TelephonyManager tele = TelephonyManager.from(context); 2575 if (tele.getSimState() == SIM_STATE_READY) { 2576 final String subscriberId = getActiveSubscriberId(context); 2577 if (mPolicyEditor.hasLimitedPolicy(buildTemplateMobileAll(subscriberId))) { 2578 limited.add(getText(R.string.data_usage_list_mobile)); 2579 } 2580 if (mPolicyEditor.hasLimitedPolicy(buildTemplateMobile3gLower(subscriberId))) { 2581 limited.add(getText(R.string.data_usage_tab_3g)); 2582 } 2583 if (mPolicyEditor.hasLimitedPolicy(buildTemplateMobile4g(subscriberId))) { 2584 limited.add(getText(R.string.data_usage_tab_4g)); 2585 } 2586 } 2587 2588 if (mPolicyEditor.hasLimitedPolicy(buildTemplateWifiWildcard())) { 2589 limited.add(getText(R.string.data_usage_tab_wifi)); 2590 } 2591 if (mPolicyEditor.hasLimitedPolicy(buildTemplateEthernet())) { 2592 limited.add(getText(R.string.data_usage_tab_ethernet)); 2593 } 2594 2595 return limited; 2596 } 2597 2598 /** 2599 * Inset both selector and divider {@link Drawable} on the given 2600 * {@link ListView} by the requested dimensions. 2601 */ insetListViewDrawables(ListView view, int insetSide)2602 private static void insetListViewDrawables(ListView view, int insetSide) { 2603 final Drawable selector = view.getSelector(); 2604 final Drawable divider = view.getDivider(); 2605 2606 // fully unregister these drawables so callbacks can be maintained after 2607 // wrapping below. 2608 final Drawable stub = new ColorDrawable(Color.TRANSPARENT); 2609 view.setSelector(stub); 2610 view.setDivider(stub); 2611 2612 view.setSelector(new InsetBoundsDrawable(selector, insetSide)); 2613 view.setDivider(new InsetBoundsDrawable(divider, insetSide)); 2614 } 2615 2616 /** 2617 * Set {@link android.R.id#title} for a preference view inflated with 2618 * {@link #inflatePreference(LayoutInflater, ViewGroup, View)}. 2619 */ setPreferenceTitle(View parent, int resId)2620 private static void setPreferenceTitle(View parent, int resId) { 2621 final TextView title = (TextView) parent.findViewById(android.R.id.title); 2622 title.setText(resId); 2623 } 2624 2625 /** 2626 * Set {@link android.R.id#summary} for a preference view inflated with 2627 * {@link #inflatePreference(LayoutInflater, ViewGroup, View)}. 2628 */ setPreferenceSummary(View parent, CharSequence string)2629 private static void setPreferenceSummary(View parent, CharSequence string) { 2630 final TextView summary = (TextView) parent.findViewById(android.R.id.summary); 2631 summary.setVisibility(View.VISIBLE); 2632 summary.setText(string); 2633 } 2634 2635 /** 2636 * For search 2637 */ 2638 public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = 2639 new BaseSearchIndexProvider() { 2640 @Override 2641 public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) { 2642 final List<SearchIndexableRaw> result = new ArrayList<SearchIndexableRaw>(); 2643 2644 final Resources res = context.getResources(); 2645 2646 // Add fragment title 2647 SearchIndexableRaw data = new SearchIndexableRaw(context); 2648 data.title = res.getString(R.string.data_usage_summary_title); 2649 data.screenTitle = res.getString(R.string.data_usage_summary_title); 2650 result.add(data); 2651 2652 // Mobile data 2653 data = new SearchIndexableRaw(context); 2654 data.key = DATA_USAGE_ENABLE_MOBILE_KEY; 2655 data.title = res.getString(R.string.data_usage_enable_mobile); 2656 data.screenTitle = res.getString(R.string.data_usage_summary_title); 2657 result.add(data); 2658 2659 // Set mobile data limit 2660 data = new SearchIndexableRaw(context); 2661 data.key = DATA_USAGE_DISABLE_MOBILE_LIMIT_KEY; 2662 data.title = res.getString(R.string.data_usage_disable_mobile_limit); 2663 data.screenTitle = res.getString(R.string.data_usage_summary_title); 2664 result.add(data); 2665 2666 // Data usage cycle 2667 data = new SearchIndexableRaw(context); 2668 data.key = DATA_USAGE_CYCLE_KEY; 2669 data.title = res.getString(R.string.data_usage_cycle); 2670 data.screenTitle = res.getString(R.string.data_usage_summary_title); 2671 result.add(data); 2672 2673 return result; 2674 } 2675 }; 2676 addMobileTab(Context context, SubscriptionInfo subInfo, boolean isMultiSim)2677 private void addMobileTab(Context context, SubscriptionInfo subInfo, boolean isMultiSim) { 2678 if (subInfo != null && mMobileTagMap != null) { 2679 if (hasReadyMobileRadio(context, subInfo.getSubscriptionId())) { 2680 if (isMultiSim) { 2681 mTabHost.addTab(buildTabSpec(mMobileTagMap.get(subInfo.getSubscriptionId()), 2682 subInfo.getDisplayName())); 2683 } else { 2684 mTabHost.addTab(buildTabSpec(mMobileTagMap.get(subInfo.getSubscriptionId()), 2685 R.string.data_usage_tab_mobile)); 2686 } 2687 } 2688 } else { 2689 if (LOGD) Log.d(TAG, "addMobileTab: subInfoList is null"); 2690 } 2691 } 2692 getCurrentTabSubInfo(Context context)2693 private SubscriptionInfo getCurrentTabSubInfo(Context context) { 2694 if (mSubInfoList != null && mTabHost != null) { 2695 final int currentTagIndex = mTabHost.getCurrentTab(); 2696 int i = 0; 2697 for (SubscriptionInfo subInfo : mSubInfoList) { 2698 if (hasReadyMobileRadio(context, subInfo.getSubscriptionId())) { 2699 if (i++ == currentTagIndex) { 2700 return subInfo; 2701 } 2702 } 2703 } 2704 } 2705 return null; 2706 } 2707 2708 /** 2709 * Init a map with subId key and mobile tag name 2710 * @param subInfoList The subscription Info List 2711 * @return The map or null if no activated subscription 2712 */ initMobileTabTag(List<SubscriptionInfo> subInfoList)2713 private Map<Integer, String> initMobileTabTag(List<SubscriptionInfo> subInfoList) { 2714 Map<Integer, String> map = null; 2715 if (subInfoList != null) { 2716 String mobileTag; 2717 map = new HashMap<Integer, String>(); 2718 for (SubscriptionInfo subInfo : subInfoList) { 2719 mobileTag = TAB_MOBILE + String.valueOf(subInfo.getSubscriptionId()); 2720 map.put(subInfo.getSubscriptionId(), mobileTag); 2721 } 2722 } 2723 return map; 2724 } 2725 isMobileTab(String currentTab)2726 private static boolean isMobileTab(String currentTab) { 2727 return currentTab != null ? currentTab.contains(TAB_MOBILE) : false; 2728 } 2729 getSubId(String currentTab)2730 private int getSubId(String currentTab) { 2731 if (mMobileTagMap != null) { 2732 Set<Integer> set = mMobileTagMap.keySet(); 2733 for (Integer subId : set) { 2734 if (mMobileTagMap.get(subId).equals(currentTab)) { 2735 return subId; 2736 } 2737 } 2738 } 2739 Log.e(TAG, "currentTab = " + currentTab + " non mobile tab called this function"); 2740 return -1; 2741 } 2742 2743 //SUB SELECT isMobileDataAvailable(long subId)2744 private boolean isMobileDataAvailable(long subId) { 2745 int[] subIds = SubscriptionManager.getSubId(PhoneConstants.SUB1); 2746 if (subIds != null && subIds[0] == subId) { 2747 return true; 2748 } 2749 2750 subIds = SubscriptionManager.getSubId(PhoneConstants.SUB2); 2751 if (subIds != null && subIds[0] == subId) { 2752 return true; 2753 } 2754 2755 subIds = SubscriptionManager.getSubId(PhoneConstants.SUB3); 2756 if (subIds != null && subIds[0] == subId) { 2757 return true; 2758 } 2759 return false; 2760 } 2761 } 2762