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
getMetricsCategory()112     protected 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         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
onResume()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
onDestroy()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      */
updateBody()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      */
updatePolicy(boolean refreshCycle)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      */
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      * @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      */
accumulate(int collapseKey, final SparseArray<AppItem> knownItems, NetworkStats.Entry entry, int itemCategory, ArrayList<AppItem> items, long largest)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      */
hasReadyMobileRadio(Context context)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      */
hasReadyMobileRadio(Context context, int subId)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