1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 * except in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the 10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 11 * KIND, either express or implied. See the License for the specific language governing 12 * permissions and limitations under the License. 13 */ 14 15 package com.android.settings.datausage; 16 17 import static android.app.usage.NetworkStats.Bucket.UID_REMOVED; 18 import static android.app.usage.NetworkStats.Bucket.UID_TETHERING; 19 import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND; 20 21 import android.app.Activity; 22 import android.app.ActivityManager; 23 import android.app.settings.SettingsEnums; 24 import android.app.usage.NetworkStats; 25 import android.app.usage.NetworkStats.Bucket; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.pm.UserInfo; 29 import android.graphics.Color; 30 import android.net.ConnectivityManager; 31 import android.net.NetworkPolicy; 32 import android.net.NetworkTemplate; 33 import android.os.Bundle; 34 import android.os.Process; 35 import android.os.UserHandle; 36 import android.os.UserManager; 37 import android.provider.Settings; 38 import android.telephony.SubscriptionInfo; 39 import android.telephony.SubscriptionManager; 40 import android.util.EventLog; 41 import android.util.Log; 42 import android.util.SparseArray; 43 import android.view.View; 44 import android.view.View.AccessibilityDelegate; 45 import android.view.accessibility.AccessibilityEvent; 46 import android.widget.AdapterView; 47 import android.widget.AdapterView.OnItemSelectedListener; 48 import android.widget.ImageView; 49 import android.widget.Spinner; 50 51 import androidx.annotation.VisibleForTesting; 52 import androidx.lifecycle.Lifecycle; 53 import androidx.loader.app.LoaderManager.LoaderCallbacks; 54 import androidx.loader.content.Loader; 55 import androidx.preference.Preference; 56 import androidx.preference.PreferenceGroup; 57 58 import com.android.settings.R; 59 import com.android.settings.core.SubSettingLauncher; 60 import com.android.settings.datausage.CycleAdapter.SpinnerInterface; 61 import com.android.settings.network.MobileDataEnabledListener; 62 import com.android.settings.network.MobileNetworkRepository; 63 import com.android.settings.network.ProxySubscriptionManager; 64 import com.android.settings.widget.LoadingViewController; 65 import com.android.settingslib.AppItem; 66 import com.android.settingslib.mobile.dataservice.SubscriptionInfoEntity; 67 import com.android.settingslib.net.NetworkCycleChartData; 68 import com.android.settingslib.net.NetworkCycleChartDataLoader; 69 import com.android.settingslib.net.NetworkStatsSummaryLoader; 70 import com.android.settingslib.net.UidDetail; 71 import com.android.settingslib.net.UidDetailProvider; 72 import com.android.settingslib.utils.ThreadUtils; 73 74 import java.util.ArrayList; 75 import java.util.Arrays; 76 import java.util.Collections; 77 import java.util.List; 78 import java.util.Objects; 79 import java.util.Optional; 80 81 /** 82 * Panel showing data usage history across various networks, including options 83 * to inspect based on usage cycle and control through {@link NetworkPolicy}. 84 */ 85 public class DataUsageList extends DataUsageBaseFragment 86 implements MobileDataEnabledListener.Client { 87 88 static final String EXTRA_SUB_ID = "sub_id"; 89 static final String EXTRA_NETWORK_TEMPLATE = "network_template"; 90 static final String EXTRA_NETWORK_TYPE = "network_type"; 91 92 private static final String TAG = "DataUsageList"; 93 private static final boolean LOGD = false; 94 95 private static final String KEY_USAGE_AMOUNT = "usage_amount"; 96 private static final String KEY_CHART_DATA = "chart_data"; 97 private static final String KEY_APPS_GROUP = "apps_group"; 98 private static final String KEY_TEMPLATE = "template"; 99 private static final String KEY_APP = "app"; 100 101 @VisibleForTesting 102 static final int LOADER_CHART_DATA = 2; 103 @VisibleForTesting 104 static final int LOADER_SUMMARY = 3; 105 106 @VisibleForTesting 107 MobileDataEnabledListener mDataStateListener; 108 109 @VisibleForTesting 110 NetworkTemplate mTemplate; 111 @VisibleForTesting 112 int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; 113 @VisibleForTesting 114 int mNetworkType; 115 @VisibleForTesting 116 Spinner mCycleSpinner; 117 @VisibleForTesting 118 LoadingViewController mLoadingViewController; 119 120 private ChartDataUsagePreference mChart; 121 private List<NetworkCycleChartData> mCycleData; 122 // Caches the cycles for startAppDataUsage usage, which need be cleared when resumed. 123 private ArrayList<Long> mCycles; 124 // Spinner will keep the selected cycle even after paused, this only keeps the displayed cycle, 125 // which need be cleared when resumed. 126 private CycleAdapter.CycleItem mLastDisplayedCycle; 127 private UidDetailProvider mUidDetailProvider; 128 private CycleAdapter mCycleAdapter; 129 private Preference mUsageAmount; 130 private PreferenceGroup mApps; 131 private View mHeader; 132 private MobileNetworkRepository mMobileNetworkRepository; 133 private SubscriptionInfoEntity mSubscriptionInfoEntity; 134 135 @Override getMetricsCategory()136 public int getMetricsCategory() { 137 return SettingsEnums.DATA_USAGE_LIST; 138 } 139 140 @Override onCreate(Bundle savedInstanceState)141 public void onCreate(Bundle savedInstanceState) { 142 super.onCreate(savedInstanceState); 143 if (isGuestUser(getContext())) { 144 Log.e(TAG, "This setting isn't available for guest user"); 145 EventLog.writeEvent(0x534e4554, "262741858", -1 /* UID */, "Guest user"); 146 finish(); 147 return; 148 } 149 150 final Activity activity = getActivity(); 151 if (!isBandwidthControlEnabled()) { 152 Log.w(TAG, "No bandwidth control; leaving"); 153 activity.finish(); 154 return; 155 } 156 157 mUidDetailProvider = new UidDetailProvider(activity); 158 mUsageAmount = findPreference(KEY_USAGE_AMOUNT); 159 mChart = findPreference(KEY_CHART_DATA); 160 mApps = findPreference(KEY_APPS_GROUP); 161 162 final Preference unnecessaryWarningPreference = findPreference("operator_warning"); 163 if (unnecessaryWarningPreference != null) { 164 unnecessaryWarningPreference.setVisible(false); 165 } 166 167 processArgument(); 168 updateSubscriptionInfoEntity(); 169 mDataStateListener = new MobileDataEnabledListener(activity, this); 170 } 171 172 @Override onViewCreated(View v, Bundle savedInstanceState)173 public void onViewCreated(View v, Bundle savedInstanceState) { 174 super.onViewCreated(v, savedInstanceState); 175 176 mHeader = setPinnedHeaderView(R.layout.apps_filter_spinner); 177 mHeader.findViewById(R.id.filter_settings).setOnClickListener(btn -> { 178 final Bundle args = new Bundle(); 179 args.putParcelable(DataUsageList.EXTRA_NETWORK_TEMPLATE, mTemplate); 180 new SubSettingLauncher(getContext()) 181 .setDestination(BillingCycleSettings.class.getName()) 182 .setTitleRes(R.string.billing_cycle) 183 .setSourceMetricsCategory(getMetricsCategory()) 184 .setArguments(args) 185 .launch(); 186 }); 187 mCycleSpinner = mHeader.findViewById(R.id.filter_spinner); 188 mCycleSpinner.setVisibility(View.GONE); 189 mCycleAdapter = new CycleAdapter(mCycleSpinner.getContext(), new SpinnerInterface() { 190 @Override 191 public void setAdapter(CycleAdapter cycleAdapter) { 192 mCycleSpinner.setAdapter(cycleAdapter); 193 } 194 195 @Override 196 public void setOnItemSelectedListener(OnItemSelectedListener listener) { 197 mCycleSpinner.setOnItemSelectedListener(listener); 198 } 199 200 @Override 201 public Object getSelectedItem() { 202 return mCycleSpinner.getSelectedItem(); 203 } 204 205 @Override 206 public void setSelection(int position) { 207 mCycleSpinner.setSelection(position); 208 } 209 }, mCycleListener); 210 mCycleSpinner.setAccessibilityDelegate(new AccessibilityDelegate() { 211 @Override 212 public void sendAccessibilityEvent(View host, int eventType) { 213 if (eventType == AccessibilityEvent.TYPE_VIEW_SELECTED) { 214 // Ignore TYPE_VIEW_SELECTED or TalkBack will speak for it at onResume. 215 return; 216 } 217 super.sendAccessibilityEvent(host, eventType); 218 } 219 }); 220 221 mLoadingViewController = new LoadingViewController( 222 getView().findViewById(R.id.loading_container), getListView()); 223 } 224 225 @Override onResume()226 public void onResume() { 227 super.onResume(); 228 mLoadingViewController.showLoadingViewDelayed(); 229 mDataStateListener.start(mSubId); 230 mCycles = null; 231 mLastDisplayedCycle = null; 232 233 // kick off loader for network history 234 // TODO: consider chaining two loaders together instead of reloading 235 // network history when showing app detail. 236 getLoaderManager().restartLoader(LOADER_CHART_DATA, 237 buildArgs(mTemplate), mNetworkCycleDataCallbacks); 238 239 updateBody(); 240 } 241 242 @Override onPause()243 public void onPause() { 244 super.onPause(); 245 mDataStateListener.stop(); 246 247 getLoaderManager().destroyLoader(LOADER_CHART_DATA); 248 getLoaderManager().destroyLoader(LOADER_SUMMARY); 249 } 250 251 @Override onDestroy()252 public void onDestroy() { 253 if (mUidDetailProvider != null) { 254 mUidDetailProvider.clearCache(); 255 mUidDetailProvider = null; 256 } 257 super.onDestroy(); 258 } 259 260 @Override getPreferenceScreenResId()261 protected int getPreferenceScreenResId() { 262 return R.xml.data_usage_list; 263 } 264 265 @Override getLogTag()266 protected String getLogTag() { 267 return TAG; 268 } 269 processArgument()270 void processArgument() { 271 final Bundle args = getArguments(); 272 if (args != null) { 273 mSubId = args.getInt(EXTRA_SUB_ID, SubscriptionManager.INVALID_SUBSCRIPTION_ID); 274 mTemplate = args.getParcelable(EXTRA_NETWORK_TEMPLATE); 275 mNetworkType = args.getInt(EXTRA_NETWORK_TYPE, ConnectivityManager.TYPE_MOBILE); 276 } 277 if (mTemplate == null && mSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 278 final Intent intent = getIntent(); 279 mSubId = intent.getIntExtra(Settings.EXTRA_SUB_ID, 280 SubscriptionManager.INVALID_SUBSCRIPTION_ID); 281 mTemplate = intent.getParcelableExtra(Settings.EXTRA_NETWORK_TEMPLATE); 282 283 if (mTemplate == null) { 284 Optional<NetworkTemplate> mobileNetworkTemplateFromSim = 285 DataUsageUtils.getMobileNetworkTemplateFromSubId(getContext(), getIntent()); 286 if (mobileNetworkTemplateFromSim.isPresent()) { 287 mTemplate = mobileNetworkTemplateFromSim.get(); 288 } 289 } 290 } 291 } 292 293 @VisibleForTesting updateSubscriptionInfoEntity()294 void updateSubscriptionInfoEntity() { 295 mMobileNetworkRepository = MobileNetworkRepository.getInstance(getContext()); 296 ThreadUtils.postOnBackgroundThread(() -> { 297 mSubscriptionInfoEntity = mMobileNetworkRepository.getSubInfoById( 298 String.valueOf(mSubId)); 299 }); 300 } 301 302 /** 303 * Implementation of {@code MobileDataEnabledListener.Client} 304 */ onMobileDataEnabledChange()305 public void onMobileDataEnabledChange() { 306 updatePolicy(); 307 } 308 309 /** 310 * Update body content based on current tab. Loads network cycle data from system, and 311 * binds them to visible controls. 312 */ updateBody()313 private void updateBody() { 314 if (!isAdded()) return; 315 316 final Context context = getActivity(); 317 318 // detail mode can change visible menus, invalidate 319 getActivity().invalidateOptionsMenu(); 320 321 int seriesColor = context.getColor(R.color.sim_noitification); 322 if (mSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 323 final SubscriptionInfo sir = ProxySubscriptionManager.getInstance(context) 324 .getActiveSubscriptionInfo(mSubId); 325 326 if (sir != null) { 327 seriesColor = sir.getIconTint(); 328 } 329 } 330 331 final int secondaryColor = Color.argb(127, Color.red(seriesColor), Color.green(seriesColor), 332 Color.blue(seriesColor)); 333 mChart.setColors(seriesColor, secondaryColor); 334 } 335 buildArgs(NetworkTemplate template)336 private Bundle buildArgs(NetworkTemplate template) { 337 final Bundle args = new Bundle(); 338 args.putParcelable(KEY_TEMPLATE, template); 339 args.putParcelable(KEY_APP, null); 340 return args; 341 } 342 343 /** 344 * Update chart sweeps and cycle list to reflect {@link NetworkPolicy} for 345 * current {@link #mTemplate}. 346 */ 347 @VisibleForTesting updatePolicy()348 void updatePolicy() { 349 final NetworkPolicy policy = services.mPolicyEditor.getPolicy(mTemplate); 350 final View configureButton = mHeader.findViewById(R.id.filter_settings); 351 //SUB SELECT 352 if (isNetworkPolicyModifiable(policy, mSubId) && isMobileDataAvailable(mSubId)) { 353 mChart.setNetworkPolicy(policy); 354 configureButton.setVisibility(View.VISIBLE); 355 ((ImageView) configureButton).setColorFilter(android.R.color.white); 356 } else { 357 // controls are disabled; don't bind warning/limit sweeps 358 mChart.setNetworkPolicy(null); 359 configureButton.setVisibility(View.GONE); 360 } 361 362 // generate cycle list based on policy and available history 363 mCycleAdapter.updateCycleList(mCycleData); 364 updateSelectedCycle(); 365 } 366 367 /** 368 * Updates the chart and detail data when initial loaded or selected cycle changed. 369 */ updateSelectedCycle()370 private void updateSelectedCycle() { 371 // Avoid from updating UI after #onStop. 372 if (!getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) { 373 return; 374 } 375 376 // Avoid from updating UI when async query still on-going. 377 // This could happen when a request from #onMobileDataEnabledChange. 378 if (mCycleData == null) { 379 return; 380 } 381 382 final int position = mCycleSpinner.getSelectedItemPosition(); 383 if (mCycleAdapter.getCount() == 0 || position < 0) { 384 return; 385 } 386 final CycleAdapter.CycleItem cycle = mCycleAdapter.getItem(position); 387 if (Objects.equals(cycle, mLastDisplayedCycle)) { 388 // Avoid duplicate update to avoid page flash. 389 return; 390 } 391 mLastDisplayedCycle = cycle; 392 393 if (LOGD) { 394 Log.d(TAG, "showing cycle " + cycle + ", [start=" + cycle.start + ", end=" 395 + cycle.end + "]"); 396 } 397 398 // update chart to show selected cycle, and update detail data 399 // to match updated sweep bounds. 400 mChart.setNetworkCycleData(mCycleData.get(position)); 401 402 updateDetailData(); 403 } 404 405 /** 406 * Update details based on {@link #mChart} inspection range depending on 407 * current mode. Updates {@link #mAdapter} with sorted list 408 * of applications data usage. 409 */ updateDetailData()410 private void updateDetailData() { 411 if (LOGD) Log.d(TAG, "updateDetailData()"); 412 413 // kick off loader for detailed stats 414 getLoaderManager().restartLoader(LOADER_SUMMARY, null /* args */, 415 mNetworkStatsDetailCallbacks); 416 417 final long totalBytes = mCycleData != null && !mCycleData.isEmpty() 418 ? mCycleData.get(mCycleSpinner.getSelectedItemPosition()).getTotalUsage() : 0; 419 final CharSequence totalPhrase = DataUsageUtils.formatDataUsage(getActivity(), totalBytes); 420 mUsageAmount.setTitle(getString(R.string.data_used_template, totalPhrase)); 421 } 422 423 /** 424 * Bind the given {@link NetworkStats}, or {@code null} to clear list. 425 */ bindStats(NetworkStats stats, int[] restrictedUids)426 private void bindStats(NetworkStats stats, int[] restrictedUids) { 427 mApps.removeAll(); 428 if (stats == null) { 429 if (LOGD) { 430 Log.d(TAG, "No network stats data. App list cleared."); 431 } 432 return; 433 } 434 435 final ArrayList<AppItem> items = new ArrayList<>(); 436 long largest = 0; 437 438 final int currentUserId = ActivityManager.getCurrentUser(); 439 final UserManager userManager = UserManager.get(getContext()); 440 final List<UserHandle> profiles = userManager.getUserProfiles(); 441 final SparseArray<AppItem> knownItems = new SparseArray<AppItem>(); 442 443 final Bucket bucket = new Bucket(); 444 while (stats.hasNextBucket() && stats.getNextBucket(bucket)) { 445 // Decide how to collapse items together 446 final int uid = bucket.getUid(); 447 final int collapseKey; 448 final int category; 449 final int userId = UserHandle.getUserId(uid); 450 if (UserHandle.isApp(uid) || Process.isSdkSandboxUid(uid)) { 451 if (profiles.contains(new UserHandle(userId))) { 452 if (userId != currentUserId) { 453 // Add to a managed user item. 454 final int managedKey = UidDetailProvider.buildKeyForUser(userId); 455 largest = accumulate(managedKey, knownItems, bucket, 456 AppItem.CATEGORY_USER, items, largest); 457 } 458 // Map SDK sandbox back to its corresponding app 459 if (Process.isSdkSandboxUid(uid)) { 460 collapseKey = Process.getAppUidForSdkSandboxUid(uid); 461 } else { 462 collapseKey = uid; 463 } 464 category = AppItem.CATEGORY_APP; 465 } else { 466 // If it is a removed user add it to the removed users' key 467 final UserInfo info = userManager.getUserInfo(userId); 468 if (info == null) { 469 collapseKey = UID_REMOVED; 470 category = AppItem.CATEGORY_APP; 471 } else { 472 // Add to other user item. 473 collapseKey = UidDetailProvider.buildKeyForUser(userId); 474 category = AppItem.CATEGORY_USER; 475 } 476 } 477 } else if (uid == UID_REMOVED || uid == UID_TETHERING 478 || uid == Process.OTA_UPDATE_UID) { 479 collapseKey = uid; 480 category = AppItem.CATEGORY_APP; 481 } else { 482 collapseKey = android.os.Process.SYSTEM_UID; 483 category = AppItem.CATEGORY_APP; 484 } 485 largest = accumulate(collapseKey, knownItems, bucket, category, items, largest); 486 } 487 stats.close(); 488 489 final int restrictedUidsMax = restrictedUids.length; 490 for (int i = 0; i < restrictedUidsMax; ++i) { 491 final int uid = restrictedUids[i]; 492 // Only splice in restricted state for current user or managed users 493 if (!profiles.contains(new UserHandle(UserHandle.getUserId(uid)))) { 494 continue; 495 } 496 497 AppItem item = knownItems.get(uid); 498 if (item == null) { 499 item = new AppItem(uid); 500 item.total = -1; 501 item.addUid(uid); 502 items.add(item); 503 knownItems.put(item.key, item); 504 } 505 item.restricted = true; 506 } 507 508 Collections.sort(items); 509 final List<String> packageNames = Arrays.asList(getContext().getResources().getStringArray( 510 R.array.datausage_hiding_carrier_service_package_names)); 511 // When there is no specified SubscriptionInfo, Wi-Fi data usage will be displayed. 512 // In this case, the carrier service package also needs to be hidden. 513 boolean shouldHidePackageName = mSubscriptionInfoEntity != null 514 ? Arrays.stream(getContext().getResources().getIntArray( 515 R.array.datausage_hiding_carrier_service_carrier_id)) 516 .anyMatch(carrierId -> (carrierId == mSubscriptionInfoEntity.carrierId)) 517 : true; 518 519 for (int i = 0; i < items.size(); i++) { 520 UidDetail detail = mUidDetailProvider.getUidDetail(items.get(i).key, true); 521 // Do not show carrier service package in data usage list if it should be hidden for 522 // the carrier. 523 if (detail != null && shouldHidePackageName && packageNames.contains( 524 detail.packageName)) { 525 continue; 526 } 527 528 final int percentTotal = largest != 0 ? (int) (items.get(i).total * 100 / largest) : 0; 529 final AppDataUsagePreference preference = new AppDataUsagePreference(getContext(), 530 items.get(i), percentTotal, mUidDetailProvider); 531 preference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { 532 @Override 533 public boolean onPreferenceClick(Preference preference) { 534 AppDataUsagePreference pref = (AppDataUsagePreference) preference; 535 AppItem item = pref.getItem(); 536 startAppDataUsage(item); 537 return true; 538 } 539 }); 540 mApps.addPreference(preference); 541 } 542 } 543 544 @VisibleForTesting startAppDataUsage(AppItem item)545 void startAppDataUsage(AppItem item) { 546 final Bundle args = new Bundle(); 547 args.putParcelable(AppDataUsage.ARG_APP_ITEM, item); 548 args.putParcelable(AppDataUsage.ARG_NETWORK_TEMPLATE, mTemplate); 549 if (mCycles == null) { 550 mCycles = new ArrayList<>(); 551 for (NetworkCycleChartData data : mCycleData) { 552 if (mCycles.isEmpty()) { 553 mCycles.add(data.getEndTime()); 554 } 555 mCycles.add(data.getStartTime()); 556 } 557 } 558 args.putSerializable(AppDataUsage.ARG_NETWORK_CYCLES, mCycles); 559 args.putLong(AppDataUsage.ARG_SELECTED_CYCLE, 560 mCycleData.get(mCycleSpinner.getSelectedItemPosition()).getEndTime()); 561 562 new SubSettingLauncher(getContext()) 563 .setDestination(AppDataUsage.class.getName()) 564 .setTitleRes(R.string.data_usage_app_summary_title) 565 .setArguments(args) 566 .setSourceMetricsCategory(getMetricsCategory()) 567 .launch(); 568 } 569 570 /** 571 * Accumulate data usage of a network stats entry for the item mapped by the collapse key. 572 * Creates the item if needed. 573 * 574 * @param collapseKey the collapse key used to map the item. 575 * @param knownItems collection of known (already existing) items. 576 * @param bucket the network stats bucket to extract data usage from. 577 * @param itemCategory the item is categorized on the list view by this category. Must be 578 */ accumulate(int collapseKey, final SparseArray<AppItem> knownItems, Bucket bucket, int itemCategory, ArrayList<AppItem> items, long largest)579 private static long accumulate(int collapseKey, final SparseArray<AppItem> knownItems, 580 Bucket bucket, int itemCategory, ArrayList<AppItem> items, long largest) { 581 final int uid = bucket.getUid(); 582 AppItem item = knownItems.get(collapseKey); 583 if (item == null) { 584 item = new AppItem(collapseKey); 585 item.category = itemCategory; 586 items.add(item); 587 knownItems.put(item.key, item); 588 } 589 item.addUid(uid); 590 item.total += bucket.getRxBytes() + bucket.getTxBytes(); 591 return Math.max(largest, item.total); 592 } 593 594 private final OnItemSelectedListener mCycleListener = new OnItemSelectedListener() { 595 @Override 596 public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { 597 updateSelectedCycle(); 598 } 599 600 @Override 601 public void onNothingSelected(AdapterView<?> parent) { 602 // ignored 603 } 604 }; 605 606 @VisibleForTesting 607 final LoaderCallbacks<List<NetworkCycleChartData>> mNetworkCycleDataCallbacks = 608 new LoaderCallbacks<List<NetworkCycleChartData>>() { 609 @Override 610 public Loader<List<NetworkCycleChartData>> onCreateLoader(int id, Bundle args) { 611 return NetworkCycleChartDataLoader.builder(getContext()) 612 .setNetworkTemplate(mTemplate) 613 .build(); 614 } 615 616 @Override 617 public void onLoadFinished(Loader<List<NetworkCycleChartData>> loader, 618 List<NetworkCycleChartData> data) { 619 mLoadingViewController.showContent(false /* animate */); 620 mCycleData = data; 621 // calculate policy cycles based on available data 622 updatePolicy(); 623 mCycleSpinner.setVisibility(View.VISIBLE); 624 } 625 626 @Override 627 public void onLoaderReset(Loader<List<NetworkCycleChartData>> loader) { 628 mCycleData = null; 629 } 630 }; 631 632 private final LoaderCallbacks<NetworkStats> mNetworkStatsDetailCallbacks = 633 new LoaderCallbacks<NetworkStats>() { 634 @Override 635 public Loader<NetworkStats> onCreateLoader(int id, Bundle args) { 636 return new NetworkStatsSummaryLoader.Builder(getContext()) 637 .setStartTime(mChart.getInspectStart()) 638 .setEndTime(mChart.getInspectEnd()) 639 .setNetworkTemplate(mTemplate) 640 .build(); 641 } 642 643 @Override 644 public void onLoadFinished(Loader<NetworkStats> loader, NetworkStats data) { 645 final int[] restrictedUids = services.mPolicyManager.getUidsWithPolicy( 646 POLICY_REJECT_METERED_BACKGROUND); 647 bindStats(data, restrictedUids); 648 updateEmptyVisible(); 649 } 650 651 @Override 652 public void onLoaderReset(Loader<NetworkStats> loader) { 653 bindStats(null, new int[0]); 654 updateEmptyVisible(); 655 } 656 657 private void updateEmptyVisible() { 658 if ((mApps.getPreferenceCount() != 0) != 659 (getPreferenceScreen().getPreferenceCount() != 0)) { 660 if (mApps.getPreferenceCount() != 0) { 661 getPreferenceScreen().addPreference(mUsageAmount); 662 getPreferenceScreen().addPreference(mApps); 663 } else { 664 getPreferenceScreen().removeAll(); 665 } 666 } 667 } 668 }; 669 isGuestUser(Context context)670 private static boolean isGuestUser(Context context) { 671 if (context == null) return false; 672 final UserManager userManager = context.getSystemService(UserManager.class); 673 if (userManager == null) return false; 674 return userManager.isGuestUser(); 675 } 676 } 677