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 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
    112     protected 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         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
    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
    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      */
    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      */
    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      */
    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      */
    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 
    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      */
    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      */
    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      */
    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