• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
getMetricsCategory()119     public int getMetricsCategory() {
120         return MetricsEvent.DATA_USAGE_LIST;
121     }
122 
123     @Override
onCreate(Bundle savedInstanceState)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
onViewCreated(View v, Bundle savedInstanceState)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
onResume()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
onPause()221     public void onPause() {
222         super.onPause();
223         mDataStateListener.setListener(false, mSubId, getContext());
224     }
225 
226     @Override
onDestroy()227     public void onDestroy() {
228         mUidDetailProvider.clearCache();
229         mUidDetailProvider = null;
230 
231         TrafficStats.closeQuietly(mStatsSession);
232 
233         super.onDestroy();
234     }
235 
processArgument()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      */
updateBody()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      */
updatePolicy()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      */
updateDetailData()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      */
bindStats(NetworkStats stats, int[] restrictedUids)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 
startAppDataUsage(AppItem item)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      */
accumulate(int collapseKey, final SparseArray<AppItem> knownItems, NetworkStats.Entry entry, int itemCategory, ArrayList<AppItem> items, long largest)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      */
hasReadyMobileRadio(Context context)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      */
hasReadyMobileRadio(Context context, int subId)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