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