Home | History | Annotate | Download | only in datausage
      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
    112     public int getMetricsCategory() {
    113         return MetricsEvent.DATA_USAGE_LIST;
    114     }
    115 
    116     @Override
    117     public void onCreate(Bundle savedInstanceState) {
    118         super.onCreate(savedInstanceState);
    119         final Context context = getActivity();
    120 
    121         if (!isBandwidthControlEnabled()) {
    122             Log.w(TAG, "No bandwidth control; leaving");
    123             getActivity().finish();
    124         }
    125 
    126         try {
    127             mStatsSession = services.mStatsService.openSession();
    128         } catch (RemoteException e) {
    129             throw new RuntimeException(e);
    130         }
    131 
    132         mUidDetailProvider = new UidDetailProvider(context);
    133 
    134         addPreferencesFromResource(R.xml.data_usage_list);
    135         mUsageAmount = findPreference(KEY_USAGE_AMOUNT);
    136         mChart = (ChartDataUsagePreference) findPreference(KEY_CHART_DATA);
    137         mApps = (PreferenceGroup) findPreference(KEY_APPS_GROUP);
    138 
    139         final Bundle args = getArguments();
    140         mSubId = args.getInt(EXTRA_SUB_ID, SubscriptionManager.INVALID_SUBSCRIPTION_ID);
    141         mTemplate = args.getParcelable(EXTRA_NETWORK_TEMPLATE);
    142     }
    143 
    144     @Override
    145     public void onViewCreated(View v, Bundle savedInstanceState) {
    146         super.onViewCreated(v, savedInstanceState);
    147 
    148         mHeader = setPinnedHeaderView(R.layout.apps_filter_spinner);
    149         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
    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
    213     public void onPause() {
    214         super.onPause();
    215         mDataStateListener.setListener(false, mSubId, getContext());
    216     }
    217 
    218     @Override
    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      */
    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      */
    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      */
    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      */
    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 
    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      */
    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      */
    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      */
    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