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 import com.android.internal.logging.MetricsProto.MetricsEvent; 50 import com.android.settings.R; 51 import com.android.settingslib.AppItem; 52 import com.android.settingslib.net.ChartData; 53 import com.android.settingslib.net.ChartDataLoader; 54 import com.android.settingslib.net.SummaryForAllUidLoader; 55 import com.android.settingslib.net.UidDetailProvider; 56 57 import java.util.ArrayList; 58 import java.util.Collections; 59 import java.util.HashMap; 60 import java.util.List; 61 import java.util.Map; 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 private static final String TAG = "DataUsage"; 77 private static final boolean LOGD = false; 78 79 private static final String KEY_USAGE_AMOUNT = "usage_amount"; 80 private static final String KEY_CHART_DATA = "chart_data"; 81 private static final String KEY_APPS_GROUP = "apps_group"; 82 83 private static final int LOADER_CHART_DATA = 2; 84 private static final int LOADER_SUMMARY = 3; 85 public static final String EXTRA_SUB_ID = "sub_id"; 86 public static final String EXTRA_NETWORK_TEMPLATE = "network_template"; 87 88 private INetworkStatsSession mStatsSession; 89 90 private ChartDataUsagePreference mChart; 91 92 private NetworkTemplate mTemplate; 93 private int mSubId; 94 private ChartData mChartData; 95 96 /** Flag used to ignore listeners during binding. */ 97 private boolean mBinding; 98 99 private UidDetailProvider mUidDetailProvider; 100 101 /** 102 * Local cache of data enabled for subId, used to work around delays. 103 */ 104 private final Map<String, Boolean> mMobileDataEnabled = new HashMap<String, Boolean>(); 105 private CycleAdapter mCycleAdapter; 106 private Spinner mCycleSpinner; 107 private Preference mUsageAmount; 108 private PreferenceGroup mApps; 109 private View mHeader; 110 111 @Override 112 protected int getMetricsCategory() { 113 return MetricsEvent.DATA_USAGE_LIST; 114 } 115 116 @Override 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 145 public void onViewCreated(View v, Bundle savedInstanceState) { 146 super.onViewCreated(v, savedInstanceState); 147 148 mHeader = setPinnedHeaderView(R.layout.apps_filter_spinner); 149 mCycleSpinner = (Spinner) mHeader.findViewById(R.id.filter_spinner); 150 mCycleAdapter = new CycleAdapter(getContext(), new CycleAdapter.SpinnerInterface() { 151 @Override 152 public void setAdapter(CycleAdapter cycleAdapter) { 153 mCycleSpinner.setAdapter(cycleAdapter); 154 } 155 156 @Override 157 public void setOnItemSelectedListener(OnItemSelectedListener listener) { 158 mCycleSpinner.setOnItemSelectedListener(listener); 159 } 160 161 @Override 162 public Object getSelectedItem() { 163 return mCycleSpinner.getSelectedItem(); 164 } 165 166 @Override 167 public void setSelection(int position) { 168 mCycleSpinner.setSelection(position); 169 } 170 }, mCycleListener, true); 171 setLoading(true, false); 172 } 173 174 @Override 175 public void onResume() { 176 super.onResume(); 177 178 updateBody(); 179 180 // kick off background task to update stats 181 new AsyncTask<Void, Void, Void>() { 182 @Override 183 protected Void doInBackground(Void... params) { 184 try { 185 // wait a few seconds before kicking off 186 Thread.sleep(2 * DateUtils.SECOND_IN_MILLIS); 187 services.mStatsService.forceUpdate(); 188 } catch (InterruptedException e) { 189 } catch (RemoteException e) { 190 } 191 return null; 192 } 193 194 @Override 195 protected void onPostExecute(Void result) { 196 if (isAdded()) { 197 updateBody(); 198 } 199 } 200 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 201 } 202 203 @Override 204 public void onDestroy() { 205 mUidDetailProvider.clearCache(); 206 mUidDetailProvider = null; 207 208 TrafficStats.closeQuietly(mStatsSession); 209 210 super.onDestroy(); 211 } 212 213 /** 214 * Update body content based on current tab. Loads 215 * {@link NetworkStatsHistory} and {@link NetworkPolicy} from system, and 216 * binds them to visible controls. 217 */ 218 private void updateBody() { 219 mBinding = true; 220 if (!isAdded()) return; 221 222 final Context context = getActivity(); 223 224 // kick off loader for network history 225 // TODO: consider chaining two loaders together instead of reloading 226 // network history when showing app detail. 227 getLoaderManager().restartLoader(LOADER_CHART_DATA, 228 ChartDataLoader.buildArgs(mTemplate, null), mChartDataCallbacks); 229 230 // detail mode can change visible menus, invalidate 231 getActivity().invalidateOptionsMenu(); 232 233 mBinding = false; 234 235 int seriesColor = context.getColor(R.color.sim_noitification); 236 if (mSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID){ 237 final SubscriptionInfo sir = services.mSubscriptionManager 238 .getActiveSubscriptionInfo(mSubId); 239 240 if (sir != null) { 241 seriesColor = sir.getIconTint(); 242 } 243 } 244 245 final int secondaryColor = Color.argb(127, Color.red(seriesColor), Color.green(seriesColor), 246 Color.blue(seriesColor)); 247 mChart.setColors(seriesColor, secondaryColor); 248 } 249 250 /** 251 * Update chart sweeps and cycle list to reflect {@link NetworkPolicy} for 252 * current {@link #mTemplate}. 253 */ 254 private void updatePolicy(boolean refreshCycle) { 255 final NetworkPolicy policy = services.mPolicyEditor.getPolicy(mTemplate); 256 //SUB SELECT 257 if (isNetworkPolicyModifiable(policy, mSubId) && isMobileDataAvailable(mSubId)) { 258 mChart.setNetworkPolicy(policy); 259 mHeader.findViewById(R.id.filter_settings).setVisibility(View.VISIBLE); 260 mHeader.findViewById(R.id.filter_settings).setOnClickListener( 261 new View.OnClickListener() { 262 @Override 263 public void onClick(View v) { 264 Bundle args = new Bundle(); 265 args.putParcelable(DataUsageList.EXTRA_NETWORK_TEMPLATE, mTemplate); 266 startFragment(DataUsageList.this, BillingCycleSettings.class.getName(), 267 R.string.billing_cycle, 0, args); 268 } 269 }); 270 } else { 271 // controls are disabled; don't bind warning/limit sweeps 272 mChart.setNetworkPolicy(null); 273 mHeader.findViewById(R.id.filter_settings).setVisibility(View.GONE); 274 } 275 276 if (refreshCycle) { 277 // generate cycle list based on policy and available history 278 if (mCycleAdapter.updateCycleList(policy, mChartData)) { 279 updateDetailData(); 280 } 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 */ 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 */ 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 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 * @param collapseKey the collapse key used to map the item. 416 * @param knownItems collection of known (already existing) items. 417 * @param entry the network stats entry to extract data usage from. 418 * @param itemCategory the item is categorized on the list view by this category. Must be 419 */ 420 private static long accumulate(int collapseKey, final SparseArray<AppItem> knownItems, 421 NetworkStats.Entry entry, int itemCategory, ArrayList<AppItem> items, long largest) { 422 final int uid = entry.uid; 423 AppItem item = knownItems.get(collapseKey); 424 if (item == null) { 425 item = new AppItem(collapseKey); 426 item.category = itemCategory; 427 items.add(item); 428 knownItems.put(item.key, item); 429 } 430 item.addUid(uid); 431 item.total += entry.rxBytes + entry.txBytes; 432 return Math.max(largest, item.total); 433 } 434 435 /** 436 * Test if device has a mobile data radio with SIM in ready state. 437 */ 438 public static boolean hasReadyMobileRadio(Context context) { 439 if (TEST_RADIOS) { 440 return SystemProperties.get(TEST_RADIOS_PROP).contains("mobile"); 441 } 442 443 final ConnectivityManager conn = ConnectivityManager.from(context); 444 final TelephonyManager tele = TelephonyManager.from(context); 445 446 final List<SubscriptionInfo> subInfoList = 447 SubscriptionManager.from(context).getActiveSubscriptionInfoList(); 448 // No activated Subscriptions 449 if (subInfoList == null) { 450 if (LOGD) Log.d(TAG, "hasReadyMobileRadio: subInfoList=null"); 451 return false; 452 } 453 // require both supported network and ready SIM 454 boolean isReady = true; 455 for (SubscriptionInfo subInfo : subInfoList) { 456 isReady = isReady & tele.getSimState(subInfo.getSimSlotIndex()) == SIM_STATE_READY; 457 if (LOGD) Log.d(TAG, "hasReadyMobileRadio: subInfo=" + subInfo); 458 } 459 boolean retVal = conn.isNetworkSupported(TYPE_MOBILE) && isReady; 460 if (LOGD) { 461 Log.d(TAG, "hasReadyMobileRadio:" 462 + " conn.isNetworkSupported(TYPE_MOBILE)=" 463 + conn.isNetworkSupported(TYPE_MOBILE) 464 + " isReady=" + isReady); 465 } 466 return retVal; 467 } 468 469 /* 470 * TODO: consider adding to TelephonyManager or SubscriptionManager. 471 */ 472 public static boolean hasReadyMobileRadio(Context context, int subId) { 473 if (TEST_RADIOS) { 474 return SystemProperties.get(TEST_RADIOS_PROP).contains("mobile"); 475 } 476 477 final ConnectivityManager conn = ConnectivityManager.from(context); 478 final TelephonyManager tele = TelephonyManager.from(context); 479 final int slotId = SubscriptionManager.getSlotId(subId); 480 final boolean isReady = tele.getSimState(slotId) == SIM_STATE_READY; 481 482 boolean retVal = conn.isNetworkSupported(TYPE_MOBILE) && isReady; 483 if (LOGD) Log.d(TAG, "hasReadyMobileRadio: subId=" + subId 484 + " conn.isNetworkSupported(TYPE_MOBILE)=" + conn.isNetworkSupported(TYPE_MOBILE) 485 + " isReady=" + isReady); 486 return retVal; 487 } 488 489 private OnItemSelectedListener mCycleListener = new OnItemSelectedListener() { 490 @Override 491 public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { 492 final CycleAdapter.CycleItem cycle = (CycleAdapter.CycleItem) 493 mCycleSpinner.getSelectedItem(); 494 495 if (LOGD) { 496 Log.d(TAG, "showing cycle " + cycle + ", start=" + cycle.start + ", end=" 497 + cycle.end + "]"); 498 } 499 500 // update chart to show selected cycle, and update detail data 501 // to match updated sweep bounds. 502 mChart.setVisibleRange(cycle.start, cycle.end); 503 504 updateDetailData(); 505 } 506 507 @Override 508 public void onNothingSelected(AdapterView<?> parent) { 509 // ignored 510 } 511 }; 512 513 private final LoaderCallbacks<ChartData> mChartDataCallbacks = new LoaderCallbacks< 514 ChartData>() { 515 @Override 516 public Loader<ChartData> onCreateLoader(int id, Bundle args) { 517 return new ChartDataLoader(getActivity(), mStatsSession, args); 518 } 519 520 @Override 521 public void onLoadFinished(Loader<ChartData> loader, ChartData data) { 522 setLoading(false, true); 523 mChartData = data; 524 mChart.setNetworkStats(mChartData.network); 525 526 // calcuate policy cycles based on available data 527 updatePolicy(true); 528 } 529 530 @Override 531 public void onLoaderReset(Loader<ChartData> loader) { 532 mChartData = null; 533 mChart.setNetworkStats(null); 534 } 535 }; 536 537 private final LoaderCallbacks<NetworkStats> mSummaryCallbacks = new LoaderCallbacks< 538 NetworkStats>() { 539 @Override 540 public Loader<NetworkStats> onCreateLoader(int id, Bundle args) { 541 return new SummaryForAllUidLoader(getActivity(), mStatsSession, args); 542 } 543 544 @Override 545 public void onLoadFinished(Loader<NetworkStats> loader, NetworkStats data) { 546 final int[] restrictedUids = services.mPolicyManager.getUidsWithPolicy( 547 POLICY_REJECT_METERED_BACKGROUND); 548 bindStats(data, restrictedUids); 549 updateEmptyVisible(); 550 } 551 552 @Override 553 public void onLoaderReset(Loader<NetworkStats> loader) { 554 bindStats(null, new int[0]); 555 updateEmptyVisible(); 556 } 557 558 private void updateEmptyVisible() { 559 if ((mApps.getPreferenceCount() != 0) != 560 (getPreferenceScreen().getPreferenceCount() != 0)) { 561 if (mApps.getPreferenceCount() != 0) { 562 getPreferenceScreen().addPreference(mUsageAmount); 563 getPreferenceScreen().addPreference(mApps); 564 } else { 565 getPreferenceScreen().removeAll(); 566 } 567 } 568 } 569 }; 570 } 571