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