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