• 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 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