1 /* 2 * Copyright (C) 2016 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.net.ConnectivityManager.TYPE_MOBILE; 18 import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND; 19 import static android.net.TrafficStats.UID_REMOVED; 20 import static android.net.TrafficStats.UID_TETHERING; 21 import static android.telephony.TelephonyManager.SIM_STATE_READY; 22 23 import android.app.ActivityManager; 24 import android.app.LoaderManager.LoaderCallbacks; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.Loader; 28 import android.content.pm.UserInfo; 29 import android.graphics.Color; 30 import android.net.ConnectivityManager; 31 import android.net.INetworkStatsSession; 32 import android.net.NetworkPolicy; 33 import android.net.NetworkStats; 34 import android.net.NetworkStatsHistory; 35 import android.net.NetworkTemplate; 36 import android.net.TrafficStats; 37 import android.os.AsyncTask; 38 import android.os.Bundle; 39 import android.os.RemoteException; 40 import android.os.SystemProperties; 41 import android.os.UserHandle; 42 import android.os.UserManager; 43 import android.provider.Settings; 44 import android.support.annotation.VisibleForTesting; 45 import android.support.v7.preference.Preference; 46 import android.support.v7.preference.PreferenceGroup; 47 import android.telephony.SubscriptionInfo; 48 import android.telephony.SubscriptionManager; 49 import android.telephony.TelephonyManager; 50 import android.text.format.DateUtils; 51 import android.util.Log; 52 import android.util.SparseArray; 53 import android.view.View; 54 import android.widget.AdapterView; 55 import android.widget.AdapterView.OnItemSelectedListener; 56 import android.widget.Spinner; 57 58 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 59 import com.android.settings.R; 60 import com.android.settings.core.SubSettingLauncher; 61 import com.android.settings.datausage.CycleAdapter.SpinnerInterface; 62 import com.android.settings.widget.LoadingViewController; 63 import com.android.settingslib.AppItem; 64 import com.android.settingslib.net.ChartData; 65 import com.android.settingslib.net.ChartDataLoader; 66 import com.android.settingslib.net.SummaryForAllUidLoader; 67 import com.android.settingslib.net.UidDetailProvider; 68 69 import java.util.ArrayList; 70 import java.util.Collections; 71 import java.util.List; 72 73 /** 74 * Panel showing data usage history across various networks, including options 75 * to inspect based on usage cycle and control through {@link NetworkPolicy}. 76 */ 77 public class DataUsageList extends DataUsageBase { 78 79 public static final String EXTRA_SUB_ID = "sub_id"; 80 public static final String EXTRA_NETWORK_TEMPLATE = "network_template"; 81 82 private static final String TAG = "DataUsage"; 83 private static final boolean LOGD = false; 84 85 private static final String KEY_USAGE_AMOUNT = "usage_amount"; 86 private static final String KEY_CHART_DATA = "chart_data"; 87 private static final String KEY_APPS_GROUP = "apps_group"; 88 89 private static final int LOADER_CHART_DATA = 2; 90 private static final int LOADER_SUMMARY = 3; 91 92 private final CellDataPreference.DataStateListener mDataStateListener = 93 new CellDataPreference.DataStateListener() { 94 @Override 95 public void onChange(boolean selfChange) { 96 updatePolicy(); 97 } 98 }; 99 100 private INetworkStatsSession mStatsSession; 101 private ChartDataUsagePreference mChart; 102 103 @VisibleForTesting 104 NetworkTemplate mTemplate; 105 @VisibleForTesting 106 int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; 107 private ChartData mChartData; 108 109 private LoadingViewController mLoadingViewController; 110 private UidDetailProvider mUidDetailProvider; 111 private CycleAdapter mCycleAdapter; 112 private Spinner mCycleSpinner; 113 private Preference mUsageAmount; 114 private PreferenceGroup mApps; 115 private View mHeader; 116 117 118 @Override 119 public int getMetricsCategory() { 120 return MetricsEvent.DATA_USAGE_LIST; 121 } 122 123 @Override 124 public void onCreate(Bundle savedInstanceState) { 125 super.onCreate(savedInstanceState); 126 final Context context = getActivity(); 127 128 if (!isBandwidthControlEnabled()) { 129 Log.w(TAG, "No bandwidth control; leaving"); 130 getActivity().finish(); 131 } 132 133 try { 134 mStatsSession = services.mStatsService.openSession(); 135 } catch (RemoteException e) { 136 throw new RuntimeException(e); 137 } 138 139 mUidDetailProvider = new UidDetailProvider(context); 140 141 addPreferencesFromResource(R.xml.data_usage_list); 142 mUsageAmount = findPreference(KEY_USAGE_AMOUNT); 143 mChart = (ChartDataUsagePreference) findPreference(KEY_CHART_DATA); 144 mApps = (PreferenceGroup) findPreference(KEY_APPS_GROUP); 145 processArgument(); 146 } 147 148 @Override 149 public void onViewCreated(View v, Bundle savedInstanceState) { 150 super.onViewCreated(v, savedInstanceState); 151 152 mHeader = setPinnedHeaderView(R.layout.apps_filter_spinner); 153 mHeader.findViewById(R.id.filter_settings).setOnClickListener(btn -> { 154 final Bundle args = new Bundle(); 155 args.putParcelable(DataUsageList.EXTRA_NETWORK_TEMPLATE, mTemplate); 156 new SubSettingLauncher(getContext()) 157 .setDestination(BillingCycleSettings.class.getName()) 158 .setTitle(R.string.billing_cycle) 159 .setSourceMetricsCategory(getMetricsCategory()) 160 .setArguments(args) 161 .launch(); 162 }); 163 mCycleSpinner = mHeader.findViewById(R.id.filter_spinner); 164 mCycleAdapter = new CycleAdapter(mCycleSpinner.getContext(), new SpinnerInterface() { 165 @Override 166 public void setAdapter(CycleAdapter cycleAdapter) { 167 mCycleSpinner.setAdapter(cycleAdapter); 168 } 169 170 @Override 171 public void setOnItemSelectedListener(OnItemSelectedListener listener) { 172 mCycleSpinner.setOnItemSelectedListener(listener); 173 } 174 175 @Override 176 public Object getSelectedItem() { 177 return mCycleSpinner.getSelectedItem(); 178 } 179 180 @Override 181 public void setSelection(int position) { 182 mCycleSpinner.setSelection(position); 183 } 184 }, mCycleListener, true); 185 186 mLoadingViewController = new LoadingViewController( 187 getView().findViewById(R.id.loading_container), getListView()); 188 mLoadingViewController.showLoadingViewDelayed(); 189 } 190 191 @Override 192 public void onResume() { 193 super.onResume(); 194 mDataStateListener.setListener(true, mSubId, getContext()); 195 updateBody(); 196 197 // kick off background task to update stats 198 new AsyncTask<Void, Void, Void>() { 199 @Override 200 protected Void doInBackground(Void... params) { 201 try { 202 // wait a few seconds before kicking off 203 Thread.sleep(2 * DateUtils.SECOND_IN_MILLIS); 204 services.mStatsService.forceUpdate(); 205 } catch (InterruptedException e) { 206 } catch (RemoteException e) { 207 } 208 return null; 209 } 210 211 @Override 212 protected void onPostExecute(Void result) { 213 if (isAdded()) { 214 updateBody(); 215 } 216 } 217 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 218 } 219 220 @Override 221 public void onPause() { 222 super.onPause(); 223 mDataStateListener.setListener(false, mSubId, getContext()); 224 } 225 226 @Override 227 public void onDestroy() { 228 mUidDetailProvider.clearCache(); 229 mUidDetailProvider = null; 230 231 TrafficStats.closeQuietly(mStatsSession); 232 233 super.onDestroy(); 234 } 235 236 void processArgument() { 237 final Bundle args = getArguments(); 238 if (args != null) { 239 mSubId = args.getInt(EXTRA_SUB_ID, SubscriptionManager.INVALID_SUBSCRIPTION_ID); 240 mTemplate = args.getParcelable(EXTRA_NETWORK_TEMPLATE); 241 } 242 if (mTemplate == null && mSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 243 final Intent intent = getIntent(); 244 mSubId = intent.getIntExtra(Settings.EXTRA_SUB_ID, 245 SubscriptionManager.INVALID_SUBSCRIPTION_ID); 246 mTemplate = intent.getParcelableExtra(Settings.EXTRA_NETWORK_TEMPLATE); 247 } 248 } 249 250 /** 251 * Update body content based on current tab. Loads 252 * {@link NetworkStatsHistory} and {@link NetworkPolicy} from system, and 253 * binds them to visible controls. 254 */ 255 private void updateBody() { 256 if (!isAdded()) return; 257 258 final Context context = getActivity(); 259 260 // kick off loader for network history 261 // TODO: consider chaining two loaders together instead of reloading 262 // network history when showing app detail. 263 getLoaderManager().restartLoader(LOADER_CHART_DATA, 264 ChartDataLoader.buildArgs(mTemplate, null), mChartDataCallbacks); 265 266 // detail mode can change visible menus, invalidate 267 getActivity().invalidateOptionsMenu(); 268 269 int seriesColor = context.getColor(R.color.sim_noitification); 270 if (mSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 271 final SubscriptionInfo sir = services.mSubscriptionManager 272 .getActiveSubscriptionInfo(mSubId); 273 274 if (sir != null) { 275 seriesColor = sir.getIconTint(); 276 } 277 } 278 279 final int secondaryColor = Color.argb(127, Color.red(seriesColor), Color.green(seriesColor), 280 Color.blue(seriesColor)); 281 mChart.setColors(seriesColor, secondaryColor); 282 } 283 284 /** 285 * Update chart sweeps and cycle list to reflect {@link NetworkPolicy} for 286 * current {@link #mTemplate}. 287 */ 288 private void updatePolicy() { 289 final NetworkPolicy policy = services.mPolicyEditor.getPolicy(mTemplate); 290 final View configureButton = mHeader.findViewById(R.id.filter_settings); 291 //SUB SELECT 292 if (isNetworkPolicyModifiable(policy, mSubId) && isMobileDataAvailable(mSubId)) { 293 mChart.setNetworkPolicy(policy); 294 configureButton.setVisibility(View.VISIBLE); 295 } else { 296 // controls are disabled; don't bind warning/limit sweeps 297 mChart.setNetworkPolicy(null); 298 configureButton.setVisibility(View.GONE); 299 } 300 301 // generate cycle list based on policy and available history 302 if (mCycleAdapter.updateCycleList(policy, mChartData)) { 303 updateDetailData(); 304 } 305 } 306 307 /** 308 * Update details based on {@link #mChart} inspection range depending on 309 * current mode. Updates {@link #mAdapter} with sorted list 310 * of applications data usage. 311 */ 312 private void updateDetailData() { 313 if (LOGD) Log.d(TAG, "updateDetailData()"); 314 315 final long start = mChart.getInspectStart(); 316 final long end = mChart.getInspectEnd(); 317 final long now = System.currentTimeMillis(); 318 319 final Context context = getActivity(); 320 321 NetworkStatsHistory.Entry entry = null; 322 if (mChartData != null) { 323 entry = mChartData.network.getValues(start, end, now, null); 324 } 325 326 // kick off loader for detailed stats 327 getLoaderManager().restartLoader(LOADER_SUMMARY, 328 SummaryForAllUidLoader.buildArgs(mTemplate, start, end), mSummaryCallbacks); 329 330 final long totalBytes = entry != null ? entry.rxBytes + entry.txBytes : 0; 331 final CharSequence totalPhrase = DataUsageUtils.formatDataUsage(context, totalBytes); 332 mUsageAmount.setTitle(getString(R.string.data_used_template, totalPhrase)); 333 } 334 335 /** 336 * Bind the given {@link NetworkStats}, or {@code null} to clear list. 337 */ 338 public void bindStats(NetworkStats stats, int[] restrictedUids) { 339 ArrayList<AppItem> items = new ArrayList<>(); 340 long largest = 0; 341 342 final int currentUserId = ActivityManager.getCurrentUser(); 343 UserManager userManager = UserManager.get(getContext()); 344 final List<UserHandle> profiles = userManager.getUserProfiles(); 345 final SparseArray<AppItem> knownItems = new SparseArray<AppItem>(); 346 347 NetworkStats.Entry entry = null; 348 final int size = stats != null ? stats.size() : 0; 349 for (int i = 0; i < size; i++) { 350 entry = stats.getValues(i, entry); 351 352 // Decide how to collapse items together 353 final int uid = entry.uid; 354 355 final int collapseKey; 356 final int category; 357 final int userId = UserHandle.getUserId(uid); 358 if (UserHandle.isApp(uid)) { 359 if (profiles.contains(new UserHandle(userId))) { 360 if (userId != currentUserId) { 361 // Add to a managed user item. 362 final int managedKey = UidDetailProvider.buildKeyForUser(userId); 363 largest = accumulate(managedKey, knownItems, entry, AppItem.CATEGORY_USER, 364 items, largest); 365 } 366 // Add to app item. 367 collapseKey = uid; 368 category = AppItem.CATEGORY_APP; 369 } else { 370 // If it is a removed user add it to the removed users' key 371 final UserInfo info = userManager.getUserInfo(userId); 372 if (info == null) { 373 collapseKey = UID_REMOVED; 374 category = AppItem.CATEGORY_APP; 375 } else { 376 // Add to other user item. 377 collapseKey = UidDetailProvider.buildKeyForUser(userId); 378 category = AppItem.CATEGORY_USER; 379 } 380 } 381 } else if (uid == UID_REMOVED || uid == UID_TETHERING) { 382 collapseKey = uid; 383 category = AppItem.CATEGORY_APP; 384 } else { 385 collapseKey = android.os.Process.SYSTEM_UID; 386 category = AppItem.CATEGORY_APP; 387 } 388 largest = accumulate(collapseKey, knownItems, entry, category, items, largest); 389 } 390 391 final int restrictedUidsMax = restrictedUids.length; 392 for (int i = 0; i < restrictedUidsMax; ++i) { 393 final int uid = restrictedUids[i]; 394 // Only splice in restricted state for current user or managed users 395 if (!profiles.contains(new UserHandle(UserHandle.getUserId(uid)))) { 396 continue; 397 } 398 399 AppItem item = knownItems.get(uid); 400 if (item == null) { 401 item = new AppItem(uid); 402 item.total = -1; 403 items.add(item); 404 knownItems.put(item.key, item); 405 } 406 item.restricted = true; 407 } 408 409 Collections.sort(items); 410 mApps.removeAll(); 411 for (int i = 0; i < items.size(); i++) { 412 final int percentTotal = largest != 0 ? (int) (items.get(i).total * 100 / largest) : 0; 413 AppDataUsagePreference preference = new AppDataUsagePreference(getContext(), 414 items.get(i), percentTotal, mUidDetailProvider); 415 preference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { 416 @Override 417 public boolean onPreferenceClick(Preference preference) { 418 AppDataUsagePreference pref = (AppDataUsagePreference) preference; 419 AppItem item = pref.getItem(); 420 startAppDataUsage(item); 421 return true; 422 } 423 }); 424 mApps.addPreference(preference); 425 } 426 } 427 428 private void startAppDataUsage(AppItem item) { 429 final Bundle args = new Bundle(); 430 args.putParcelable(AppDataUsage.ARG_APP_ITEM, item); 431 args.putParcelable(AppDataUsage.ARG_NETWORK_TEMPLATE, mTemplate); 432 433 new SubSettingLauncher(getContext()) 434 .setDestination(AppDataUsage.class.getName()) 435 .setTitle(R.string.app_data_usage) 436 .setArguments(args) 437 .setSourceMetricsCategory(getMetricsCategory()) 438 .launch(); 439 } 440 441 /** 442 * Accumulate data usage of a network stats entry for the item mapped by the collapse key. 443 * Creates the item if needed. 444 * 445 * @param collapseKey the collapse key used to map the item. 446 * @param knownItems collection of known (already existing) items. 447 * @param entry the network stats entry to extract data usage from. 448 * @param itemCategory the item is categorized on the list view by this category. Must be 449 */ 450 private static long accumulate(int collapseKey, final SparseArray<AppItem> knownItems, 451 NetworkStats.Entry entry, int itemCategory, ArrayList<AppItem> items, long largest) { 452 final int uid = entry.uid; 453 AppItem item = knownItems.get(collapseKey); 454 if (item == null) { 455 item = new AppItem(collapseKey); 456 item.category = itemCategory; 457 items.add(item); 458 knownItems.put(item.key, item); 459 } 460 item.addUid(uid); 461 item.total += entry.rxBytes + entry.txBytes; 462 return Math.max(largest, item.total); 463 } 464 465 /** 466 * Test if device has a mobile data radio with SIM in ready state. 467 */ 468 public static boolean hasReadyMobileRadio(Context context) { 469 if (DataUsageUtils.TEST_RADIOS) { 470 return SystemProperties.get(DataUsageUtils.TEST_RADIOS_PROP).contains("mobile"); 471 } 472 473 final ConnectivityManager conn = ConnectivityManager.from(context); 474 final TelephonyManager tele = TelephonyManager.from(context); 475 476 final List<SubscriptionInfo> subInfoList = 477 SubscriptionManager.from(context).getActiveSubscriptionInfoList(); 478 // No activated Subscriptions 479 if (subInfoList == null) { 480 if (LOGD) Log.d(TAG, "hasReadyMobileRadio: subInfoList=null"); 481 return false; 482 } 483 // require both supported network and ready SIM 484 boolean isReady = true; 485 for (SubscriptionInfo subInfo : subInfoList) { 486 isReady = isReady & tele.getSimState(subInfo.getSimSlotIndex()) == SIM_STATE_READY; 487 if (LOGD) Log.d(TAG, "hasReadyMobileRadio: subInfo=" + subInfo); 488 } 489 boolean retVal = conn.isNetworkSupported(TYPE_MOBILE) && isReady; 490 if (LOGD) { 491 Log.d(TAG, "hasReadyMobileRadio:" 492 + " conn.isNetworkSupported(TYPE_MOBILE)=" 493 + conn.isNetworkSupported(TYPE_MOBILE) 494 + " isReady=" + isReady); 495 } 496 return retVal; 497 } 498 499 /* 500 * TODO: consider adding to TelephonyManager or SubscriptionManager. 501 */ 502 public static boolean hasReadyMobileRadio(Context context, int subId) { 503 if (DataUsageUtils.TEST_RADIOS) { 504 return SystemProperties.get(DataUsageUtils.TEST_RADIOS_PROP).contains("mobile"); 505 } 506 507 final ConnectivityManager conn = ConnectivityManager.from(context); 508 final TelephonyManager tele = TelephonyManager.from(context); 509 final int slotId = SubscriptionManager.getSlotIndex(subId); 510 final boolean isReady = tele.getSimState(slotId) == SIM_STATE_READY; 511 512 boolean retVal = conn.isNetworkSupported(TYPE_MOBILE) && isReady; 513 if (LOGD) { 514 Log.d(TAG, "hasReadyMobileRadio: subId=" + subId 515 + " conn.isNetworkSupported(TYPE_MOBILE)=" 516 + conn.isNetworkSupported(TYPE_MOBILE) 517 + " isReady=" + isReady); 518 } 519 return retVal; 520 } 521 522 private OnItemSelectedListener mCycleListener = new OnItemSelectedListener() { 523 @Override 524 public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { 525 final CycleAdapter.CycleItem cycle = (CycleAdapter.CycleItem) 526 mCycleSpinner.getSelectedItem(); 527 528 if (LOGD) { 529 Log.d(TAG, "showing cycle " + cycle + ", start=" + cycle.start + ", end=" 530 + cycle.end + "]"); 531 } 532 533 // update chart to show selected cycle, and update detail data 534 // to match updated sweep bounds. 535 mChart.setVisibleRange(cycle.start, cycle.end); 536 537 updateDetailData(); 538 } 539 540 @Override 541 public void onNothingSelected(AdapterView<?> parent) { 542 // ignored 543 } 544 }; 545 546 private final LoaderCallbacks<ChartData> mChartDataCallbacks = new LoaderCallbacks< 547 ChartData>() { 548 @Override 549 public Loader<ChartData> onCreateLoader(int id, Bundle args) { 550 return new ChartDataLoader(getActivity(), mStatsSession, args); 551 } 552 553 @Override 554 public void onLoadFinished(Loader<ChartData> loader, ChartData data) { 555 mLoadingViewController.showContent(false /* animate */); 556 mChartData = data; 557 mChart.setNetworkStats(mChartData.network); 558 559 // calculate policy cycles based on available data 560 updatePolicy(); 561 } 562 563 @Override 564 public void onLoaderReset(Loader<ChartData> loader) { 565 mChartData = null; 566 mChart.setNetworkStats(null); 567 } 568 }; 569 570 private final LoaderCallbacks<NetworkStats> mSummaryCallbacks = new LoaderCallbacks< 571 NetworkStats>() { 572 @Override 573 public Loader<NetworkStats> onCreateLoader(int id, Bundle args) { 574 return new SummaryForAllUidLoader(getActivity(), mStatsSession, args); 575 } 576 577 @Override 578 public void onLoadFinished(Loader<NetworkStats> loader, NetworkStats data) { 579 final int[] restrictedUids = services.mPolicyManager.getUidsWithPolicy( 580 POLICY_REJECT_METERED_BACKGROUND); 581 bindStats(data, restrictedUids); 582 updateEmptyVisible(); 583 } 584 585 @Override 586 public void onLoaderReset(Loader<NetworkStats> loader) { 587 bindStats(null, new int[0]); 588 updateEmptyVisible(); 589 } 590 591 private void updateEmptyVisible() { 592 if ((mApps.getPreferenceCount() != 0) != 593 (getPreferenceScreen().getPreferenceCount() != 0)) { 594 if (mApps.getPreferenceCount() != 0) { 595 getPreferenceScreen().addPreference(mUsageAmount); 596 getPreferenceScreen().addPreference(mApps); 597 } else { 598 getPreferenceScreen().removeAll(); 599 } 600 } 601 } 602 }; 603 } 604