Update data usage UX
Update the UX and dig the data usage screen out of a huge whole of
technical debt. Switch every to use Preferences rather than standard
layouts and ListViews.
Split data usage into several fragments, all separated.
DataUsageSummary:
- Shows a summary of the 'default' usage at the top, this will be
the default sim on phones, or wifi if it has it, or ethernet
as last attempt to show something.
- Also has individual categories for each network type that has
data, cell, wifi, and ethernet. Maybe should look into bt though?
DataUsageList:
- Takes a NetworkTemplate as an input, and can only be reached from
the network specific categories in DataUsageSummary
- Shows a graph of current usage for that network and links to
app detail page for any app.
- Has gear link to quick get to billing cycle screen if available
BillingCycleSettings:
- Just a screen with the cycle day and warning/limits separated
out from the data usage.
AppDataUsage:
- App specific data usage details
- May need some UX iteration given lack of clarity in the spec
Bug: 22459566
Change-Id: I0222d8d7ea7b75a9775207a6026ebbdcce8f5e46
diff --git a/src/com/android/settings/AppHeader.java b/src/com/android/settings/AppHeader.java
index 257b2f0..36026bb 100644
--- a/src/com/android/settings/AppHeader.java
+++ b/src/com/android/settings/AppHeader.java
@@ -57,7 +57,7 @@
tintColorRes, bar);
}
- private static View setupHeaderView(final Activity activity, Drawable icon, CharSequence label,
+ public static View setupHeaderView(final Activity activity, Drawable icon, CharSequence label,
final String pkgName, final int uid, boolean includeAppInfo, int tintColorRes,
View bar) {
final ImageView appIcon = (ImageView) bar.findViewById(R.id.app_icon);
@@ -86,7 +86,7 @@
return bar;
}
- private static boolean includeAppInfo(final Fragment fragment) {
+ public static boolean includeAppInfo(final Fragment fragment) {
Bundle args = fragment.getArguments();
boolean showInfo = true;
if (args != null && args.getBoolean(EXTRA_HIDE_INFO_BUTTON, false)) {
diff --git a/src/com/android/settings/DataUsageSummary.java b/src/com/android/settings/DataUsageSummary.java
deleted file mode 100644
index c9a2999..0000000
--- a/src/com/android/settings/DataUsageSummary.java
+++ /dev/null
@@ -1,2848 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings;
-
-import android.animation.LayoutTransition;
-import android.app.Activity;
-import android.app.ActivityManager;
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.app.DialogFragment;
-import android.app.Fragment;
-import android.app.FragmentTransaction;
-import android.app.LoaderManager.LoaderCallbacks;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.Loader;
-import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.UserInfo;
-import android.content.res.Resources;
-import android.graphics.Color;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.icu.impl.ICUResourceBundle;
-import android.icu.util.UResourceBundle;
-import android.net.ConnectivityManager;
-import android.net.INetworkPolicyManager;
-import android.net.INetworkStatsService;
-import android.net.INetworkStatsSession;
-import android.net.NetworkPolicy;
-import android.net.NetworkPolicyManager;
-import android.net.NetworkStats;
-import android.net.NetworkStatsHistory;
-import android.net.NetworkTemplate;
-import android.net.TrafficStats;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.os.INetworkManagementService;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.SystemProperties;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.support.v7.preference.Preference;
-import android.telephony.SubscriptionInfo;
-import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
-import android.text.TextUtils;
-import android.text.format.DateUtils;
-import android.text.format.Formatter;
-import android.text.format.Time;
-import android.util.Log;
-import android.util.SparseArray;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemClickListener;
-import android.widget.AdapterView.OnItemSelectedListener;
-import android.widget.ArrayAdapter;
-import android.widget.BaseAdapter;
-import android.widget.Button;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.ListView;
-import android.widget.NumberPicker;
-import android.widget.ProgressBar;
-import android.widget.Spinner;
-import android.widget.Switch;
-import android.widget.TabHost;
-import android.widget.TabHost.OnTabChangeListener;
-import android.widget.TabHost.TabContentFactory;
-import android.widget.TabHost.TabSpec;
-import android.widget.TabWidget;
-import android.widget.TextView;
-import android.widget.Toast;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.telephony.PhoneConstants;
-import com.android.settings.dashboard.SummaryLoader;
-import com.android.settings.dashboard.conditional.BackgroundDataCondition;
-import com.android.settings.dashboard.conditional.ConditionManager;
-import com.android.settings.drawable.InsetBoundsDrawable;
-import com.android.settings.net.DataUsageMeteredSettings;
-import com.android.settings.search.BaseSearchIndexProvider;
-import com.android.settings.search.Indexable;
-import com.android.settings.search.SearchIndexableRaw;
-import com.android.settings.widget.ChartDataUsageView;
-import com.android.settings.widget.ChartDataUsageView.DataUsageChartListener;
-import com.android.settings.widget.ChartNetworkSeriesView;
-import com.android.settingslib.AppItem;
-import com.android.settingslib.NetworkPolicyEditor;
-import com.android.settingslib.net.ChartData;
-import com.android.settingslib.net.ChartDataLoader;
-import com.android.settingslib.net.MobileDataController;
-import com.android.settingslib.net.SummaryForAllUidLoader;
-import com.android.settingslib.net.UidDetail;
-import com.android.settingslib.net.UidDetailProvider;
-import com.google.android.collect.Lists;
-import libcore.util.Objects;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-
-import static android.net.ConnectivityManager.TYPE_ETHERNET;
-import static android.net.ConnectivityManager.TYPE_MOBILE;
-import static android.net.ConnectivityManager.TYPE_WIFI;
-import static android.net.ConnectivityManager.TYPE_WIMAX;
-import static android.net.NetworkPolicy.LIMIT_DISABLED;
-import static android.net.NetworkPolicy.WARNING_DISABLED;
-import static android.net.NetworkPolicyManager.EXTRA_NETWORK_TEMPLATE;
-import static android.net.NetworkPolicyManager.POLICY_NONE;
-import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
-import static android.net.NetworkPolicyManager.computeLastCycleBoundary;
-import static android.net.NetworkPolicyManager.computeNextCycleBoundary;
-import static android.net.NetworkTemplate.MATCH_MOBILE_3G_LOWER;
-import static android.net.NetworkTemplate.MATCH_MOBILE_4G;
-import static android.net.NetworkTemplate.MATCH_MOBILE_ALL;
-import static android.net.NetworkTemplate.MATCH_WIFI;
-import static android.net.NetworkTemplate.buildTemplateEthernet;
-import static android.net.NetworkTemplate.buildTemplateMobile3gLower;
-import static android.net.NetworkTemplate.buildTemplateMobile4g;
-import static android.net.NetworkTemplate.buildTemplateMobileAll;
-import static android.net.NetworkTemplate.buildTemplateWifiWildcard;
-import static android.net.TrafficStats.GB_IN_BYTES;
-import static android.net.TrafficStats.MB_IN_BYTES;
-import static android.net.TrafficStats.UID_REMOVED;
-import static android.net.TrafficStats.UID_TETHERING;
-import static android.telephony.TelephonyManager.SIM_STATE_READY;
-import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH;
-import static android.text.format.DateUtils.FORMAT_SHOW_DATE;
-import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-import static com.android.internal.util.Preconditions.checkNotNull;
-import static com.android.settings.Utils.prepareCustomPreferencesList;
-
-/**
- * Panel showing data usage history across various networks, including options
- * to inspect based on usage cycle and control through {@link NetworkPolicy}.
- */
-public class DataUsageSummary extends HighlightingFragment implements Indexable {
- private static final String TAG = "DataUsage";
- private static final boolean LOGD = false;
-
- // TODO: remove this testing code
- private static final boolean TEST_ANIM = false;
- private static final boolean TEST_RADIOS = false;
-
- private static final String TEST_RADIOS_PROP = "test.radios";
- private static final String TEST_SUBSCRIBER_PROP = "test.subscriberid";
-
- private static final String TAB_3G = "3g";
- private static final String TAB_4G = "4g";
- private static final String TAB_MOBILE = "mobile";
- private static final String TAB_WIFI = "wifi";
- private static final String TAB_ETHERNET = "ethernet";
-
- private static final String TAG_CONFIRM_DATA_DISABLE = "confirmDataDisable";
- private static final String TAG_CONFIRM_LIMIT = "confirmLimit";
- private static final String TAG_CYCLE_EDITOR = "cycleEditor";
- private static final String TAG_WARNING_EDITOR = "warningEditor";
- private static final String TAG_LIMIT_EDITOR = "limitEditor";
- private static final String TAG_CONFIRM_RESTRICT = "confirmRestrict";
- private static final String TAG_DENIED_RESTRICT = "deniedRestrict";
- private static final String TAG_CONFIRM_APP_RESTRICT = "confirmAppRestrict";
- private static final String TAG_APP_DETAILS = "appDetails";
-
- private static final String DATA_USAGE_ENABLE_MOBILE_KEY = "data_usage_enable_mobile";
- private static final String DATA_USAGE_DISABLE_MOBILE_LIMIT_KEY =
- "data_usage_disable_mobile_limit";
- private static final String DATA_USAGE_CYCLE_KEY = "data_usage_cycle";
-
- public static final String EXTRA_SHOW_APP_IMMEDIATE_PKG = "showAppImmediatePkg";
-
- private static final int LOADER_CHART_DATA = 2;
- private static final int LOADER_SUMMARY = 3;
-
- private INetworkManagementService mNetworkService;
- private INetworkStatsService mStatsService;
- private NetworkPolicyManager mPolicyManager;
- private TelephonyManager mTelephonyManager;
- private SubscriptionManager mSubscriptionManager;
- private UserManager mUserManager;
-
- private INetworkStatsSession mStatsSession;
-
- private static final String PREF_FILE = "data_usage";
- private static final String PREF_SHOW_WIFI = "show_wifi";
- private static final String PREF_SHOW_ETHERNET = "show_ethernet";
-
- private SharedPreferences mPrefs;
-
- private TabHost mTabHost;
- private ViewGroup mTabsContainer;
- private TabWidget mTabWidget;
- private ListView mListView;
- private ChartNetworkSeriesView mSeries;
- private ChartNetworkSeriesView mDetailedSeries;
- private DataUsageAdapter mAdapter;
-
- /** Distance to inset content from sides, when needed. */
- private int mInsetSide = 0;
-
- private ViewGroup mHeader;
-
- private ViewGroup mNetworkSwitchesContainer;
- private LinearLayout mNetworkSwitches;
- private boolean mDataEnabledSupported;
- private Switch mDataEnabled;
- private View mDataEnabledView;
- private boolean mDisableAtLimitSupported;
- private Switch mDisableAtLimit;
- private View mDisableAtLimitView;
-
- private View mCycleView;
- private Spinner mCycleSpinner;
- private CycleAdapter mCycleAdapter;
- private TextView mCycleSummary;
-
- private ChartDataUsageView mChart;
- private View mDisclaimer;
- private TextView mEmpty;
- private View mStupidPadding;
-
- private View mAppDetail;
- private ImageView mAppIcon;
- private ViewGroup mAppTitles;
- private TextView mAppTotal;
- private TextView mAppForeground;
- private TextView mAppBackground;
- private Button mAppSettings;
-
- private LinearLayout mAppSwitches;
- private Switch mAppRestrict;
- private View mAppRestrictView;
-
- private boolean mShowWifi = false;
- private boolean mShowEthernet = false;
-
- private NetworkTemplate mTemplate;
- private ChartData mChartData;
-
- private AppItem mCurrentApp = null;
-
- private Intent mAppSettingsIntent;
-
- private NetworkPolicyEditor mPolicyEditor;
-
- private String mCurrentTab = null;
- private String mIntentTab = null;
-
- private MenuItem mMenuRestrictBackground;
- private MenuItem mMenuShowWifi;
- private MenuItem mMenuShowEthernet;
- private MenuItem mMenuSimCards;
- private MenuItem mMenuCellularNetworks;
-
- private List<SubscriptionInfo> mSubInfoList;
- private Map<Integer,String> mMobileTagMap;
-
- /** Flag used to ignore listeners during binding. */
- private boolean mBinding;
-
- private UidDetailProvider mUidDetailProvider;
-
- // Indicates request to show app immediately rather than list.
- private String mShowAppImmediatePkg;
-
- /**
- * Local cache of data enabled for subId, used to work around delays.
- */
- private final Map<String, Boolean> mMobileDataEnabled = new HashMap<String, Boolean>();
-
- @Override
- protected int getMetricsCategory() {
- return MetricsLogger.DATA_USAGE_SUMMARY;
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- final Context context = getActivity();
-
- mNetworkService = INetworkManagementService.Stub.asInterface(
- ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
- mStatsService = INetworkStatsService.Stub.asInterface(
- ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
- mPolicyManager = NetworkPolicyManager.from(context);
- mTelephonyManager = TelephonyManager.from(context);
- mSubscriptionManager = SubscriptionManager.from(context);
- mUserManager = UserManager.get(context);
-
- mPrefs = getActivity().getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE);
-
- mPolicyEditor = new NetworkPolicyEditor(mPolicyManager);
- mPolicyEditor.read();
-
- mSubInfoList = mSubscriptionManager.getActiveSubscriptionInfoList();
- mMobileTagMap = initMobileTabTag(mSubInfoList);
-
- try {
- if (!mNetworkService.isBandwidthControlEnabled()) {
- Log.w(TAG, "No bandwidth control; leaving");
- getActivity().finish();
- }
- } catch (RemoteException e) {
- Log.w(TAG, "No bandwidth control; leaving");
- getActivity().finish();
- }
-
- try {
- mStatsSession = mStatsService.openSession();
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
-
- mShowWifi = mPrefs.getBoolean(PREF_SHOW_WIFI, false);
- mShowEthernet = mPrefs.getBoolean(PREF_SHOW_ETHERNET, false);
-
- // override preferences when no mobile radio
- if (!hasReadyMobileRadio(context)) {
- mShowWifi = true;
- mShowEthernet = true;
- }
-
- mUidDetailProvider = new UidDetailProvider(context);
-
- Bundle arguments = getArguments();
- if (arguments != null) {
- mShowAppImmediatePkg = arguments.getString(EXTRA_SHOW_APP_IMMEDIATE_PKG);
- }
-
- setHasOptionsMenu(true);
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
-
- final Context context = inflater.getContext();
- final View view = inflater.inflate(R.layout.data_usage_summary, container, false);
-
-
- mTabHost = (TabHost) view.findViewById(android.R.id.tabhost);
- mTabsContainer = (ViewGroup) view.findViewById(R.id.tabs_container);
- mTabWidget = (TabWidget) view.findViewById(android.R.id.tabs);
- mListView = (ListView) view.findViewById(android.R.id.list);
-
- // decide if we need to manually inset our content, or if we should rely
- // on parent container for inset.
- final boolean shouldInset = mListView.getScrollBarStyle()
- == View.SCROLLBARS_OUTSIDE_OVERLAY;
- mInsetSide = 0;
-
- // adjust padding around tabwidget as needed
- prepareCustomPreferencesList(container, view, mListView, false);
-
- mTabHost.setup();
- mTabHost.setOnTabChangedListener(mTabListener);
-
- mHeader = (ViewGroup) inflater.inflate(R.layout.data_usage_header, mListView, false);
- mHeader.setClickable(true);
-
- mListView.addHeaderView(new View(context), null, true);
- mListView.addHeaderView(mHeader, null, true);
- mListView.setItemsCanFocus(true);
-
- if (mInsetSide > 0) {
- // inset selector and divider drawables
- insetListViewDrawables(mListView, mInsetSide);
- mHeader.setPaddingRelative(mInsetSide, 0, mInsetSide, 0);
- }
-
- {
- // bind network switches
- mNetworkSwitchesContainer = (ViewGroup) mHeader.findViewById(
- R.id.network_switches_container);
- mNetworkSwitches = (LinearLayout) mHeader.findViewById(R.id.network_switches);
-
- mDataEnabled = new Switch(inflater.getContext());
- mDataEnabled.setClickable(false);
- mDataEnabled.setFocusable(false);
- mDataEnabledView = inflatePreference(inflater, mNetworkSwitches, mDataEnabled);
- mDataEnabledView.setTag(R.id.preference_highlight_key,
- DATA_USAGE_ENABLE_MOBILE_KEY);
- mDataEnabledView.setClickable(true);
- mDataEnabledView.setFocusable(true);
- mDataEnabledView.setOnClickListener(mDataEnabledListener);
- mNetworkSwitches.addView(mDataEnabledView);
-
- mDisableAtLimit = new Switch(inflater.getContext());
- mDisableAtLimit.setClickable(false);
- mDisableAtLimit.setFocusable(false);
- mDisableAtLimitView = inflatePreference(inflater, mNetworkSwitches, mDisableAtLimit);
- mDisableAtLimitView.setTag(R.id.preference_highlight_key,
- DATA_USAGE_DISABLE_MOBILE_LIMIT_KEY);
- mDisableAtLimitView.setClickable(true);
- mDisableAtLimitView.setFocusable(true);
- mDisableAtLimitView.setOnClickListener(mDisableAtLimitListener);
- mNetworkSwitches.addView(mDisableAtLimitView);
-
- mCycleView = inflater.inflate(R.layout.data_usage_cycles, mNetworkSwitches, false);
- mCycleView.setTag(R.id.preference_highlight_key, DATA_USAGE_CYCLE_KEY);
- mCycleSpinner = (Spinner) mCycleView.findViewById(R.id.cycles_spinner);
- mCycleAdapter = new CycleAdapter(context);
- mCycleSpinner.setAdapter(mCycleAdapter);
- mCycleSpinner.setOnItemSelectedListener(mCycleListener);
- mCycleSummary = (TextView) mCycleView.findViewById(R.id.cycle_summary);
- mNetworkSwitches.addView(mCycleView);
- mSeries = (ChartNetworkSeriesView)view.findViewById(R.id.series);
- mDetailedSeries = (ChartNetworkSeriesView)view.findViewById(R.id.detail_series);
- }
-
- mChart = (ChartDataUsageView) mHeader.findViewById(R.id.chart);
- mChart.setListener(mChartListener);
- mChart.bindNetworkPolicy(null);
-
- {
- // bind app detail controls
- mAppDetail = mHeader.findViewById(R.id.app_detail);
- mAppIcon = (ImageView) mAppDetail.findViewById(R.id.app_icon);
- mAppTitles = (ViewGroup) mAppDetail.findViewById(R.id.app_titles);
- mAppForeground = (TextView) mAppDetail.findViewById(R.id.app_foreground);
- mAppBackground = (TextView) mAppDetail.findViewById(R.id.app_background);
- mAppSwitches = (LinearLayout) mAppDetail.findViewById(R.id.app_switches);
-
- mAppSettings = (Button) mAppDetail.findViewById(R.id.app_settings);
-
- mAppRestrict = new Switch(inflater.getContext());
- mAppRestrict.setClickable(false);
- mAppRestrict.setFocusable(false);
- mAppRestrictView = inflatePreference(inflater, mAppSwitches, mAppRestrict);
- mAppRestrictView.setClickable(true);
- mAppRestrictView.setFocusable(true);
- mAppRestrictView.setOnClickListener(mAppRestrictListener);
- mAppSwitches.addView(mAppRestrictView);
- }
-
- mDisclaimer = mHeader.findViewById(R.id.disclaimer);
- mEmpty = (TextView) mHeader.findViewById(android.R.id.empty);
- mStupidPadding = mHeader.findViewById(R.id.stupid_padding);
-
- final UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
- mAdapter = new DataUsageAdapter(um, mUidDetailProvider, mInsetSide);
- mListView.setOnItemClickListener(mListListener);
- mListView.setAdapter(mAdapter);
-
- showRequestedAppIfNeeded(view);
-
- return view;
- }
-
- private void showRequestedAppIfNeeded(View rootView) {
- if (mShowAppImmediatePkg == null) {
- return;
- }
- try {
- int uid = getActivity().getPackageManager().getPackageUidAsUser(mShowAppImmediatePkg,
- UserHandle.myUserId());
- AppItem app = new AppItem(uid);
- app.addUid(uid);
-
- final UidDetail detail = mUidDetailProvider.getUidDetail(app.key, true);
- // When we are going straight to an app then we are coming from App Info and want
- // a header at the top.
- FrameLayout pinnedHeader = (FrameLayout) rootView.findViewById(R.id.pinned_header);
- AppHeader.createAppHeader(getActivity(), detail.icon, detail.label,
- mShowAppImmediatePkg, uid, pinnedHeader);
- AppDetailsFragment.show(DataUsageSummary.this, app, detail.label, false);
- } catch (NameNotFoundException e) {
- Log.w(TAG, "Could not find " + mShowAppImmediatePkg, e);
- Toast.makeText(getActivity(), getString(R.string.unknown_app), Toast.LENGTH_LONG)
- .show();
- getActivity().finish();
- }
- }
-
- @Override
- public void onViewStateRestored(Bundle savedInstanceState) {
- super.onViewStateRestored(savedInstanceState);
-
- // pick default tab based on incoming intent
- final Intent intent = getActivity().getIntent();
- mIntentTab = computeTabFromIntent(intent);
-
- // this kicks off chain reaction which creates tabs, binds the body to
- // selected network, and binds chart, cycles and detail list.
- updateTabs();
- }
-
- @Override
- public void onResume() {
- super.onResume();
-
- getView().post(new Runnable() {
- @Override
- public void run() {
- highlightViewIfNeeded();
- }
- });
-
- // kick off background task to update stats
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... params) {
- try {
- // wait a few seconds before kicking off
- Thread.sleep(2 * DateUtils.SECOND_IN_MILLIS);
- mStatsService.forceUpdate();
- } catch (InterruptedException e) {
- } catch (RemoteException e) {
- }
- return null;
- }
-
- @Override
- protected void onPostExecute(Void result) {
- if (isAdded()) {
- updateBody();
- }
- }
- }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
- }
-
- @Override
- public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
- inflater.inflate(R.menu.data_usage, menu);
- }
-
- @Override
- public void onPrepareOptionsMenu(Menu menu) {
- final Context context = getContext();
- if (context == null) return;
- final boolean appDetailMode = isAppDetailMode();
- final boolean isAdmin = mUserManager.isAdminUser();
-
- mMenuShowWifi = menu.findItem(R.id.data_usage_menu_show_wifi);
- if (hasWifiRadio(context) && hasReadyMobileRadio(context)) {
- mMenuShowWifi.setVisible(!appDetailMode);
- } else {
- mMenuShowWifi.setVisible(false);
- }
-
- mMenuShowEthernet = menu.findItem(R.id.data_usage_menu_show_ethernet);
- if (hasEthernet(context) && hasReadyMobileRadio(context)) {
- mMenuShowEthernet.setVisible(!appDetailMode);
- } else {
- mMenuShowEthernet.setVisible(false);
- }
-
- mMenuRestrictBackground = menu.findItem(R.id.data_usage_menu_restrict_background);
- mMenuRestrictBackground.setVisible(
- hasReadyMobileRadio(context) && isAdmin && !appDetailMode);
-
- final MenuItem metered = menu.findItem(R.id.data_usage_menu_metered);
- if (hasReadyMobileRadio(context) || hasWifiRadio(context)) {
- metered.setVisible(!appDetailMode);
- } else {
- metered.setVisible(false);
- }
-
- // TODO: show when multiple sims available
- mMenuSimCards = menu.findItem(R.id.data_usage_menu_sim_cards);
- mMenuSimCards.setVisible(false);
-
- mMenuCellularNetworks = menu.findItem(R.id.data_usage_menu_cellular_networks);
- mMenuCellularNetworks.setVisible(hasReadyMobileRadio(context)
- && !appDetailMode && isAdmin);
-
- final MenuItem help = menu.findItem(R.id.data_usage_menu_help);
- String helpUrl;
- if (!TextUtils.isEmpty(helpUrl = getResources().getString(R.string.help_url_data_usage))) {
- HelpUtils.prepareHelpMenuItem(getActivity(), help, helpUrl, getClass().getName());
- } else {
- help.setVisible(false);
- }
-
- updateMenuTitles();
- }
-
- private void updateMenuTitles() {
- if (mPolicyManager.getRestrictBackground()) {
- mMenuRestrictBackground.setTitle(R.string.data_usage_menu_allow_background);
- } else {
- mMenuRestrictBackground.setTitle(R.string.data_usage_menu_restrict_background);
- }
-
- if (mShowWifi) {
- mMenuShowWifi.setTitle(R.string.data_usage_menu_hide_wifi);
- } else {
- mMenuShowWifi.setTitle(R.string.data_usage_menu_show_wifi);
- }
-
- if (mShowEthernet) {
- mMenuShowEthernet.setTitle(R.string.data_usage_menu_hide_ethernet);
- } else {
- mMenuShowEthernet.setTitle(R.string.data_usage_menu_show_ethernet);
- }
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case R.id.data_usage_menu_restrict_background: {
- final boolean restrictBackground = !mPolicyManager.getRestrictBackground();
- if (restrictBackground) {
- ConfirmRestrictFragment.show(this);
- } else {
- // no confirmation to drop restriction
- setRestrictBackground(false);
- }
- return true;
- }
- case R.id.data_usage_menu_show_wifi: {
- mShowWifi = !mShowWifi;
- mPrefs.edit().putBoolean(PREF_SHOW_WIFI, mShowWifi).apply();
- updateMenuTitles();
- updateTabs();
- return true;
- }
- case R.id.data_usage_menu_show_ethernet: {
- mShowEthernet = !mShowEthernet;
- mPrefs.edit().putBoolean(PREF_SHOW_ETHERNET, mShowEthernet).apply();
- updateMenuTitles();
- updateTabs();
- return true;
- }
- case R.id.data_usage_menu_sim_cards: {
- // TODO: hook up to sim cards
- return true;
- }
- case R.id.data_usage_menu_cellular_networks: {
- final Intent intent = new Intent(Intent.ACTION_MAIN);
- intent.setComponent(new ComponentName("com.android.phone",
- "com.android.phone.MobileNetworkSettings"));
- startActivity(intent);
- return true;
- }
- case R.id.data_usage_menu_metered: {
- final SettingsActivity sa = (SettingsActivity) getActivity();
- sa.startPreferencePanel(DataUsageMeteredSettings.class.getCanonicalName(), null,
- R.string.data_usage_metered_title, null, this, 0);
- return true;
- }
- }
- return false;
- }
-
- @Override
- public void onDestroy() {
- mDataEnabledView = null;
- mDisableAtLimitView = null;
-
- mUidDetailProvider.clearCache();
- mUidDetailProvider = null;
-
- TrafficStats.closeQuietly(mStatsSession);
-
- super.onDestroy();
- }
-
- /**
- * Build and assign {@link LayoutTransition} to various containers. Should
- * only be assigned after initial layout is complete.
- */
- private void ensureLayoutTransitions() {
- if (mShowAppImmediatePkg != null) {
- // If we are skipping right to showing an app, we don't care about transitions.
- return;
- }
- // skip when already setup
- if (mChart.getLayoutTransition() != null) return;
-
- mTabsContainer.setLayoutTransition(buildLayoutTransition());
- mHeader.setLayoutTransition(buildLayoutTransition());
- mNetworkSwitchesContainer.setLayoutTransition(buildLayoutTransition());
-
- final LayoutTransition chartTransition = buildLayoutTransition();
- chartTransition.disableTransitionType(LayoutTransition.APPEARING);
- chartTransition.disableTransitionType(LayoutTransition.DISAPPEARING);
- mChart.setLayoutTransition(chartTransition);
- }
-
- private static LayoutTransition buildLayoutTransition() {
- final LayoutTransition transition = new LayoutTransition();
- if (TEST_ANIM) {
- transition.setDuration(1500);
- }
- transition.setAnimateParentHierarchy(false);
- return transition;
- }
-
- /**
- * Rebuild all tabs based on {@link NetworkPolicyEditor} and
- * {@link #mShowWifi}, hiding the tabs entirely when applicable. Selects
- * first tab, and kicks off a full rebind of body contents.
- */
- private void updateTabs() {
- final Context context = getActivity();
- mTabHost.clearAllTabs();
-
- int simCount = mTelephonyManager.getSimCount();
-
- List<SubscriptionInfo> sirs = mSubscriptionManager.getActiveSubscriptionInfoList();
- if (sirs != null) {
- for (SubscriptionInfo sir : sirs) {
- addMobileTab(context, sir, (simCount > 1));
- }
- }
-
- if (mShowWifi && hasWifiRadio(context)) {
- mTabHost.addTab(buildTabSpec(TAB_WIFI, R.string.data_usage_tab_wifi));
- }
-
- if (mShowEthernet && hasEthernet(context)) {
- mTabHost.addTab(buildTabSpec(TAB_ETHERNET, R.string.data_usage_tab_ethernet));
- }
-
- final boolean noTabs = mTabWidget.getTabCount() == 0;
- final boolean multipleTabs = mTabWidget.getTabCount() > 1;
- mTabWidget.setVisibility(multipleTabs ? View.VISIBLE : View.GONE);
- if (mIntentTab != null) {
- if (Objects.equal(mIntentTab, mTabHost.getCurrentTabTag())) {
- // already hit updateBody() when added; ignore
- updateBody();
- } else {
- mTabHost.setCurrentTabByTag(mIntentTab);
- }
- mIntentTab = null;
- } else if (noTabs) {
- // no usable tabs, so hide body
- updateBody();
- } else {
- // already hit updateBody() when added; ignore
- }
- }
-
- /**
- * Factory that provide empty {@link View} to make {@link TabHost} happy.
- */
- private TabContentFactory mEmptyTabContent = new TabContentFactory() {
- @Override
- public View createTabContent(String tag) {
- return new View(mTabHost.getContext());
- }
- };
-
- /**
- * Build {@link TabSpec} with thin indicator, and empty content.
- */
- private TabSpec buildTabSpec(String tag, int titleRes) {
- return mTabHost.newTabSpec(tag).setIndicator(getText(titleRes)).setContent(
- mEmptyTabContent);
- }
-
- /**
- * Build {@link TabSpec} with thin indicator, and empty content.
- */
- private TabSpec buildTabSpec(String tag, CharSequence title) {
- return mTabHost.newTabSpec(tag).setIndicator(title).setContent(
- mEmptyTabContent);
- }
-
-
- private OnTabChangeListener mTabListener = new OnTabChangeListener() {
- @Override
- public void onTabChanged(String tabId) {
- // user changed tab; update body
- updateBody();
- }
- };
-
- /**
- * Update body content based on current tab. Loads
- * {@link NetworkStatsHistory} and {@link NetworkPolicy} from system, and
- * binds them to visible controls.
- */
- private void updateBody() {
- mBinding = true;
- if (!isAdded()) return;
-
- final Context context = getActivity();
- final String currentTab = mTabHost.getCurrentTabTag();
- final boolean isAdmin = mUserManager.isAdminUser();
-
- if (currentTab == null) {
- Log.w(TAG, "no tab selected; hiding body");
- mListView.setVisibility(View.GONE);
- return;
- } else {
- mListView.setVisibility(View.VISIBLE);
- }
-
- mCurrentTab = currentTab;
-
- if (LOGD) Log.d(TAG, "updateBody() with currentTab=" + currentTab);
-
- mDataEnabledSupported = isAdmin;
- mDisableAtLimitSupported = true;
-
- // TODO: remove mobile tabs when SIM isn't ready probably by
- // TODO: using SubscriptionManager.getActiveSubscriptionInfoList.
- if (LOGD) Log.d(TAG, "updateBody() isMobileTab=" + isMobileTab(currentTab));
-
- if (isMobileTab(currentTab)) {
- if (LOGD) Log.d(TAG, "updateBody() mobile tab");
- setPreferenceTitle(mDataEnabledView, R.string.data_usage_enable_mobile);
- setPreferenceTitle(mDisableAtLimitView, R.string.data_usage_disable_mobile_limit);
- mDataEnabledSupported = isMobileDataAvailable(getSubId(currentTab));
-
- // Match mobile traffic for this subscriber, but normalize it to
- // catch any other merged subscribers.
- mTemplate = buildTemplateMobileAll(
- getActiveSubscriberId(context, getSubId(currentTab)));
- mTemplate = NetworkTemplate.normalize(mTemplate,
- mTelephonyManager.getMergedSubscriberIds());
-
- } else if (TAB_3G.equals(currentTab)) {
- if (LOGD) Log.d(TAG, "updateBody() 3g tab");
- setPreferenceTitle(mDataEnabledView, R.string.data_usage_enable_3g);
- setPreferenceTitle(mDisableAtLimitView, R.string.data_usage_disable_3g_limit);
- // TODO: bind mDataEnabled to 3G radio state
- mTemplate = buildTemplateMobile3gLower(getActiveSubscriberId(context));
-
- } else if (TAB_4G.equals(currentTab)) {
- if (LOGD) Log.d(TAG, "updateBody() 4g tab");
- setPreferenceTitle(mDataEnabledView, R.string.data_usage_enable_4g);
- setPreferenceTitle(mDisableAtLimitView, R.string.data_usage_disable_4g_limit);
- // TODO: bind mDataEnabled to 4G radio state
- mTemplate = buildTemplateMobile4g(getActiveSubscriberId(context));
-
- } else if (TAB_WIFI.equals(currentTab)) {
- // wifi doesn't have any controls
- if (LOGD) Log.d(TAG, "updateBody() wifi tab");
- mDataEnabledSupported = false;
- mDisableAtLimitSupported = false;
- mTemplate = buildTemplateWifiWildcard();
-
- } else if (TAB_ETHERNET.equals(currentTab)) {
- // ethernet doesn't have any controls
- if (LOGD) Log.d(TAG, "updateBody() ethernet tab");
- mDataEnabledSupported = false;
- mDisableAtLimitSupported = false;
- mTemplate = buildTemplateEthernet();
-
- } else {
- if (LOGD) Log.d(TAG, "updateBody() unknown tab");
- throw new IllegalStateException("unknown tab: " + currentTab);
- }
-
- mPolicyEditor.read();
- final NetworkPolicy policy = mPolicyEditor.getPolicy(mTemplate);
- if (policy != null) {
- final long currentTime = System.currentTimeMillis();
- final long start = computeLastCycleBoundary(currentTime, policy);
- final long end = currentTime;
- long totalBytes = 0;
-
- try {
- totalBytes = mStatsService.getNetworkTotalBytes(policy.template, start, end);
- } catch (RuntimeException e) {
- } catch (RemoteException e) {
- }
-
- if (policy.isOverLimit(totalBytes) && policy.lastLimitSnooze < start) {
- setPreferenceSummary(mDataEnabledView,
- getString(R.string.data_usage_cellular_data_summary));
- } else {
- final TextView summary = (TextView) mDataEnabledView
- .findViewById(android.R.id.summary);
- summary.setVisibility(View.GONE);
- }
- }
-
- // kick off loader for network history
- // TODO: consider chaining two loaders together instead of reloading
- // network history when showing app detail.
- getLoaderManager().restartLoader(LOADER_CHART_DATA,
- ChartDataLoader.buildArgs(mTemplate, mCurrentApp), mChartDataCallbacks);
-
- // detail mode can change visible menus, invalidate
- getActivity().invalidateOptionsMenu();
-
- mBinding = false;
-
- int seriesColor = context.getColor(R.color.sim_noitification);
- if (mCurrentTab != null && mCurrentTab.length() > TAB_MOBILE.length() ){
- final int slotId = Integer.parseInt(mCurrentTab.substring(TAB_MOBILE.length(),
- mCurrentTab.length()));
- final SubscriptionInfo sir = mSubscriptionManager
- .getActiveSubscriptionInfoForSimSlotIndex(slotId);
-
- if (sir != null) {
- seriesColor = sir.getIconTint();
- }
- }
-
- final int secondaryColor = Color.argb(127, Color.red(seriesColor), Color.green(seriesColor),
- Color.blue(seriesColor));
- mSeries.setChartColor(Color.BLACK, seriesColor, secondaryColor);
- mDetailedSeries.setChartColor(Color.BLACK, seriesColor, secondaryColor);
- }
-
- private boolean isAppDetailMode() {
- return mCurrentApp != null;
- }
-
- /**
- * Update UID details panels to match {@link #mCurrentApp}, showing or
- * hiding them depending on {@link #isAppDetailMode()}.
- */
- private void updateAppDetail() {
- final Context context = getActivity();
- final PackageManager pm = context.getPackageManager();
- final LayoutInflater inflater = getActivity().getLayoutInflater();
-
- if (isAppDetailMode()) {
- mAppDetail.setVisibility(View.VISIBLE);
- mCycleAdapter.setChangeVisible(false);
- mChart.setVisibility(View.GONE);
- } else {
- mAppDetail.setVisibility(View.GONE);
- mCycleAdapter.setChangeVisible(true);
- mChart.setVisibility(View.VISIBLE);
-
- // hide detail stats when not in detail mode
- mChart.bindDetailNetworkStats(null);
- return;
- }
-
- // remove warning/limit sweeps while in detail mode
- mChart.bindNetworkPolicy(null);
-
- // show icon and all labels appearing under this app
- final int uid = mCurrentApp.key;
- final UidDetail detail = mUidDetailProvider.getUidDetail(uid, true);
- mAppIcon.setImageDrawable(detail.icon);
-
- mAppTitles.removeAllViews();
-
- View title = null;
- if (detail.detailLabels != null) {
- final int n = detail.detailLabels.length;
- for (int i = 0; i < n; ++i) {
- CharSequence label = detail.detailLabels[i];
- CharSequence contentDescription = detail.detailContentDescriptions[i];
- title = inflater.inflate(R.layout.data_usage_app_title, mAppTitles, false);
- TextView appTitle = (TextView) title.findViewById(R.id.app_title);
- appTitle.setText(label);
- appTitle.setContentDescription(contentDescription);
- mAppTitles.addView(title);
- }
- } else {
- title = inflater.inflate(R.layout.data_usage_app_title, mAppTitles, false);
- TextView appTitle = (TextView) title.findViewById(R.id.app_title);
- appTitle.setText(detail.label);
- appTitle.setContentDescription(detail.contentDescription);
- mAppTitles.addView(title);
- }
-
- // Remember last slot for summary
- if (title != null) {
- mAppTotal = (TextView) title.findViewById(R.id.app_summary);
- } else {
- mAppTotal = null;
- }
-
- // enable settings button when package provides it
- final String[] packageNames = pm.getPackagesForUid(uid);
- if (packageNames != null && packageNames.length > 0) {
- mAppSettingsIntent = new Intent(Intent.ACTION_MANAGE_NETWORK_USAGE);
- mAppSettingsIntent.addCategory(Intent.CATEGORY_DEFAULT);
-
- // Search for match across all packages
- boolean matchFound = false;
- for (String packageName : packageNames) {
- mAppSettingsIntent.setPackage(packageName);
- if (pm.resolveActivity(mAppSettingsIntent, 0) != null) {
- matchFound = true;
- break;
- }
- }
-
- mAppSettings.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- if (!isAdded()) {
- return;
- }
-
- // TODO: target towards entire UID instead of just first package
- getActivity().startActivityAsUser(mAppSettingsIntent,
- new UserHandle(UserHandle.getUserId(uid)));
- }
- });
- mAppSettings.setEnabled(matchFound);
- mAppSettings.setVisibility(View.VISIBLE);
-
- } else {
- mAppSettingsIntent = null;
- mAppSettings.setOnClickListener(null);
- mAppSettings.setVisibility(View.GONE);
- }
-
- updateDetailData();
-
- if (UserHandle.isApp(uid) && !mPolicyManager.getRestrictBackground()
- && isBandwidthControlEnabled() && hasReadyMobileRadio(context)) {
- setPreferenceTitle(mAppRestrictView, R.string.data_usage_app_restrict_background);
- setPreferenceSummary(mAppRestrictView,
- getString(R.string.data_usage_app_restrict_background_summary));
-
- mAppRestrictView.setVisibility(View.VISIBLE);
- mAppRestrict.setChecked(getAppRestrictBackground());
-
- } else {
- mAppRestrictView.setVisibility(View.GONE);
- }
- }
-
- private void setPolicyWarningBytes(long warningBytes) {
- if (LOGD) Log.d(TAG, "setPolicyWarningBytes()");
- mPolicyEditor.setPolicyWarningBytes(mTemplate, warningBytes);
- updatePolicy(false);
- }
-
- private void setPolicyLimitBytes(long limitBytes) {
- if (LOGD) Log.d(TAG, "setPolicyLimitBytes()");
- mPolicyEditor.setPolicyLimitBytes(mTemplate, limitBytes);
- updatePolicy(false);
- }
-
- private boolean isMobileDataEnabled(int subId) {
- if (LOGD) Log.d(TAG, "isMobileDataEnabled:+ subId=" + subId);
- boolean isEnable = false;
- if (mMobileDataEnabled.get(String.valueOf(subId)) != null) {
- //TODO: deprecate and remove this once enabled flag is on policy
- //Multiple Subscriptions, the value need to be reseted
- isEnable = mMobileDataEnabled.get(String.valueOf(subId)).booleanValue();
- if (LOGD) {
- Log.d(TAG, "isMobileDataEnabled: != null, subId=" + subId
- + " isEnable=" + isEnable);
- }
- mMobileDataEnabled.put(String.valueOf(subId), null);
- } else {
- // SUB SELECT
- isEnable = mTelephonyManager.getDataEnabled(subId);
- if (LOGD) {
- Log.d(TAG, "isMobileDataEnabled: == null, subId=" + subId
- + " isEnable=" + isEnable);
- }
- }
- return isEnable;
- }
-
- private void setMobileDataEnabled(int subId, boolean enabled) {
- if (LOGD) Log.d(TAG, "setMobileDataEnabled()");
- mTelephonyManager.setDataEnabled(subId, enabled);
- mMobileDataEnabled.put(String.valueOf(subId), enabled);
- updatePolicy(false);
- }
-
- private boolean isNetworkPolicyModifiable(NetworkPolicy policy) {
- return policy != null && isBandwidthControlEnabled() && mDataEnabled.isChecked()
- && mUserManager.isAdminUser();
- }
-
- private boolean isBandwidthControlEnabled() {
- try {
- return mNetworkService.isBandwidthControlEnabled();
- } catch (RemoteException e) {
- Log.w(TAG, "problem talking with INetworkManagementService: " + e);
- return false;
- }
- }
-
- public void setRestrictBackground(boolean restrictBackground) {
- mPolicyManager.setRestrictBackground(restrictBackground);
- updateMenuTitles();
- ConditionManager.get(getContext()).getCondition(BackgroundDataCondition.class)
- .refreshState();
- }
-
- private boolean getAppRestrictBackground() {
- final int uid = mCurrentApp.key;
- final int uidPolicy = mPolicyManager.getUidPolicy(uid);
- return (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0;
- }
-
- private void setAppRestrictBackground(boolean restrictBackground) {
- if (LOGD) Log.d(TAG, "setAppRestrictBackground()");
- final int uid = mCurrentApp.key;
- mPolicyManager.setUidPolicy(
- uid, restrictBackground ? POLICY_REJECT_METERED_BACKGROUND : POLICY_NONE);
- mAppRestrict.setChecked(restrictBackground);
- }
-
- /**
- * Update chart sweeps and cycle list to reflect {@link NetworkPolicy} for
- * current {@link #mTemplate}.
- */
- private void updatePolicy(boolean refreshCycle) {
- boolean dataEnabledVisible = mDataEnabledSupported;
- boolean disableAtLimitVisible = mDisableAtLimitSupported;
-
- if (isAppDetailMode()) {
- dataEnabledVisible = false;
- disableAtLimitVisible = false;
- }
-
- // TODO: move enabled state directly into policy
- if (isMobileTab(mCurrentTab)) {
- mBinding = true;
- mDataEnabled.setChecked(isMobileDataEnabled(getSubId(mCurrentTab)));
- mBinding = false;
- }
-
- final NetworkPolicy policy = mPolicyEditor.getPolicy(mTemplate);
- //SUB SELECT
- if (isNetworkPolicyModifiable(policy) && isMobileDataAvailable(getSubId(mCurrentTab))) {
- mDisableAtLimit.setChecked(policy != null && policy.limitBytes != LIMIT_DISABLED);
- if (!isAppDetailMode()) {
- mChart.bindNetworkPolicy(policy);
- }
-
- } else {
- // controls are disabled; don't bind warning/limit sweeps
- disableAtLimitVisible = false;
- mChart.bindNetworkPolicy(null);
- }
-
- mDataEnabledView.setVisibility(dataEnabledVisible ? View.VISIBLE : View.GONE);
- mDisableAtLimitView.setVisibility(disableAtLimitVisible ? View.VISIBLE : View.GONE);
-
- if (refreshCycle) {
- // generate cycle list based on policy and available history
- updateCycleList(policy);
- }
- }
-
- /**
- * Rebuild {@link #mCycleAdapter} based on {@link NetworkPolicy#cycleDay}
- * and available {@link NetworkStatsHistory} data. Always selects the newest
- * item, updating the inspection range on {@link #mChart}.
- */
- private void updateCycleList(NetworkPolicy policy) {
- // stash away currently selected cycle to try restoring below
- final CycleItem previousItem = (CycleItem) mCycleSpinner.getSelectedItem();
- mCycleAdapter.clear();
-
- final Context context = mCycleSpinner.getContext();
- NetworkStatsHistory.Entry entry = null;
-
- long historyStart = Long.MAX_VALUE;
- long historyEnd = Long.MIN_VALUE;
- if (mChartData != null) {
- historyStart = mChartData.network.getStart();
- historyEnd = mChartData.network.getEnd();
- }
-
- final long now = System.currentTimeMillis();
- if (historyStart == Long.MAX_VALUE) historyStart = now;
- if (historyEnd == Long.MIN_VALUE) historyEnd = now + 1;
-
- boolean hasCycles = false;
- if (policy != null) {
- // find the next cycle boundary
- long cycleEnd = computeNextCycleBoundary(historyEnd, policy);
-
- // walk backwards, generating all valid cycle ranges
- while (cycleEnd > historyStart) {
- final long cycleStart = computeLastCycleBoundary(cycleEnd, policy);
- Log.d(TAG, "generating cs=" + cycleStart + " to ce=" + cycleEnd + " waiting for hs="
- + historyStart);
-
- final boolean includeCycle;
- if (mChartData != null) {
- entry = mChartData.network.getValues(cycleStart, cycleEnd, entry);
- includeCycle = (entry.rxBytes + entry.txBytes) > 0;
- } else {
- includeCycle = true;
- }
-
- if (includeCycle) {
- mCycleAdapter.add(new CycleItem(context, cycleStart, cycleEnd));
- hasCycles = true;
- }
- cycleEnd = cycleStart;
- }
-
- // one last cycle entry to modify policy cycle day
- mCycleAdapter.setChangePossible(isNetworkPolicyModifiable(policy));
- }
-
- if (!hasCycles) {
- // no policy defined cycles; show entry for each four-week period
- long cycleEnd = historyEnd;
- while (cycleEnd > historyStart) {
- final long cycleStart = cycleEnd - (DateUtils.WEEK_IN_MILLIS * 4);
-
- final boolean includeCycle;
- if (mChartData != null) {
- entry = mChartData.network.getValues(cycleStart, cycleEnd, entry);
- includeCycle = (entry.rxBytes + entry.txBytes) > 0;
- } else {
- includeCycle = true;
- }
-
- if (includeCycle) {
- mCycleAdapter.add(new CycleItem(context, cycleStart, cycleEnd));
- }
- cycleEnd = cycleStart;
- }
-
- mCycleAdapter.setChangePossible(false);
- }
-
- // force pick the current cycle (first item)
- if (mCycleAdapter.getCount() > 0) {
- final int position = mCycleAdapter.findNearestPosition(previousItem);
- mCycleSpinner.setSelection(position);
-
- // only force-update cycle when changed; skipping preserves any
- // user-defined inspection region.
- final CycleItem selectedItem = mCycleAdapter.getItem(position);
- if (!Objects.equal(selectedItem, previousItem)) {
- mCycleListener.onItemSelected(mCycleSpinner, null, position, 0);
- } else {
- // but still kick off loader for detailed list
- updateDetailData();
- }
- } else {
- updateDetailData();
- }
- }
-
- private void disableDataForOtherSubscriptions(SubscriptionInfo currentSir) {
- if (mSubInfoList != null) {
- for (SubscriptionInfo subInfo : mSubInfoList) {
- if (subInfo.getSubscriptionId() != currentSir.getSubscriptionId()) {
- setMobileDataEnabled(subInfo.getSubscriptionId(), false);
- }
- }
- }
- }
-
- private View.OnClickListener mDataEnabledListener = new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- if (mBinding) return;
-
- final boolean enabled = !mDataEnabled.isChecked();
- final String currentTab = mCurrentTab;
- if (isMobileTab(currentTab)) {
- MetricsLogger.action(getContext(), MetricsLogger.ACTION_CELL_DATA_TOGGLE, enabled);
- if (enabled) {
- // If we are showing the Sim Card tile then we are a Multi-Sim device.
- if (Utils.showSimCardTile(getActivity())) {
- handleMultiSimDataDialog();
- } else {
- setMobileDataEnabled(getSubId(currentTab), true);
- }
- } else {
- // disabling data; show confirmation dialog which eventually
- // calls setMobileDataEnabled() once user confirms.
- ConfirmDataDisableFragment.show(DataUsageSummary.this, getSubId(mCurrentTab));
- }
- }
-
- updatePolicy(false);
- }
- };
-
- private void handleMultiSimDataDialog() {
- final Context context = getActivity();
- final SubscriptionInfo currentSir = getCurrentTabSubInfo(context);
-
- //If sim has not loaded after toggling data switch, return.
- if (currentSir == null) {
- return;
- }
-
- final SubscriptionInfo nextSir = mSubscriptionManager.getDefaultDataSubscriptionInfo();
-
- // If the device is single SIM or is enabling data on the active data SIM then forgo
- // the pop-up.
- if (!Utils.showSimCardTile(context) ||
- (nextSir != null && currentSir != null &&
- currentSir.getSubscriptionId() == nextSir.getSubscriptionId())) {
- setMobileDataEnabled(currentSir.getSubscriptionId(), true);
- if (nextSir != null && currentSir != null &&
- currentSir.getSubscriptionId() == nextSir.getSubscriptionId()) {
- disableDataForOtherSubscriptions(currentSir);
- }
- updateBody();
- return;
- }
-
- final String previousName = (nextSir == null)
- ? context.getResources().getString(R.string.sim_selection_required_pref)
- : nextSir.getDisplayName().toString();
-
- AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
-
- builder.setTitle(R.string.sim_change_data_title);
- builder.setMessage(getActivity().getResources().getString(R.string.sim_change_data_message,
- currentSir.getDisplayName(), previousName));
-
- builder.setPositiveButton(R.string.okay, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int id) {
- mSubscriptionManager.setDefaultDataSubId(currentSir.getSubscriptionId());
- setMobileDataEnabled(currentSir.getSubscriptionId(), true);
- disableDataForOtherSubscriptions(currentSir);
- updateBody();
- }
- });
- builder.setNegativeButton(R.string.cancel, null);
-
- builder.create().show();
- }
-
- private View.OnClickListener mDisableAtLimitListener = new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- final boolean disableAtLimit = !mDisableAtLimit.isChecked();
- if (disableAtLimit) {
- // enabling limit; show confirmation dialog which eventually
- // calls setPolicyLimitBytes() once user confirms.
- ConfirmLimitFragment.show(DataUsageSummary.this);
- } else {
- setPolicyLimitBytes(LIMIT_DISABLED);
- }
- }
- };
-
- private View.OnClickListener mAppRestrictListener = new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- final boolean restrictBackground = !mAppRestrict.isChecked();
-
- if (restrictBackground) {
- // enabling restriction; show confirmation dialog which
- // eventually calls setRestrictBackground() once user
- // confirms.
- ConfirmAppRestrictFragment.show(DataUsageSummary.this);
- } else {
- setAppRestrictBackground(false);
- }
- }
- };
-
- private OnItemClickListener mListListener = new OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- final Context context = view.getContext();
- final AppItem app = (AppItem) parent.getItemAtPosition(position);
-
- // TODO: sigh, remove this hack once we understand 6450986
- if (mUidDetailProvider == null || app == null) return;
-
- final UidDetail detail = mUidDetailProvider.getUidDetail(app.key, true);
- AppDetailsFragment.show(DataUsageSummary.this, app, detail.label);
- }
- };
-
- private OnItemSelectedListener mCycleListener = new OnItemSelectedListener() {
- @Override
- public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
- final CycleItem cycle = (CycleItem) parent.getItemAtPosition(position);
- if (cycle instanceof CycleChangeItem) {
- // show cycle editor; will eventually call setPolicyCycleDay()
- // when user finishes editing.
- CycleEditorFragment.show(DataUsageSummary.this);
-
- // reset spinner to something other than "change cycle..."
- mCycleSpinner.setSelection(0);
-
- } else {
- if (LOGD) {
- Log.d(TAG, "showing cycle " + cycle + ", start=" + cycle.start + ", end="
- + cycle.end + "]");
- }
-
- // update chart to show selected cycle, and update detail data
- // to match updated sweep bounds.
- mChart.setVisibleRange(cycle.start, cycle.end);
-
- updateDetailData();
- }
- }
-
- @Override
- public void onNothingSelected(AdapterView<?> parent) {
- // ignored
- }
- };
-
- /**
- * Update details based on {@link #mChart} inspection range depending on
- * current mode. In network mode, updates {@link #mAdapter} with sorted list
- * of applications data usage, and when {@link #isAppDetailMode()} update
- * app details.
- */
- private void updateDetailData() {
- if (LOGD) Log.d(TAG, "updateDetailData()");
-
- final long start = mChart.getInspectStart();
- final long end = mChart.getInspectEnd();
- final long now = System.currentTimeMillis();
-
- final Context context = getActivity();
-
- NetworkStatsHistory.Entry entry = null;
- if (isAppDetailMode() && mChartData != null && mChartData.detail != null) {
- // bind foreground/background to piechart and labels
- entry = mChartData.detailDefault.getValues(start, end, now, entry);
- final long defaultBytes = entry.rxBytes + entry.txBytes;
- entry = mChartData.detailForeground.getValues(start, end, now, entry);
- final long foregroundBytes = entry.rxBytes + entry.txBytes;
- final long totalBytes = defaultBytes + foregroundBytes;
-
- if (mAppTotal != null) {
- mAppTotal.setText(Formatter.formatFileSize(context, totalBytes));
- }
- mAppBackground.setText(Formatter.formatFileSize(context, defaultBytes));
- mAppForeground.setText(Formatter.formatFileSize(context, foregroundBytes));
-
- // and finally leave with summary data for label below
- entry = mChartData.detail.getValues(start, end, now, null);
-
- getLoaderManager().destroyLoader(LOADER_SUMMARY);
-
- mCycleSummary.setVisibility(View.GONE);
-
- } else {
- if (mChartData != null) {
- entry = mChartData.network.getValues(start, end, now, null);
- }
-
- mCycleSummary.setVisibility(View.VISIBLE);
-
- // kick off loader for detailed stats
- getLoaderManager().restartLoader(LOADER_SUMMARY,
- SummaryForAllUidLoader.buildArgs(mTemplate, start, end), mSummaryCallbacks);
- }
-
- final long totalBytes = entry != null ? entry.rxBytes + entry.txBytes : 0;
- final String totalPhrase = Formatter.formatFileSize(context, totalBytes);
- mCycleSummary.setText(totalPhrase);
-
- if (isMobileTab(mCurrentTab) || TAB_3G.equals(mCurrentTab)
- || TAB_4G.equals(mCurrentTab)) {
- if (isAppDetailMode()) {
- mDisclaimer.setVisibility(View.GONE);
- } else {
- mDisclaimer.setVisibility(View.VISIBLE);
- }
- } else {
- mDisclaimer.setVisibility(View.GONE);
- }
-
- // initial layout is finished above, ensure we have transitions
- ensureLayoutTransitions();
- }
-
- private final LoaderCallbacks<ChartData> mChartDataCallbacks = new LoaderCallbacks<
- ChartData>() {
- @Override
- public Loader<ChartData> onCreateLoader(int id, Bundle args) {
- return new ChartDataLoader(getActivity(), mStatsSession, args);
- }
-
- @Override
- public void onLoadFinished(Loader<ChartData> loader, ChartData data) {
- mChartData = data;
- mChart.bindNetworkStats(mChartData.network);
- mChart.bindDetailNetworkStats(mChartData.detail);
-
- // calcuate policy cycles based on available data
- updatePolicy(true);
- updateAppDetail();
-
- // force scroll to top of body when showing detail
- if (mChartData.detail != null) {
- mListView.smoothScrollToPosition(0);
- }
- }
-
- @Override
- public void onLoaderReset(Loader<ChartData> loader) {
- mChartData = null;
- mChart.bindNetworkStats(null);
- mChart.bindDetailNetworkStats(null);
- }
- };
-
- private final LoaderCallbacks<NetworkStats> mSummaryCallbacks = new LoaderCallbacks<
- NetworkStats>() {
- @Override
- public Loader<NetworkStats> onCreateLoader(int id, Bundle args) {
- return new SummaryForAllUidLoader(getActivity(), mStatsSession, args);
- }
-
- @Override
- public void onLoadFinished(Loader<NetworkStats> loader, NetworkStats data) {
- final int[] restrictedUids = mPolicyManager.getUidsWithPolicy(
- POLICY_REJECT_METERED_BACKGROUND);
- mAdapter.bindStats(data, restrictedUids);
- updateEmptyVisible();
- }
-
- @Override
- public void onLoaderReset(Loader<NetworkStats> loader) {
- mAdapter.bindStats(null, new int[0]);
- updateEmptyVisible();
- }
-
- private void updateEmptyVisible() {
- final boolean isEmpty = mAdapter.isEmpty() && !isAppDetailMode();
- mEmpty.setVisibility(isEmpty ? View.VISIBLE : View.GONE);
- mStupidPadding.setVisibility(isEmpty ? View.VISIBLE : View.GONE);
- }
- };
-
- private static String getActiveSubscriberId(Context context) {
- final TelephonyManager tele = TelephonyManager.from(context);
- final String actualSubscriberId = tele.getSubscriberId();
- String retVal = SystemProperties.get(TEST_SUBSCRIBER_PROP, actualSubscriberId);
- if (LOGD) Log.d(TAG, "getActiveSubscriberId=" + retVal + " actualSubscriberId=" + actualSubscriberId);
- return retVal;
- }
-
- private static String getActiveSubscriberId(Context context, int subId) {
- final TelephonyManager tele = TelephonyManager.from(context);
- String retVal = tele.getSubscriberId(subId);
- if (LOGD) Log.d(TAG, "getActiveSubscriberId=" + retVal + " subId=" + subId);
- return retVal;
- }
-
- private DataUsageChartListener mChartListener = new DataUsageChartListener() {
- @Override
- public void onWarningChanged() {
- setPolicyWarningBytes(mChart.getWarningBytes());
- }
-
- @Override
- public void onLimitChanged() {
- setPolicyLimitBytes(mChart.getLimitBytes());
- updateBody();
- }
-
- @Override
- public void requestWarningEdit() {
- WarningEditorFragment.show(DataUsageSummary.this);
- }
-
- @Override
- public void requestLimitEdit() {
- LimitEditorFragment.show(DataUsageSummary.this);
- }
- };
-
- /**
- * List item that reflects a specific data usage cycle.
- */
- public static class CycleItem implements Comparable<CycleItem> {
- public CharSequence label;
- public long start;
- public long end;
-
- CycleItem(CharSequence label) {
- this.label = label;
- }
-
- public CycleItem(Context context, long start, long end) {
- this.label = formatDateRange(context, start, end);
- this.start = start;
- this.end = end;
- }
-
- @Override
- public String toString() {
- return label.toString();
- }
-
- @Override
- public boolean equals(Object o) {
- if (o instanceof CycleItem) {
- final CycleItem another = (CycleItem) o;
- return start == another.start && end == another.end;
- }
- return false;
- }
-
- @Override
- public int compareTo(CycleItem another) {
- return Long.compare(start, another.start);
- }
- }
-
- private static final StringBuilder sBuilder = new StringBuilder(50);
- private static final java.util.Formatter sFormatter = new java.util.Formatter(
- sBuilder, Locale.getDefault());
-
- public static String formatDateRange(Context context, long start, long end) {
- final int flags = FORMAT_SHOW_DATE | FORMAT_ABBREV_MONTH;
-
- synchronized (sBuilder) {
- sBuilder.setLength(0);
- return DateUtils.formatDateRange(context, sFormatter, start, end, flags, null)
- .toString();
- }
- }
-
- /**
- * Special-case data usage cycle that triggers dialog to change
- * {@link NetworkPolicy#cycleDay}.
- */
- public static class CycleChangeItem extends CycleItem {
- public CycleChangeItem(Context context) {
- super(context.getString(R.string.data_usage_change_cycle));
- }
- }
-
- public static class CycleAdapter extends ArrayAdapter<CycleItem> {
- private boolean mChangePossible = false;
- private boolean mChangeVisible = false;
-
- private final CycleChangeItem mChangeItem;
-
- public CycleAdapter(Context context) {
- super(context, R.layout.data_usage_cycle_item);
- setDropDownViewResource(R.layout.data_usage_cycle_item_dropdown);
- mChangeItem = new CycleChangeItem(context);
- }
-
- public void setChangePossible(boolean possible) {
- mChangePossible = possible;
- updateChange();
- }
-
- public void setChangeVisible(boolean visible) {
- mChangeVisible = visible;
- updateChange();
- }
-
- private void updateChange() {
- remove(mChangeItem);
- if (mChangePossible && mChangeVisible) {
- add(mChangeItem);
- }
- }
-
- /**
- * Find position of {@link CycleItem} in this adapter which is nearest
- * the given {@link CycleItem}.
- */
- public int findNearestPosition(CycleItem target) {
- if (target != null) {
- final int count = getCount();
- for (int i = count - 1; i >= 0; i--) {
- final CycleItem item = getItem(i);
- if (item instanceof CycleChangeItem) {
- continue;
- } else if (item.compareTo(target) >= 0) {
- return i;
- }
- }
- }
- return 0;
- }
- }
-
- /**
- * Adapter of applications, sorted by total usage descending.
- */
- public static class DataUsageAdapter extends BaseAdapter {
- private final UidDetailProvider mProvider;
- private final int mInsetSide;
- private final UserManager mUm;
-
- private ArrayList<AppItem> mItems = Lists.newArrayList();
- private long mLargest;
-
- public DataUsageAdapter(final UserManager userManager, UidDetailProvider provider, int insetSide) {
- mProvider = checkNotNull(provider);
- mInsetSide = insetSide;
- mUm = userManager;
- }
-
- /**
- * Bind the given {@link NetworkStats}, or {@code null} to clear list.
- */
- public void bindStats(NetworkStats stats, int[] restrictedUids) {
- mItems.clear();
- mLargest = 0;
-
- final int currentUserId = ActivityManager.getCurrentUser();
- final List<UserHandle> profiles = mUm.getUserProfiles();
- final SparseArray<AppItem> knownItems = new SparseArray<AppItem>();
-
- NetworkStats.Entry entry = null;
- final int size = stats != null ? stats.size() : 0;
- for (int i = 0; i < size; i++) {
- entry = stats.getValues(i, entry);
-
- // Decide how to collapse items together
- final int uid = entry.uid;
-
- final int collapseKey;
- final int category;
- final int userId = UserHandle.getUserId(uid);
- if (UserHandle.isApp(uid)) {
- if (profiles.contains(new UserHandle(userId))) {
- if (userId != currentUserId) {
- // Add to a managed user item.
- final int managedKey = UidDetailProvider.buildKeyForUser(userId);
- accumulate(managedKey, knownItems, entry,
- AppItem.CATEGORY_USER);
- }
- // Add to app item.
- collapseKey = uid;
- category = AppItem.CATEGORY_APP;
- } else {
- // If it is a removed user add it to the removed users' key
- final UserInfo info = mUm.getUserInfo(userId);
- if (info == null) {
- collapseKey = UID_REMOVED;
- category = AppItem.CATEGORY_APP;
- } else {
- // Add to other user item.
- collapseKey = UidDetailProvider.buildKeyForUser(userId);
- category = AppItem.CATEGORY_USER;
- }
- }
- } else if (uid == UID_REMOVED || uid == UID_TETHERING) {
- collapseKey = uid;
- category = AppItem.CATEGORY_APP;
- } else {
- collapseKey = android.os.Process.SYSTEM_UID;
- category = AppItem.CATEGORY_APP;
- }
- accumulate(collapseKey, knownItems, entry, category);
- }
-
- final int restrictedUidsMax = restrictedUids.length;
- for (int i = 0; i < restrictedUidsMax; ++i) {
- final int uid = restrictedUids[i];
- // Only splice in restricted state for current user or managed users
- if (!profiles.contains(new UserHandle(UserHandle.getUserId(uid)))) {
- continue;
- }
-
- AppItem item = knownItems.get(uid);
- if (item == null) {
- item = new AppItem(uid);
- item.total = -1;
- mItems.add(item);
- knownItems.put(item.key, item);
- }
- item.restricted = true;
- }
-
- if (!mItems.isEmpty()) {
- final AppItem title = new AppItem();
- title.category = AppItem.CATEGORY_APP_TITLE;
- mItems.add(title);
- }
-
- Collections.sort(mItems);
- notifyDataSetChanged();
- }
-
- /**
- * Accumulate data usage of a network stats entry for the item mapped by the collapse key.
- * Creates the item if needed.
- *
- * @param collapseKey the collapse key used to map the item.
- * @param knownItems collection of known (already existing) items.
- * @param entry the network stats entry to extract data usage from.
- * @param itemCategory the item is categorized on the list view by this category. Must be
- * either AppItem.APP_ITEM_CATEGORY or AppItem.MANAGED_USER_ITEM_CATEGORY
- */
- private void accumulate(int collapseKey, final SparseArray<AppItem> knownItems,
- NetworkStats.Entry entry, int itemCategory) {
- final int uid = entry.uid;
- AppItem item = knownItems.get(collapseKey);
- if (item == null) {
- item = new AppItem(collapseKey);
- item.category = itemCategory;
- mItems.add(item);
- knownItems.put(item.key, item);
- }
- item.addUid(uid);
- item.total += entry.rxBytes + entry.txBytes;
- if (mLargest < item.total) {
- mLargest = item.total;
- }
- }
-
- @Override
- public int getCount() {
- return mItems.size();
- }
-
- @Override
- public Object getItem(int position) {
- return mItems.get(position);
- }
-
- @Override
- public long getItemId(int position) {
- return mItems.get(position).key;
- }
-
- /**
- * See {@link #getItemViewType} for the view types.
- */
- @Override
- public int getViewTypeCount() {
- return 2;
- }
-
- /**
- * Returns 1 for separator items and 0 for anything else.
- */
- @Override
- public int getItemViewType(int position) {
- final AppItem item = mItems.get(position);
- if (item.category == AppItem.CATEGORY_APP_TITLE) {
- return 1;
- } else {
- return 0;
- }
- }
-
- @Override
- public boolean areAllItemsEnabled() {
- return false;
- }
-
- @Override
- public boolean isEnabled(int position) {
- if (position > mItems.size()) {
- throw new ArrayIndexOutOfBoundsException();
- }
- return getItemViewType(position) == 0;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- final AppItem item = mItems.get(position);
- LayoutInflater inflater = LayoutInflater.from(parent.getContext());
- if (getItemViewType(position) == 1) {
- if (convertView == null) {
- convertView = Utils.inflateCategoryHeader(inflater, parent);
- }
-
- final TextView title = (TextView) convertView.findViewById(android.R.id.title);
- title.setText(R.string.data_usage_app);
-
- } else {
- if (convertView == null) {
- convertView = inflater.inflate(R.layout.data_usage_item, parent, false);
- inflater.inflate(R.layout.widget_progress_bar,
- (ViewGroup) convertView.findViewById(android.R.id.widget_frame));
-
- if (mInsetSide > 0) {
- convertView.setPaddingRelative(mInsetSide, 0, mInsetSide, 0);
- }
- }
-
- final Context context = parent.getContext();
-
- final TextView summary = (TextView) convertView.findViewById(android.R.id.summary);
- final ProgressBar progress = (ProgressBar) convertView.findViewById(
- android.R.id.progress);
-
- // kick off async load of app details
- UidDetailTask.bindView(mProvider, item, convertView);
-
- if (item.restricted && item.total <= 0) {
- summary.setText(R.string.data_usage_app_restricted);
- progress.setVisibility(View.GONE);
- } else {
- summary.setText(Formatter.formatFileSize(context, item.total));
- progress.setVisibility(View.VISIBLE);
- }
-
- final int percentTotal = mLargest != 0 ? (int) (item.total * 100 / mLargest) : 0;
- progress.setProgress(percentTotal);
- }
-
- return convertView;
- }
- }
-
- /**
- * Empty {@link Fragment} that controls display of UID details in
- * {@link DataUsageSummary}.
- */
- public static class AppDetailsFragment extends Fragment {
- private static final String EXTRA_APP = "app";
-
- public static void show(DataUsageSummary parent, AppItem app, CharSequence label) {
- show(parent, app, label, true);
- }
-
- public static void show(DataUsageSummary parent, AppItem app, CharSequence label,
- boolean addToBack) {
- if (!parent.isAdded()) return;
-
- final Bundle args = new Bundle();
- args.putParcelable(EXTRA_APP, app);
-
- final AppDetailsFragment fragment = new AppDetailsFragment();
- fragment.setArguments(args);
- fragment.setTargetFragment(parent, 0);
- final FragmentTransaction ft = parent.getFragmentManager().beginTransaction();
- ft.add(fragment, TAG_APP_DETAILS);
- if (addToBack) {
- ft.addToBackStack(TAG_APP_DETAILS);
- }
- ft.setBreadCrumbTitle(
- parent.getResources().getString(R.string.data_usage_app_summary_title));
- ft.commitAllowingStateLoss();
- }
-
- @Override
- public void onStart() {
- super.onStart();
- final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
- target.mCurrentApp = getArguments().getParcelable(EXTRA_APP);
- target.updateBody();
- }
-
- @Override
- public void onStop() {
- super.onStop();
- final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
- target.mCurrentApp = null;
- target.updateBody();
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- getFragmentManager().popBackStack();
- return true;
- }
- return super.onOptionsItemSelected(item);
- }
- }
-
- /**
- * Dialog to request user confirmation before setting
- * {@link NetworkPolicy#limitBytes}.
- */
- public static class ConfirmLimitFragment extends DialogFragment {
- private static final String EXTRA_MESSAGE = "message";
- private static final String EXTRA_LIMIT_BYTES = "limitBytes";
-
- public static void show(DataUsageSummary parent) {
- if (!parent.isAdded()) return;
-
- final NetworkPolicy policy = parent.mPolicyEditor.getPolicy(parent.mTemplate);
- if (policy == null) return;
-
- final Resources res = parent.getResources();
- final CharSequence message;
- final long minLimitBytes = (long) (policy.warningBytes * 1.2f);
- final long limitBytes;
-
- // TODO: customize default limits based on network template
- final String currentTab = parent.mCurrentTab;
- if (TAB_3G.equals(currentTab)) {
- message = res.getString(R.string.data_usage_limit_dialog_mobile);
- limitBytes = Math.max(5 * GB_IN_BYTES, minLimitBytes);
- } else if (TAB_4G.equals(currentTab)) {
- message = res.getString(R.string.data_usage_limit_dialog_mobile);
- limitBytes = Math.max(5 * GB_IN_BYTES, minLimitBytes);
- } else if (isMobileTab(currentTab)) {
- message = res.getString(R.string.data_usage_limit_dialog_mobile);
- limitBytes = Math.max(5 * GB_IN_BYTES, minLimitBytes);
- } else {
- throw new IllegalArgumentException("unknown current tab: " + currentTab);
- }
-
- final Bundle args = new Bundle();
- args.putCharSequence(EXTRA_MESSAGE, message);
- args.putLong(EXTRA_LIMIT_BYTES, limitBytes);
-
- final ConfirmLimitFragment dialog = new ConfirmLimitFragment();
- dialog.setArguments(args);
- dialog.setTargetFragment(parent, 0);
- dialog.show(parent.getFragmentManager(), TAG_CONFIRM_LIMIT);
- }
-
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- final Context context = getActivity();
-
- final CharSequence message = getArguments().getCharSequence(EXTRA_MESSAGE);
- final long limitBytes = getArguments().getLong(EXTRA_LIMIT_BYTES);
-
- final AlertDialog.Builder builder = new AlertDialog.Builder(context);
- builder.setTitle(R.string.data_usage_limit_dialog_title);
- builder.setMessage(message);
-
- builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
- if (target != null) {
- target.setPolicyLimitBytes(limitBytes);
- }
- }
- });
-
- return builder.create();
- }
- }
-
- /**
- * Dialog to edit {@link NetworkPolicy#cycleDay}.
- */
- public static class CycleEditorFragment extends DialogFragment {
- private static final String EXTRA_TEMPLATE = "template";
-
- public static void show(DataUsageSummary parent) {
- if (!parent.isAdded()) return;
-
- final Bundle args = new Bundle();
- args.putParcelable(EXTRA_TEMPLATE, parent.mTemplate);
-
- final CycleEditorFragment dialog = new CycleEditorFragment();
- dialog.setArguments(args);
- dialog.setTargetFragment(parent, 0);
- dialog.show(parent.getFragmentManager(), TAG_CYCLE_EDITOR);
- }
-
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- final Context context = getActivity();
- final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
- final NetworkPolicyEditor editor = target.mPolicyEditor;
-
- final AlertDialog.Builder builder = new AlertDialog.Builder(context);
- final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
-
- final View view = dialogInflater.inflate(R.layout.data_usage_cycle_editor, null, false);
- final NumberPicker cycleDayPicker = (NumberPicker) view.findViewById(R.id.cycle_day);
-
- final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE);
- final int cycleDay = editor.getPolicyCycleDay(template);
-
- cycleDayPicker.setMinValue(1);
- cycleDayPicker.setMaxValue(31);
- cycleDayPicker.setValue(cycleDay);
- cycleDayPicker.setWrapSelectorWheel(true);
-
- builder.setTitle(R.string.data_usage_cycle_editor_title);
- builder.setView(view);
-
- builder.setPositiveButton(R.string.data_usage_cycle_editor_positive,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- // clear focus to finish pending text edits
- cycleDayPicker.clearFocus();
-
- final int cycleDay = cycleDayPicker.getValue();
- final String cycleTimezone = new Time().timezone;
- editor.setPolicyCycleDay(template, cycleDay, cycleTimezone);
- target.updatePolicy(true);
- }
- });
-
- return builder.create();
- }
- }
-
- /**
- * Dialog to edit {@link NetworkPolicy#warningBytes}.
- */
- public static class WarningEditorFragment extends DialogFragment {
- private static final String EXTRA_TEMPLATE = "template";
-
- public static void show(DataUsageSummary parent) {
- if (!parent.isAdded()) return;
-
- final Bundle args = new Bundle();
- args.putParcelable(EXTRA_TEMPLATE, parent.mTemplate);
-
- final WarningEditorFragment dialog = new WarningEditorFragment();
- dialog.setArguments(args);
- dialog.setTargetFragment(parent, 0);
- dialog.show(parent.getFragmentManager(), TAG_WARNING_EDITOR);
- }
-
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- final Context context = getActivity();
- final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
- final NetworkPolicyEditor editor = target.mPolicyEditor;
-
- final AlertDialog.Builder builder = new AlertDialog.Builder(context);
- final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
-
- final View view = dialogInflater.inflate(R.layout.data_usage_bytes_editor, null, false);
- final NumberPicker bytesPicker = (NumberPicker) view.findViewById(R.id.bytes);
-
- final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE);
- final long warningBytes = editor.getPolicyWarningBytes(template);
- final long limitBytes = editor.getPolicyLimitBytes(template);
-
- bytesPicker.setMinValue(0);
- if (limitBytes != LIMIT_DISABLED) {
- bytesPicker.setMaxValue((int) (limitBytes / MB_IN_BYTES) - 1);
- } else {
- bytesPicker.setMaxValue(Integer.MAX_VALUE);
- }
- bytesPicker.setValue((int) (warningBytes / MB_IN_BYTES));
- bytesPicker.setWrapSelectorWheel(false);
-
- builder.setTitle(R.string.data_usage_warning_editor_title);
- builder.setView(view);
-
- builder.setPositiveButton(R.string.data_usage_cycle_editor_positive,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- // clear focus to finish pending text edits
- bytesPicker.clearFocus();
-
- final long bytes = bytesPicker.getValue() * MB_IN_BYTES;
- editor.setPolicyWarningBytes(template, bytes);
- target.updatePolicy(false);
- }
- });
-
- return builder.create();
- }
- }
-
- /**
- * Dialog to edit {@link NetworkPolicy#limitBytes}.
- */
- public static class LimitEditorFragment extends DialogFragment {
- private static final String EXTRA_TEMPLATE = "template";
-
- public static void show(DataUsageSummary parent) {
- if (!parent.isAdded()) return;
-
- final Bundle args = new Bundle();
- args.putParcelable(EXTRA_TEMPLATE, parent.mTemplate);
-
- final LimitEditorFragment dialog = new LimitEditorFragment();
- dialog.setArguments(args);
- dialog.setTargetFragment(parent, 0);
- dialog.show(parent.getFragmentManager(), TAG_LIMIT_EDITOR);
- }
-
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- final Context context = getActivity();
- final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
- final NetworkPolicyEditor editor = target.mPolicyEditor;
-
- final AlertDialog.Builder builder = new AlertDialog.Builder(context);
- final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
-
- final View view = dialogInflater.inflate(R.layout.data_usage_bytes_editor, null, false);
- final NumberPicker bytesPicker = (NumberPicker) view.findViewById(R.id.bytes);
-
- final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE);
- final long warningBytes = editor.getPolicyWarningBytes(template);
- final long limitBytes = editor.getPolicyLimitBytes(template);
-
- bytesPicker.setMaxValue(Integer.MAX_VALUE);
- if (warningBytes != WARNING_DISABLED && limitBytes > 0) {
- bytesPicker.setMinValue((int) (warningBytes / MB_IN_BYTES) + 1);
- } else {
- bytesPicker.setMinValue(0);
- }
- bytesPicker.setValue((int) (limitBytes / MB_IN_BYTES));
- bytesPicker.setWrapSelectorWheel(false);
-
- builder.setTitle(R.string.data_usage_limit_editor_title);
- builder.setView(view);
-
- builder.setPositiveButton(R.string.data_usage_cycle_editor_positive,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- // clear focus to finish pending text edits
- bytesPicker.clearFocus();
-
- final long bytes = bytesPicker.getValue() * MB_IN_BYTES;
- editor.setPolicyLimitBytes(template, bytes);
- target.updatePolicy(false);
- }
- });
-
- return builder.create();
- }
- }
- /**
- * Dialog to request user confirmation before disabling data.
- */
- public static class ConfirmDataDisableFragment extends DialogFragment {
- static int mSubId;
- public static void show(DataUsageSummary parent, int subId) {
- mSubId = subId;
- if (!parent.isAdded()) return;
-
- final ConfirmDataDisableFragment dialog = new ConfirmDataDisableFragment();
- dialog.setTargetFragment(parent, 0);
- dialog.show(parent.getFragmentManager(), TAG_CONFIRM_DATA_DISABLE);
- }
-
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- final Context context = getActivity();
-
- final AlertDialog.Builder builder = new AlertDialog.Builder(context);
- builder.setMessage(R.string.data_usage_disable_mobile);
-
- builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
- if (target != null) {
- // TODO: extend to modify policy enabled flag.
- target.setMobileDataEnabled(mSubId, false);
- }
- }
- });
- builder.setNegativeButton(android.R.string.cancel, null);
-
- return builder.create();
- }
- }
-
- /**
- * Dialog to request user confirmation before setting
- * {@link INetworkPolicyManager#setRestrictBackground(boolean)}.
- */
- public static class ConfirmRestrictFragment extends DialogFragment {
- public static void show(DataUsageSummary parent) {
- if (!parent.isAdded()) return;
-
- final ConfirmRestrictFragment dialog = new ConfirmRestrictFragment();
- dialog.setTargetFragment(parent, 0);
- dialog.show(parent.getFragmentManager(), TAG_CONFIRM_RESTRICT);
- }
-
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- final Context context = getActivity();
-
- final AlertDialog.Builder builder = new AlertDialog.Builder(context);
- builder.setTitle(R.string.data_usage_restrict_background_title);
- if (Utils.hasMultipleUsers(context)) {
- builder.setMessage(R.string.data_usage_restrict_background_multiuser);
- } else {
- builder.setMessage(R.string.data_usage_restrict_background);
- }
-
- builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
- if (target != null) {
- target.setRestrictBackground(true);
- }
- }
- });
- builder.setNegativeButton(android.R.string.cancel, null);
-
- return builder.create();
- }
- }
-
- /**
- * Dialog to inform user that {@link #POLICY_REJECT_METERED_BACKGROUND}
- * change has been denied, usually based on
- * {@link DataUsageSummary#hasLimitedNetworks()}.
- */
- public static class DeniedRestrictFragment extends DialogFragment {
- public static void show(DataUsageSummary parent) {
- if (!parent.isAdded()) return;
-
- final DeniedRestrictFragment dialog = new DeniedRestrictFragment();
- dialog.setTargetFragment(parent, 0);
- dialog.show(parent.getFragmentManager(), TAG_DENIED_RESTRICT);
- }
-
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- final Context context = getActivity();
-
- final AlertDialog.Builder builder = new AlertDialog.Builder(context);
- builder.setTitle(R.string.data_usage_app_restrict_background);
- builder.setMessage(R.string.data_usage_restrict_denied_dialog);
- builder.setPositiveButton(android.R.string.ok, null);
-
- return builder.create();
- }
- }
-
- /**
- * Dialog to request user confirmation before setting
- * {@link #POLICY_REJECT_METERED_BACKGROUND}.
- */
- public static class ConfirmAppRestrictFragment extends DialogFragment {
- public static void show(DataUsageSummary parent) {
- if (!parent.isAdded()) return;
-
- final ConfirmAppRestrictFragment dialog = new ConfirmAppRestrictFragment();
- dialog.setTargetFragment(parent, 0);
- dialog.show(parent.getFragmentManager(), TAG_CONFIRM_APP_RESTRICT);
- }
-
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- final Context context = getActivity();
-
- final AlertDialog.Builder builder = new AlertDialog.Builder(context);
- builder.setTitle(R.string.data_usage_app_restrict_dialog_title);
- builder.setMessage(R.string.data_usage_app_restrict_dialog);
-
- builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
- if (target != null) {
- target.setAppRestrictBackground(true);
- }
- }
- });
- builder.setNegativeButton(android.R.string.cancel, null);
-
- return builder.create();
- }
- }
-
- /**
- * Compute default tab that should be selected, based on
- * {@link NetworkPolicyManager#EXTRA_NETWORK_TEMPLATE} extra.
- */
- private static String computeTabFromIntent(Intent intent) {
- final NetworkTemplate template = intent.getParcelableExtra(EXTRA_NETWORK_TEMPLATE);
- if (template == null) {
- final int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
- SubscriptionManager.INVALID_SUBSCRIPTION_ID);
- if (SubscriptionManager.isValidSubscriptionId(subId)) {
- return TAB_MOBILE + String.valueOf(subId);
- }
- return null;
- }
-
- switch (template.getMatchRule()) {
- case MATCH_MOBILE_3G_LOWER:
- return TAB_3G;
- case MATCH_MOBILE_4G:
- return TAB_4G;
- case MATCH_MOBILE_ALL:
- return TAB_MOBILE;
- case MATCH_WIFI:
- return TAB_WIFI;
- default:
- return null;
- }
- }
-
- /**
- * Background task that loads {@link UidDetail}, binding to
- * {@link DataUsageAdapter} row item when finished.
- */
- private static class UidDetailTask extends AsyncTask<Void, Void, UidDetail> {
- private final UidDetailProvider mProvider;
- private final AppItem mItem;
- private final View mTarget;
-
- private UidDetailTask(UidDetailProvider provider, AppItem item, View target) {
- mProvider = checkNotNull(provider);
- mItem = checkNotNull(item);
- mTarget = checkNotNull(target);
- }
-
- public static void bindView(
- UidDetailProvider provider, AppItem item, View target) {
- final UidDetailTask existing = (UidDetailTask) target.getTag();
- if (existing != null) {
- existing.cancel(false);
- }
-
- final UidDetail cachedDetail = provider.getUidDetail(item.key, false);
- if (cachedDetail != null) {
- bindView(cachedDetail, target);
- } else {
- target.setTag(new UidDetailTask(provider, item, target).executeOnExecutor(
- AsyncTask.THREAD_POOL_EXECUTOR));
- }
- }
-
- private static void bindView(UidDetail detail, View target) {
- final ImageView icon = (ImageView) target.findViewById(android.R.id.icon);
- final TextView title = (TextView) target.findViewById(android.R.id.title);
-
- if (detail != null) {
- icon.setImageDrawable(detail.icon);
- title.setText(detail.label);
- title.setContentDescription(detail.contentDescription);
- } else {
- icon.setImageDrawable(null);
- title.setText(null);
- }
- }
-
- @Override
- protected void onPreExecute() {
- bindView(null, mTarget);
- }
-
- @Override
- protected UidDetail doInBackground(Void... params) {
- return mProvider.getUidDetail(mItem.key, true);
- }
-
- @Override
- protected void onPostExecute(UidDetail result) {
- bindView(result, mTarget);
- }
- }
-
- /**
- * Test if device has a mobile data radio with SIM in ready state.
- */
- public static boolean hasReadyMobileRadio(Context context) {
- if (TEST_RADIOS) {
- return SystemProperties.get(TEST_RADIOS_PROP).contains("mobile");
- }
-
- final ConnectivityManager conn = ConnectivityManager.from(context);
- final TelephonyManager tele = TelephonyManager.from(context);
-
- final List<SubscriptionInfo> subInfoList =
- SubscriptionManager.from(context).getActiveSubscriptionInfoList();
- // No activated Subscriptions
- if (subInfoList == null) {
- if (LOGD) Log.d(TAG, "hasReadyMobileRadio: subInfoList=null");
- return false;
- }
- // require both supported network and ready SIM
- boolean isReady = true;
- for (SubscriptionInfo subInfo : subInfoList) {
- isReady = isReady & tele.getSimState(subInfo.getSimSlotIndex()) == SIM_STATE_READY;
- if (LOGD) Log.d(TAG, "hasReadyMobileRadio: subInfo=" + subInfo);
- }
- boolean retVal = conn.isNetworkSupported(TYPE_MOBILE) && isReady;
- if (LOGD) {
- Log.d(TAG, "hasReadyMobileRadio:"
- + " conn.isNetworkSupported(TYPE_MOBILE)="
- + conn.isNetworkSupported(TYPE_MOBILE)
- + " isReady=" + isReady);
- }
- return retVal;
- }
-
- /*
- * TODO: consider adding to TelephonyManager or SubscritpionManager.
- */
- public static boolean hasReadyMobileRadio(Context context, int subId) {
- if (TEST_RADIOS) {
- return SystemProperties.get(TEST_RADIOS_PROP).contains("mobile");
- }
-
- final ConnectivityManager conn = ConnectivityManager.from(context);
- final TelephonyManager tele = TelephonyManager.from(context);
- final int slotId = SubscriptionManager.getSlotId(subId);
- final boolean isReady = tele.getSimState(slotId) == SIM_STATE_READY;
-
- boolean retVal = conn.isNetworkSupported(TYPE_MOBILE) && isReady;
- if (LOGD) Log.d(TAG, "hasReadyMobileRadio: subId=" + subId
- + " conn.isNetworkSupported(TYPE_MOBILE)=" + conn.isNetworkSupported(TYPE_MOBILE)
- + " isReady=" + isReady);
- return retVal;
- }
-
- /**
- * Test if device has a mobile 4G data radio.
- */
- public static boolean hasReadyMobile4gRadio(Context context) {
- if (!NetworkPolicyEditor.ENABLE_SPLIT_POLICIES) {
- return false;
- }
- if (TEST_RADIOS) {
- return SystemProperties.get(TEST_RADIOS_PROP).contains("4g");
- }
-
- final ConnectivityManager conn = ConnectivityManager.from(context);
- final TelephonyManager tele = TelephonyManager.from(context);
-
- final boolean hasWimax = conn.isNetworkSupported(TYPE_WIMAX);
- final boolean hasLte = (tele.getLteOnCdmaMode() == PhoneConstants.LTE_ON_CDMA_TRUE)
- && hasReadyMobileRadio(context);
- return hasWimax || hasLte;
- }
-
- /**
- * Test if device has a Wi-Fi data radio.
- */
- public static boolean hasWifiRadio(Context context) {
- if (TEST_RADIOS) {
- return SystemProperties.get(TEST_RADIOS_PROP).contains("wifi");
- }
-
- final ConnectivityManager conn = ConnectivityManager.from(context);
- return conn.isNetworkSupported(TYPE_WIFI);
- }
-
- /**
- * Test if device has an ethernet network connection.
- */
- public boolean hasEthernet(Context context) {
- if (TEST_RADIOS) {
- return SystemProperties.get(TEST_RADIOS_PROP).contains("ethernet");
- }
-
- final ConnectivityManager conn = ConnectivityManager.from(context);
- final boolean hasEthernet = conn.isNetworkSupported(TYPE_ETHERNET);
-
- final long ethernetBytes;
- if (mStatsSession != null) {
- try {
- ethernetBytes = mStatsSession.getSummaryForNetwork(
- NetworkTemplate.buildTemplateEthernet(), Long.MIN_VALUE, Long.MAX_VALUE)
- .getTotalBytes();
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
- } else {
- ethernetBytes = 0;
- }
-
- // only show ethernet when both hardware present and traffic has occurred
- return hasEthernet && ethernetBytes > 0;
- }
-
- /**
- * Inflate a {@link Preference} style layout, adding the given {@link View}
- * widget into {@link android.R.id#widget_frame}.
- */
- private static View inflatePreference(LayoutInflater inflater, ViewGroup root, View widget) {
- final View view = inflater.inflate(R.layout.preference, root, false);
- final LinearLayout widgetFrame = (LinearLayout) view.findViewById(
- android.R.id.widget_frame);
- widgetFrame.addView(widget, new LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
- return view;
- }
-
- /**
- * Test if any networks are currently limited.
- */
- private boolean hasLimitedNetworks() {
- return !buildLimitedNetworksList().isEmpty();
- }
-
- /**
- * Build string describing currently limited networks, which defines when
- * background data is restricted.
- */
- @Deprecated
- private CharSequence buildLimitedNetworksString() {
- final List<CharSequence> limited = buildLimitedNetworksList();
-
- // handle case where no networks limited
- if (limited.isEmpty()) {
- limited.add(getText(R.string.data_usage_list_none));
- }
-
- final ICUResourceBundle icuBundle = (ICUResourceBundle) UResourceBundle.
- getBundleInstance(ICUResourceBundle.ICU_BASE_NAME);
- final String listMiddlePattern =
- icuBundle.getStringWithFallback("listPattern/standard/middle");
- // The returned pattern is something like "{0}, {1}", from which we want
- // to extract the ", " part.
- final int firstClosingBrace = listMiddlePattern.indexOf('}');
- final int lastOpeningBrace = listMiddlePattern.lastIndexOf('{');
- final CharSequence delimiter = listMiddlePattern.substring(
- firstClosingBrace+1, lastOpeningBrace);
-
- return TextUtils.join(delimiter, limited);
- }
-
- /**
- * Build list of currently limited networks, which defines when background
- * data is restricted.
- */
- @Deprecated
- private List<CharSequence> buildLimitedNetworksList() {
- final Context context = getActivity();
-
- // build combined list of all limited networks
- final ArrayList<CharSequence> limited = Lists.newArrayList();
-
- final TelephonyManager tele = TelephonyManager.from(context);
- if (tele.getSimState() == SIM_STATE_READY) {
- final String subscriberId = getActiveSubscriberId(context);
- if (mPolicyEditor.hasLimitedPolicy(buildTemplateMobileAll(subscriberId))) {
- limited.add(getText(R.string.data_usage_list_mobile));
- }
- if (mPolicyEditor.hasLimitedPolicy(buildTemplateMobile3gLower(subscriberId))) {
- limited.add(getText(R.string.data_usage_tab_3g));
- }
- if (mPolicyEditor.hasLimitedPolicy(buildTemplateMobile4g(subscriberId))) {
- limited.add(getText(R.string.data_usage_tab_4g));
- }
- }
-
- if (mPolicyEditor.hasLimitedPolicy(buildTemplateWifiWildcard())) {
- limited.add(getText(R.string.data_usage_tab_wifi));
- }
- if (mPolicyEditor.hasLimitedPolicy(buildTemplateEthernet())) {
- limited.add(getText(R.string.data_usage_tab_ethernet));
- }
-
- return limited;
- }
-
- /**
- * Inset both selector and divider {@link Drawable} on the given
- * {@link ListView} by the requested dimensions.
- */
- private static void insetListViewDrawables(ListView view, int insetSide) {
- final Drawable selector = view.getSelector();
- final Drawable divider = view.getDivider();
-
- // fully unregister these drawables so callbacks can be maintained after
- // wrapping below.
- final Drawable stub = new ColorDrawable(Color.TRANSPARENT);
- view.setSelector(stub);
- view.setDivider(stub);
-
- view.setSelector(new InsetBoundsDrawable(selector, insetSide));
- view.setDivider(new InsetBoundsDrawable(divider, insetSide));
- }
-
- /**
- * Set {@link android.R.id#title} for a preference view inflated with
- * {@link #inflatePreference(LayoutInflater, ViewGroup, View)}.
- */
- private static void setPreferenceTitle(View parent, int resId) {
- final TextView title = (TextView) parent.findViewById(android.R.id.title);
- title.setText(resId);
- }
-
- /**
- * Set {@link android.R.id#summary} for a preference view inflated with
- * {@link #inflatePreference(LayoutInflater, ViewGroup, View)}.
- */
- private static void setPreferenceSummary(View parent, CharSequence string) {
- final TextView summary = (TextView) parent.findViewById(android.R.id.summary);
- summary.setVisibility(View.VISIBLE);
- summary.setText(string);
- }
-
- private void addMobileTab(Context context, SubscriptionInfo subInfo, boolean isMultiSim) {
- if (subInfo != null && mMobileTagMap != null) {
- if (hasReadyMobileRadio(context, subInfo.getSubscriptionId())) {
- if (isMultiSim) {
- mTabHost.addTab(buildTabSpec(mMobileTagMap.get(subInfo.getSubscriptionId()),
- subInfo.getDisplayName()));
- } else {
- mTabHost.addTab(buildTabSpec(mMobileTagMap.get(subInfo.getSubscriptionId()),
- R.string.data_usage_tab_mobile));
- }
- }
- } else {
- if (LOGD) Log.d(TAG, "addMobileTab: subInfoList is null");
- }
- }
-
- private SubscriptionInfo getCurrentTabSubInfo(Context context) {
- if (mSubInfoList != null && mTabHost != null) {
- final int currentTagIndex = mTabHost.getCurrentTab();
- int i = 0;
- for (SubscriptionInfo subInfo : mSubInfoList) {
- if (hasReadyMobileRadio(context, subInfo.getSubscriptionId())) {
- if (i++ == currentTagIndex) {
- return subInfo;
- }
- }
- }
- }
- return null;
- }
-
- /**
- * Init a map with subId key and mobile tag name
- * @param subInfoList The subscription Info List
- * @return The map or null if no activated subscription
- */
- private Map<Integer, String> initMobileTabTag(List<SubscriptionInfo> subInfoList) {
- Map<Integer, String> map = null;
- if (subInfoList != null) {
- String mobileTag;
- map = new HashMap<Integer, String>();
- for (SubscriptionInfo subInfo : subInfoList) {
- mobileTag = TAB_MOBILE + String.valueOf(subInfo.getSubscriptionId());
- map.put(subInfo.getSubscriptionId(), mobileTag);
- }
- }
- return map;
- }
-
- private static boolean isMobileTab(String currentTab) {
- return currentTab != null ? currentTab.contains(TAB_MOBILE) : false;
- }
-
- private int getSubId(String currentTab) {
- if (mMobileTagMap != null) {
- Set<Integer> set = mMobileTagMap.keySet();
- for (Integer subId : set) {
- if (mMobileTagMap.get(subId).equals(currentTab)) {
- return subId;
- }
- }
- }
- Log.e(TAG, "currentTab = " + currentTab + " non mobile tab called this function");
- return -1;
- }
-
- private boolean isMobileDataAvailable(int subId) {
- return mSubscriptionManager.getActiveSubscriptionInfo(subId) != null;
- }
-
- private static class SummaryProvider
- implements SummaryLoader.SummaryProvider {
-
- private final Activity mActivity;
- private final SummaryLoader mSummaryLoader;
- private final MobileDataController mDataController;
-
- public SummaryProvider(Activity activity, SummaryLoader summaryLoader) {
- mActivity = activity;
- mSummaryLoader = summaryLoader;
- mDataController = new MobileDataController(activity);
- }
-
- @Override
- public void setListening(boolean listening) {
- if (listening) {
- MobileDataController.DataUsageInfo info = mDataController.getDataUsageInfo();
- String used;
- if (info == null) {
- used = Formatter.formatFileSize(mActivity, 0);
- } else if (info.limitLevel <= 0) {
- used = Formatter.formatFileSize(mActivity, info.usageLevel);
- } else {
- used = Utils.formatPercentage(info.usageLevel, info.limitLevel);
- }
- mSummaryLoader.setSummary(this,
- mActivity.getString(R.string.data_usage_summary_format, used));
- }
- }
- }
-
- public static final SummaryLoader.SummaryProviderFactory SUMMARY_PROVIDER_FACTORY
- = new SummaryLoader.SummaryProviderFactory() {
- @Override
- public SummaryLoader.SummaryProvider createSummaryProvider(Activity activity,
- SummaryLoader summaryLoader) {
- return new SummaryProvider(activity, summaryLoader);
- }
- };
-
- /**
- * For search
- */
- public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
- new BaseSearchIndexProvider() {
- @Override
- public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
- final List<SearchIndexableRaw> result = new ArrayList<SearchIndexableRaw>();
-
- final Resources res = context.getResources();
-
- // Add fragment title
- SearchIndexableRaw data = new SearchIndexableRaw(context);
- data.title = res.getString(R.string.data_usage_summary_title);
- data.screenTitle = res.getString(R.string.data_usage_summary_title);
- result.add(data);
-
- // Mobile data
- data = new SearchIndexableRaw(context);
- data.key = DATA_USAGE_ENABLE_MOBILE_KEY;
- data.title = res.getString(R.string.data_usage_enable_mobile);
- data.screenTitle = res.getString(R.string.data_usage_summary_title);
- result.add(data);
-
- // Set mobile data limit
- data = new SearchIndexableRaw(context);
- data.key = DATA_USAGE_DISABLE_MOBILE_LIMIT_KEY;
- data.title = res.getString(R.string.data_usage_disable_mobile_limit);
- data.screenTitle = res.getString(R.string.data_usage_summary_title);
- result.add(data);
-
- // Data usage cycle
- data = new SearchIndexableRaw(context);
- data.key = DATA_USAGE_CYCLE_KEY;
- data.title = res.getString(R.string.data_usage_cycle);
- data.screenTitle = res.getString(R.string.data_usage_summary_title);
- result.add(data);
-
- return result;
- }
- };
-}
diff --git a/src/com/android/settings/HighlightingFragment.java b/src/com/android/settings/HighlightingFragment.java
deleted file mode 100644
index 2d305e7..0000000
--- a/src/com/android/settings/HighlightingFragment.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings;
-
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.text.TextUtils;
-import android.view.View;
-import android.view.ViewGroup;
-
-public abstract class HighlightingFragment extends InstrumentedFragment {
-
- private static final String TAG = "HighlightSettingsFragment";
-
- private static final int DELAY_HIGHLIGHT_DURATION_MILLIS = 400;
- private static final String SAVE_HIGHLIGHTED_KEY = "android:view_highlighted";
-
- private String mViewKey;
- private boolean mViewHighlighted = false;
- private Drawable mHighlightDrawable;
-
- @Override
- public void onCreate(Bundle icicle) {
- super.onCreate(icicle);
-
- if (icicle != null) {
- mViewHighlighted = icicle.getBoolean(SAVE_HIGHLIGHTED_KEY);
- }
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
-
- outState.putBoolean(SAVE_HIGHLIGHTED_KEY, mViewHighlighted);
- }
-
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
-
- final Bundle args = getArguments();
- if (args != null) {
- mViewKey = args.getString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY);
- highlightViewIfNeeded();
- }
- }
-
- public void highlightViewIfNeeded() {
- if (!mViewHighlighted &&!TextUtils.isEmpty(mViewKey)) {
- highlightView(mViewKey);
- }
- }
-
- private Drawable getHighlightDrawable() {
- if (mHighlightDrawable == null) {
- mHighlightDrawable = getActivity().getDrawable(R.drawable.preference_highlight);
- }
- return mHighlightDrawable;
- }
-
- private void highlightView(String key) {
- final Drawable highlight = getHighlightDrawable();
-
- // Try locating the View thru its Tag / Key
- final View view = findViewForKey(getView(), key);
- if (view != null ) {
- view.setBackground(highlight);
-
- getView().postDelayed(new Runnable() {
- @Override
- public void run() {
- final int centerX = view.getWidth() / 2;
- final int centerY = view.getHeight() / 2;
- highlight.setHotspot(centerX, centerY);
- view.setPressed(true);
- view.setPressed(false);
- }
- }, DELAY_HIGHLIGHT_DURATION_MILLIS);
-
- mViewHighlighted = true;
- }
- }
-
- private View findViewForKey(View root, String key) {
- if (checkTag(root, key)) {
- return root;
- }
- if (root instanceof ViewGroup) {
- final ViewGroup group = (ViewGroup) root;
- final int count = group.getChildCount();
- for (int n = 0; n < count; n++) {
- final View child = group.getChildAt(n);
- final View view = findViewForKey(child, key);
- if (view != null) {
- return view;
- }
- }
- }
- return null;
- }
-
- private boolean checkTag(View view, String key) {
- final Object tag = view.getTag(R.id.preference_highlight_key);
- if (tag == null || !(tag instanceof String)) {
- return false;
- }
- final String viewKey = (String) tag;
- return (!TextUtils.isEmpty(viewKey) && viewKey.equals(key));
- }
-}
diff --git a/src/com/android/settings/InstrumentedFragment.java b/src/com/android/settings/InstrumentedFragment.java
index 2d39961..58b8eb0 100644
--- a/src/com/android/settings/InstrumentedFragment.java
+++ b/src/com/android/settings/InstrumentedFragment.java
@@ -34,6 +34,9 @@
public static final int CONFIGURE_WIFI = UNDECLARED + 4;
public static final int DISPLAY_SCREEN_ZOOM = UNDECLARED + 5;
public static final int ACCESSIBILITY_FONT_SIZE = UNDECLARED + 6;
+ public static final int DATA_USAGE_LIST = UNDECLARED + 7;
+ public static final int BILLING_CYCLE = UNDECLARED + 8;
+ public static final int APP_DATA_USAGE = UNDECLARED + 9;
/**
* Declare the view of this category.
diff --git a/src/com/android/settings/SettingsActivity.java b/src/com/android/settings/SettingsActivity.java
index de66aea..dab2cb7 100644
--- a/src/com/android/settings/SettingsActivity.java
+++ b/src/com/android/settings/SettingsActivity.java
@@ -68,6 +68,7 @@
import com.android.settings.bluetooth.BluetoothSettings;
import com.android.settings.dashboard.DashboardSummary;
import com.android.settings.dashboard.SearchResultsSummary;
+import com.android.settings.datausage.DataUsageSummary;
import com.android.settings.deviceinfo.PrivateVolumeForget;
import com.android.settings.deviceinfo.PrivateVolumeSettings;
import com.android.settings.deviceinfo.PublicVolumeSettings;
@@ -85,9 +86,9 @@
import com.android.settings.notification.AppNotificationSettings;
import com.android.settings.notification.ConfigureNotificationSettings;
import com.android.settings.notification.NotificationAccessSettings;
-import com.android.settings.notification.SoundSettings;
import com.android.settings.notification.NotificationStation;
import com.android.settings.notification.OtherSoundSettings;
+import com.android.settings.notification.SoundSettings;
import com.android.settings.notification.ZenAccessSettings;
import com.android.settings.notification.ZenModeAutomationSettings;
import com.android.settings.notification.ZenModeEventRuleSettings;
diff --git a/src/com/android/settings/SettingsPreferenceFragment.java b/src/com/android/settings/SettingsPreferenceFragment.java
index f79def9..2ba49ce 100644
--- a/src/com/android/settings/SettingsPreferenceFragment.java
+++ b/src/com/android/settings/SettingsPreferenceFragment.java
@@ -24,7 +24,6 @@
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
-import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceGroup;
@@ -187,6 +186,11 @@
setEmptyView(loading);
}
+ public void setLoading(boolean loading, boolean animate) {
+ View loading_container = getView().findViewById(R.id.loading_container);
+ Utils.handleLoadingContainer(loading_container, getListView(), !loading, animate);
+ }
+
public void registerObserverIfNeeded() {
if (!mIsDataSetObserverRegistered) {
if (mCurrentRootAdapter != null) {
diff --git a/src/com/android/settings/SummaryPreference.java b/src/com/android/settings/SummaryPreference.java
new file mode 100644
index 0000000..0943a2b
--- /dev/null
+++ b/src/com/android/settings/SummaryPreference.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.settings;
+
+import android.content.Context;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceViewHolder;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.TextView;
+import com.android.settings.applications.LinearColorBar;
+
+/**
+ * Provides a summary of a setting page in a preference. Such as memory or data usage.
+ */
+public class SummaryPreference extends Preference {
+
+ private static final String TAG = "SummaryPreference";
+ private String mAmount;
+ private String mUnits;
+
+ private int mLeft, mMiddle, mRight;
+ private float mLeftRatio, mMiddleRatio, mRightRatio;
+ private String mStartLabel;
+ private String mEndLabel;
+
+ public SummaryPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ setLayoutResource(R.layout.settings_summary_preference);
+ mLeft = context.getColor(R.color.summary_default_start);
+ mRight = context.getColor(R.color.summary_default_end);
+ }
+
+ public void setAmount(String amount) {
+ mAmount = amount;
+ if (mAmount != null && mUnits != null) {
+ setTitle(TextUtils.expandTemplate(getContext().getText(R.string.storage_size_large),
+ mAmount, mUnits));
+ }
+ }
+
+ public void setUnits(String units) {
+ mUnits = units;
+ if (mAmount != null && mUnits != null) {
+ setTitle(TextUtils.expandTemplate(getContext().getText(R.string.storage_size_large),
+ mAmount, mUnits));
+ }
+ }
+
+ public void setLabels(String start, String end) {
+ mStartLabel = start;
+ mEndLabel = end;
+ notifyChanged();
+ }
+
+ public void setRatios(float left, float middle, float right) {
+ mLeftRatio = left;
+ mMiddleRatio = middle;
+ mRightRatio = right;
+ notifyChanged();
+ }
+
+ public void setColors(int left, int middle, int right) {
+ mLeft = left;
+ mMiddle = middle;
+ mRight = right;
+ notifyChanged();
+ }
+
+ @Override
+ public void onBindViewHolder(PreferenceViewHolder holder) {
+ super.onBindViewHolder(holder);
+
+ LinearColorBar colorBar = (LinearColorBar) holder.itemView.findViewById(R.id.color_bar);
+ colorBar.setRatios(mLeftRatio, mMiddleRatio, mRightRatio);
+ colorBar.setColors(mLeft, mMiddle, mRight);
+
+ if (!TextUtils.isEmpty(mStartLabel) || !TextUtils.isEmpty(mEndLabel)) {
+ holder.findViewById(R.id.label_bar).setVisibility(View.VISIBLE);
+ ((TextView) holder.findViewById(android.R.id.text1)).setText(mStartLabel);
+ ((TextView) holder.findViewById(android.R.id.text2)).setText(mEndLabel);
+ } else {
+ holder.findViewById(R.id.label_bar).setVisibility(View.GONE);
+ }
+ }
+}
diff --git a/src/com/android/settings/Utils.java b/src/com/android/settings/Utils.java
index 4afa1d9..96d0a37 100644
--- a/src/com/android/settings/Utils.java
+++ b/src/com/android/settings/Utils.java
@@ -67,6 +67,7 @@
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextUtils;
+import android.text.format.DateUtils;
import android.text.style.TtsSpan;
import android.util.ArraySet;
import android.util.Log;
@@ -81,6 +82,7 @@
import android.widget.ListView;
import android.widget.TabWidget;
import com.android.internal.util.UserIcons;
+import com.android.settings.datausage.DataUsageList;
import java.io.IOException;
import java.io.InputStream;
@@ -91,6 +93,8 @@
import java.util.Locale;
import static android.content.Intent.EXTRA_USER;
+import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH;
+import static android.text.format.DateUtils.FORMAT_SHOW_DATE;
public final class Utils extends com.android.settingslib.Utils {
@@ -978,5 +982,19 @@
context.getTheme().resolveAttribute(attr, value, true);
return value.resourceId;
}
+
+ private static final StringBuilder sBuilder = new StringBuilder(50);
+ private static final java.util.Formatter sFormatter = new java.util.Formatter(
+ sBuilder, Locale.getDefault());
+
+ public static String formatDateRange(Context context, long start, long end) {
+ final int flags = FORMAT_SHOW_DATE | FORMAT_ABBREV_MONTH;
+
+ synchronized (sBuilder) {
+ sBuilder.setLength(0);
+ return DateUtils.formatDateRange(context, sFormatter, start, end, flags, null)
+ .toString();
+ }
+ }
}
diff --git a/src/com/android/settings/applications/InstalledAppDetails.java b/src/com/android/settings/applications/InstalledAppDetails.java
index 7d8a7f0..42d8e46 100755
--- a/src/com/android/settings/applications/InstalledAppDetails.java
+++ b/src/com/android/settings/applications/InstalledAppDetails.java
@@ -21,7 +21,6 @@
import android.app.AlertDialog;
import android.app.LoaderManager.LoaderCallbacks;
import android.app.admin.DevicePolicyManager;
-import android.icu.text.ListFormatter;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -37,6 +36,7 @@
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
+import android.icu.text.ListFormatter;
import android.net.INetworkStatsService;
import android.net.INetworkStatsSession;
import android.net.NetworkTemplate;
@@ -63,16 +63,17 @@
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
-
import com.android.internal.logging.MetricsLogger;
import com.android.internal.os.BatterySipper;
import com.android.internal.os.BatteryStatsHelper;
import com.android.settings.AppHeader;
-import com.android.settings.DataUsageSummary;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.Utils;
import com.android.settings.applications.PermissionsSummaryHelper.PermissionsResultCallback;
+import com.android.settings.datausage.AppDataUsage;
+import com.android.settings.datausage.DataUsageList;
+import com.android.settings.datausage.DataUsageSummary;
import com.android.settings.fuelgauge.BatteryEntry;
import com.android.settings.fuelgauge.PowerUsageDetail;
import com.android.settings.notification.AppNotificationSettings;
@@ -753,13 +754,7 @@
ProcessStatsBase.launchMemoryDetail((SettingsActivity) getActivity(),
mStatsManager.getMemInfo(), mStats, false);
} else if (preference == mDataPreference) {
- Bundle args = new Bundle();
- args.putString(DataUsageSummary.EXTRA_SHOW_APP_IMMEDIATE_PKG,
- mAppEntry.info.packageName);
-
- SettingsActivity sa = (SettingsActivity) getActivity();
- sa.startPreferencePanel(DataUsageSummary.class.getName(), args, -1,
- getString(R.string.app_data_usage), this, SUB_INFO_FRAGMENT);
+ startAppInfoFragment(AppDataUsage.class, getString(R.string.app_data_usage));
} else if (preference == mBatteryPreference) {
BatteryEntry entry = new BatteryEntry(getActivity(), null, mUserManager, mSipper);
PowerUsageDetail.startBatteryDetailPage((SettingsActivity) getActivity(),
@@ -794,7 +789,7 @@
}
public static NetworkTemplate getTemplate(Context context) {
- if (DataUsageSummary.hasReadyMobileRadio(context)) {
+ if (DataUsageList.hasReadyMobileRadio(context)) {
return NetworkTemplate.buildTemplateMobileWildcard();
}
if (DataUsageSummary.hasWifiRadio(context)) {
diff --git a/src/com/android/settings/applications/ProcessStatsDetail.java b/src/com/android/settings/applications/ProcessStatsDetail.java
index 9db79bc..4ad480e 100644
--- a/src/com/android/settings/applications/ProcessStatsDetail.java
+++ b/src/com/android/settings/applications/ProcessStatsDetail.java
@@ -40,14 +40,13 @@
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
-import android.widget.TextView;
-
import com.android.internal.logging.MetricsLogger;
import com.android.settings.AppHeader;
import com.android.settings.CancellablePreference;
import com.android.settings.CancellablePreference.OnCancelListener;
import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.SummaryPreference;
import com.android.settings.applications.ProcStatsEntry.Service;
import java.util.ArrayList;
@@ -87,8 +86,6 @@
private long mTotalTime;
private long mOnePercentTime;
- private LinearColorBar mColorBar;
-
private double mMaxMemoryUsage;
private double mTotalScale;
@@ -177,20 +174,19 @@
mProcGroup = (PreferenceCategory) findPreference(KEY_PROCS);
fillProcessesSection();
- LayoutPreference headerLayout = (LayoutPreference) findPreference(KEY_DETAILS_HEADER);
+ SummaryPreference summaryPreference = (SummaryPreference) findPreference(KEY_DETAILS_HEADER);
// TODO: Find way to share this code with ProcessStatsPreference.
boolean statsForeground = mApp.mRunWeight > mApp.mBgWeight;
double avgRam = (statsForeground ? mApp.mRunWeight : mApp.mBgWeight) * mWeightToRam;
float avgRatio = (float) (avgRam / mMaxMemoryUsage);
float remainingRatio = 1 - avgRatio;
- mColorBar = (LinearColorBar) headerLayout.findViewById(R.id.color_bar);
Context context = getActivity();
- mColorBar.setColors( context.getColor(R.color.memory_max_use), 0,
- context.getColor(R.color.memory_remaining));
- mColorBar.setRatios(avgRatio, 0, remainingRatio);
- ((TextView) headerLayout.findViewById(R.id.memory_state)).setText(
- Formatter.formatShortFileSize(getContext(), (long) avgRam));
+ summaryPreference.setRatios(avgRatio, 0, remainingRatio);
+ Formatter.BytesResult usedResult = Formatter.formatBytes(context.getResources(),
+ (long) avgRam, Formatter.FLAG_SHORTER);
+ summaryPreference.setAmount(usedResult.value);
+ summaryPreference.setUnits(usedResult.units);
long duration = Math.max(mApp.mRunDuration, mApp.mBgDuration);
CharSequence frequency = ProcStatsPackageEntry.getFrequency(duration
diff --git a/src/com/android/settings/applications/ProcessStatsSummary.java b/src/com/android/settings/applications/ProcessStatsSummary.java
index 09fea89..28917c8 100644
--- a/src/com/android/settings/applications/ProcessStatsSummary.java
+++ b/src/com/android/settings/applications/ProcessStatsSummary.java
@@ -16,18 +16,15 @@
package com.android.settings.applications;
import android.app.Activity;
-import android.content.BroadcastReceiver;
import android.content.Context;
import android.os.Bundle;
import android.support.v7.preference.Preference;
import android.support.v7.preference.Preference.OnPreferenceClickListener;
-import android.text.TextUtils;
import android.text.format.Formatter;
import android.text.format.Formatter.BytesResult;
-import android.widget.TextView;
-
import com.android.internal.logging.MetricsLogger;
import com.android.settings.R;
+import com.android.settings.SummaryPreference;
import com.android.settings.Utils;
import com.android.settings.applications.ProcStatsData.MemInfo;
import com.android.settings.dashboard.SummaryLoader;
@@ -42,9 +39,7 @@
private static final String KEY_FREE = "free";
private static final String KEY_APP_LIST = "apps_list";
- private LinearColorBar mColors;
- private LayoutPreference mHeader;
- private TextView mMemStatus;
+ private SummaryPreference mSummaryPref;
private Preference mPerformance;
private Preference mTotalMemory;
@@ -57,9 +52,10 @@
super.onCreate(icicle);
addPreferencesFromResource(R.xml.process_stats_summary);
- mHeader = (LayoutPreference) findPreference(KEY_STATUS_HEADER);
- mMemStatus = (TextView) mHeader.findViewById(R.id.memory_state);
- mColors = (LinearColorBar) mHeader.findViewById(R.id.color_bar);
+ mSummaryPref = (SummaryPreference) findPreference(KEY_STATUS_HEADER);
+ int memColor = getContext().getColor(R.color.running_processes_apps_ram);
+ mSummaryPref.setColors(memColor, memColor,
+ getContext().getColor(R.color.running_processes_free_ram));
mPerformance = findPreference(KEY_PERFORMANCE);
mTotalMemory = findPreference(KEY_TOTAL_MEMORY);
@@ -72,8 +68,6 @@
@Override
public void refreshUi() {
Context context = getContext();
- int memColor = context.getColor(R.color.running_processes_apps_ram);
- mColors.setColors(memColor, memColor, context.getColor(R.color.running_processes_free_ram));
MemInfo memInfo = mStatsManager.getMemInfo();
@@ -92,10 +86,10 @@
} else {
memString = memStatesStr[memStatesStr.length - 1];
}
- mMemStatus.setText(TextUtils.expandTemplate(getText(R.string.storage_size_large),
- usedResult.value, usedResult.units));
+ mSummaryPref.setAmount(usedResult.value);
+ mSummaryPref.setUnits(usedResult.units);
float usedRatio = (float)(usedRam / (freeRam + usedRam));
- mColors.setRatios(usedRatio, 0, 1 - usedRatio);
+ mSummaryPref.setRatios(usedRatio, 0, 1 - usedRatio);
mPerformance.setSummary(memString);
mTotalMemory.setSummary(totalString);
diff --git a/src/com/android/settings/datausage/AppDataUsage.java b/src/com/android/settings/datausage/AppDataUsage.java
new file mode 100644
index 0000000..9ca066f
--- /dev/null
+++ b/src/com/android/settings/datausage/AppDataUsage.java
@@ -0,0 +1,343 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.settings.datausage;
+
+import com.android.settings.AppHeader;
+import com.android.settings.InstrumentedFragment;
+import com.android.settings.R;
+import com.android.settings.applications.AppInfoBase;
+import com.android.settingslib.AppItem;
+import com.android.settingslib.net.ChartData;
+import com.android.settingslib.net.ChartDataLoader;
+
+import android.app.LoaderManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.Loader;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
+import android.net.INetworkStatsSession;
+import android.net.NetworkPolicy;
+import android.net.NetworkStatsHistory;
+import android.net.NetworkTemplate;
+import android.net.TrafficStats;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.support.v14.preference.SwitchPreference;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceCategory;
+import android.text.format.Formatter;
+import android.util.ArraySet;
+import android.util.Log;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.Spinner;
+
+import static android.net.NetworkPolicyManager.POLICY_NONE;
+import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
+
+public class AppDataUsage extends DataUsageBase implements Preference.OnPreferenceChangeListener {
+
+ public static final String ARG_APP_ITEM = "app_item";
+ public static final String ARG_NETWORK_TEMPLATE = "network_template";
+
+ private static final String KEY_TOTAL_USAGE = "total_usage";
+ private static final String KEY_FOREGROUND_USAGE = "foreground_usage";
+ private static final String KEY_BACKGROUND_USAGE = "background_usage";
+ private static final String KEY_APP_SETTINGS = "app_settings";
+ private static final String KEY_RESTRICT_BACKGROUND = "restrict_background";
+ private static final String KEY_APP_LIST = "app_list";
+
+ private static final int LOADER_CHART_DATA = 2;
+
+ private final ArraySet<String> mPackages = new ArraySet<>();
+ private Preference mTotalUsage;
+ private Preference mForegroundUsage;
+ private Preference mBackgroundUsage;
+ private Preference mAppSettings;
+ private SwitchPreference mRestrictBackground;
+ private PreferenceCategory mAppList;
+
+ private Drawable mIcon;
+ private CharSequence mLabel;
+ private INetworkStatsSession mStatsSession;
+ private Spinner mCycleSpinner;
+ private CycleAdapter mCycleAdapter;
+
+ private long mStart;
+ private long mEnd;
+ private ChartData mChartData;
+ private NetworkTemplate mTemplate;
+ private NetworkPolicy mPolicy;
+ private AppItem mAppItem;
+ private Intent mAppSettingsIntent;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ final Bundle args = getArguments();
+
+ try {
+ mStatsSession = services.mStatsService.openSession();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+
+ mAppItem = (args != null) ? (AppItem) args.getParcelable(ARG_APP_ITEM) : null;
+ mTemplate = (args != null) ? (NetworkTemplate) args.getParcelable(ARG_NETWORK_TEMPLATE)
+ : null;
+ if (mTemplate == null) {
+ Context context = getContext();
+ mTemplate = DataUsageSummary.getDefaultTemplate(context,
+ DataUsageSummary.getDefaultSubscriptionId(context));
+ }
+ if (mAppItem == null) {
+ int uid = (args != null) ? args.getInt(AppInfoBase.ARG_PACKAGE_UID, -1)
+ : getActivity().getIntent().getIntExtra(AppInfoBase.ARG_PACKAGE_UID, -1);
+ if (uid == -1) {
+ // TODO: Log error.
+ getActivity().finish();
+ } else {
+ addUid(uid);
+ mAppItem = new AppItem(uid);
+ mAppItem.addUid(uid);
+ }
+ } else {
+ for (int i = 0; i < mAppItem.uids.size(); i++) {
+ addUid(mAppItem.uids.keyAt(i));
+ }
+ }
+ if (mPackages.size() != 0) {
+ PackageManager pm = getPackageManager();
+ try {
+ ApplicationInfo info = pm.getApplicationInfo(mPackages.valueAt(0), 0);
+ mIcon = info.loadIcon(pm);
+ mLabel = info.loadLabel(pm);
+ } catch (PackageManager.NameNotFoundException e) {
+ }
+ }
+ addPreferencesFromResource(R.xml.app_data_usage);
+
+ mTotalUsage = findPreference(KEY_TOTAL_USAGE);
+ mForegroundUsage = findPreference(KEY_FOREGROUND_USAGE);
+ mBackgroundUsage = findPreference(KEY_BACKGROUND_USAGE);
+
+ if (UserHandle.isApp(mAppItem.key)) {
+ mRestrictBackground = (SwitchPreference) findPreference(KEY_RESTRICT_BACKGROUND);
+ mRestrictBackground.setOnPreferenceChangeListener(this);
+ mAppSettings = findPreference(KEY_APP_SETTINGS);
+
+ mAppSettingsIntent = new Intent(Intent.ACTION_MANAGE_NETWORK_USAGE);
+ mAppSettingsIntent.addCategory(Intent.CATEGORY_DEFAULT);
+
+ PackageManager pm = getPackageManager();
+ boolean matchFound = false;
+ for (String packageName : mPackages) {
+ mAppSettingsIntent.setPackage(packageName);
+ if (pm.resolveActivity(mAppSettingsIntent, 0) != null) {
+ matchFound = true;
+ break;
+ }
+ }
+ if (!matchFound) {
+ removePreference(KEY_APP_SETTINGS);
+ mAppSettings = null;
+ }
+
+ if (mPackages.size() > 1) {
+ mAppList = (PreferenceCategory) findPreference(KEY_APP_LIST);
+ for (int i = 1 ; i < mPackages.size(); i++) {
+ new AppPrefLoader().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
+ mPackages.valueAt(i));
+ }
+ } else {
+ removePreference(KEY_APP_LIST);
+ }
+ } else {
+ removePreference(KEY_APP_SETTINGS);
+ removePreference(KEY_RESTRICT_BACKGROUND);
+ removePreference(KEY_APP_LIST);
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ TrafficStats.closeQuietly(mStatsSession);
+ super.onDestroy();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ mPolicy = services.mPolicyEditor.getPolicy(mTemplate);
+ getLoaderManager().restartLoader(LOADER_CHART_DATA,
+ ChartDataLoader.buildArgs(mTemplate, mAppItem), mChartDataCallbacks);
+ updatePrefs();
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ if (preference == mRestrictBackground) {
+ setAppRestrictBackground((Boolean) newValue);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onPreferenceTreeClick(Preference preference) {
+ if (preference == mAppSettings) {
+ // TODO: target towards entire UID instead of just first package
+ getActivity().startActivityAsUser(mAppSettingsIntent, new UserHandle(
+ UserHandle.getUserId(mAppItem.key)));
+ return true;
+ }
+ return super.onPreferenceTreeClick(preference);
+ }
+
+ private void updatePrefs() {
+ if (mRestrictBackground != null) {
+ mRestrictBackground.setChecked(getAppRestrictBackground());
+ }
+ }
+
+ private void addUid(int uid) {
+ String[] packages = getPackageManager().getPackagesForUid(uid);
+ if (packages != null) {
+ for (int i = 0; i < packages.length; i++) {
+ mPackages.add(packages[i]);
+ }
+ }
+ }
+
+ private void bindData() {
+ if (mChartData == null || mStart == 0) {
+ return;
+ }
+ final Context context = getContext();
+ final long now = System.currentTimeMillis();
+
+ NetworkStatsHistory.Entry entry = null;
+ entry = mChartData.detailDefault.getValues(mStart, mEnd, now, entry);
+ final long backgroundBytes = entry.rxBytes + entry.txBytes;
+ entry = mChartData.detailForeground.getValues(mStart, mEnd, now, entry);
+ final long foregroundBytes = entry.rxBytes + entry.txBytes;
+ final long totalBytes = backgroundBytes + foregroundBytes;
+
+ mTotalUsage.setSummary(Formatter.formatFileSize(context, totalBytes));
+ mForegroundUsage.setSummary(Formatter.formatFileSize(context, foregroundBytes));
+ mBackgroundUsage.setSummary(Formatter.formatFileSize(context, backgroundBytes));
+ }
+
+ private boolean getAppRestrictBackground() {
+ final int uid = mAppItem.key;
+ final int uidPolicy = services.mPolicyManager.getUidPolicy(uid);
+ return (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0;
+ }
+
+ private void setAppRestrictBackground(boolean restrictBackground) {
+ final int uid = mAppItem.key;
+ services.mPolicyManager.setUidPolicy(
+ uid, restrictBackground ? POLICY_REJECT_METERED_BACKGROUND : POLICY_NONE);
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+
+ View header = setPinnedHeaderView(R.layout.data_usage_app_header);
+ String pkg = mPackages.size() != 0 ? mPackages.valueAt(0) : null;
+ int uid = 0;
+ try {
+ uid = pkg != null ? getPackageManager().getPackageUid(pkg, 0) : 0;
+ } catch (PackageManager.NameNotFoundException e) {
+ }
+ AppHeader.setupHeaderView(getActivity(), mIcon, mLabel,
+ pkg, uid, AppHeader.includeAppInfo(this), 0, header);
+
+ mCycleSpinner = (Spinner) header.findViewById(R.id.filter_spinner);
+ mCycleAdapter = new CycleAdapter(getContext(), mCycleSpinner, mCycleListener);
+ }
+
+ @Override
+ protected int getMetricsCategory() {
+ return InstrumentedFragment.APP_DATA_USAGE;
+ }
+
+ private AdapterView.OnItemSelectedListener mCycleListener =
+ new AdapterView.OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ final CycleAdapter.CycleItem cycle =
+ (CycleAdapter.CycleItem) parent.getItemAtPosition(position);
+
+ mStart = cycle.start;
+ mEnd = cycle.end;
+ bindData();
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+ // ignored
+ }
+ };
+
+ private final LoaderManager.LoaderCallbacks<ChartData> mChartDataCallbacks =
+ new LoaderManager.LoaderCallbacks<ChartData>() {
+ @Override
+ public Loader<ChartData> onCreateLoader(int id, Bundle args) {
+ return new ChartDataLoader(getActivity(), mStatsSession, args);
+ }
+
+ @Override
+ public void onLoadFinished(Loader<ChartData> loader, ChartData data) {
+ mChartData = data;
+ mCycleAdapter.updateCycleList(mPolicy, mChartData);
+ bindData();
+ }
+
+ @Override
+ public void onLoaderReset(Loader<ChartData> loader) {
+ }
+ };
+
+ private class AppPrefLoader extends AsyncTask<String, Void, Preference> {
+ @Override
+ protected Preference doInBackground(String... params) {
+ PackageManager pm = getPackageManager();
+ String pkg = params[0];
+ try {
+ ApplicationInfo info = pm.getApplicationInfo(pkg, 0);
+ Preference preference = new Preference(getPrefContext());
+ preference.setIcon(info.loadIcon(pm));
+ preference.setTitle(info.loadLabel(pm));
+ preference.setSelectable(false);
+ return preference;
+ } catch (PackageManager.NameNotFoundException e) {
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Preference pref) {
+ if (pref != null && mAppList != null) {
+ mAppList.addPreference(pref);
+ }
+ }
+ }
+}
diff --git a/src/com/android/settings/datausage/AppDataUsagePreference.java b/src/com/android/settings/datausage/AppDataUsagePreference.java
new file mode 100644
index 0000000..04e2b6d
--- /dev/null
+++ b/src/com/android/settings/datausage/AppDataUsagePreference.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.settings.datausage;
+
+import android.content.Context;
+import android.os.AsyncTask;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceViewHolder;
+import android.text.format.Formatter;
+import android.view.View;
+import android.widget.ProgressBar;
+import com.android.settingslib.AppItem;
+import com.android.settingslib.net.UidDetail;
+import com.android.settingslib.net.UidDetailProvider;
+
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+public class AppDataUsagePreference extends Preference {
+
+ private final AppItem mItem;
+ private final int mPercent;
+
+ public AppDataUsagePreference(Context context, AppItem item, int percent,
+ UidDetailProvider provider) {
+ super(context);
+ mItem = item;
+ mPercent = percent;
+ setLayoutResource(com.android.settings.R.layout.data_usage_item);
+ setWidgetLayoutResource(com.android.settings.R.layout.widget_progress_bar);
+ if (item.restricted && item.total <= 0) {
+ setSummary(com.android.settings.R.string.data_usage_app_restricted);
+ } else {
+ setSummary(Formatter.formatFileSize(context, item.total));
+ }
+
+ // kick off async load of app details
+ UidDetailTask.bindView(provider, item, this);
+ }
+
+ @Override
+ public void onBindViewHolder(PreferenceViewHolder holder) {
+ super.onBindViewHolder(holder);
+
+ final ProgressBar progress = (ProgressBar) holder.findViewById(
+ android.R.id.progress);
+
+ if (mItem.restricted && mItem.total <= 0) {
+ progress.setVisibility(View.GONE);
+ } else {
+ progress.setVisibility(View.VISIBLE);
+ }
+ progress.setProgress(mPercent);
+ }
+
+ public AppItem getItem() {
+ return mItem;
+ }
+
+ /**
+ * Background task that loads {@link UidDetail}, binding to
+ * {@link DataUsageAdapter} row item when finished.
+ */
+ private static class UidDetailTask extends AsyncTask<Void, Void, UidDetail> {
+ private final UidDetailProvider mProvider;
+ private final AppItem mItem;
+ private final AppDataUsagePreference mTarget;
+
+ private UidDetailTask(UidDetailProvider provider, AppItem item,
+ AppDataUsagePreference target) {
+ mProvider = checkNotNull(provider);
+ mItem = checkNotNull(item);
+ mTarget = checkNotNull(target);
+ }
+
+ public static void bindView(UidDetailProvider provider, AppItem item,
+ AppDataUsagePreference target) {
+ final UidDetail cachedDetail = provider.getUidDetail(item.key, false);
+ if (cachedDetail != null) {
+ bindView(cachedDetail, target);
+ } else {
+ new UidDetailTask(provider, item, target).executeOnExecutor(
+ AsyncTask.THREAD_POOL_EXECUTOR);
+ }
+ }
+
+ private static void bindView(UidDetail detail, Preference target) {
+ if (detail != null) {
+ target.setIcon(detail.icon);
+ target.setTitle(detail.label);
+ } else {
+ target.setIcon(null);
+ target.setTitle(null);
+ }
+ }
+
+ @Override
+ protected void onPreExecute() {
+ bindView(null, mTarget);
+ }
+
+ @Override
+ protected UidDetail doInBackground(Void... params) {
+ return mProvider.getUidDetail(mItem.key, true);
+ }
+
+ @Override
+ protected void onPostExecute(UidDetail result) {
+ bindView(result, mTarget);
+ }
+ }
+}
diff --git a/src/com/android/settings/datausage/BillingCyclePreference.java b/src/com/android/settings/datausage/BillingCyclePreference.java
new file mode 100644
index 0000000..84eaabb
--- /dev/null
+++ b/src/com/android/settings/datausage/BillingCyclePreference.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.settings.datausage;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.NetworkPolicy;
+import android.net.NetworkTemplate;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.support.v7.preference.Preference;
+import android.util.AttributeSet;
+import com.android.settings.R;
+import com.android.settings.Utils;
+
+public class BillingCyclePreference extends Preference implements TemplatePreference {
+
+ private NetworkTemplate mTemplate;
+ private NetworkServices mServices;
+ private NetworkPolicy mPolicy;
+ private int mSubId;
+
+ public BillingCyclePreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public void onAttached() {
+ super.onAttached();
+ getContext().registerReceiver(mReceiver, new IntentFilter(
+ CellDataPreference.ACTION_DATA_ENABLED_CHANGED));
+ }
+
+ @Override
+ public void onDetached() {
+ getContext().unregisterReceiver(mReceiver);
+ super.onDetached();
+ }
+
+ @Override
+ public void setTemplate(NetworkTemplate template, int subId,
+ NetworkServices services) {
+ mTemplate = template;
+ mSubId = subId;
+ mServices = services;
+ mPolicy = services.mPolicyEditor.getPolicy(mTemplate);
+ setSummary(getContext().getString(R.string.billing_cycle_fragment_summary,
+ mPolicy != null ? mPolicy.cycleDay : 1));
+ setIntent(getIntent());
+ }
+
+ private void updateEnabled() {
+ try {
+ setEnabled(mPolicy != null && mServices.mNetworkService.isBandwidthControlEnabled()
+ && mServices.mTelephonyManager.getDataEnabled(mSubId)
+ && mServices.mUserManager.isAdminUser());
+ } catch (RemoteException e) {
+ setEnabled(false);
+ }
+ }
+
+ @Override
+ public Intent getIntent() {
+ Bundle args = new Bundle();
+ args.putParcelable(DataUsageList.EXTRA_NETWORK_TEMPLATE, mTemplate);
+ return Utils.onBuildStartFragmentIntent(getContext(), BillingCycleSettings.class.getName(),
+ args, null, 0, getTitle(), false);
+ }
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ updateEnabled();
+ }
+ };
+}
diff --git a/src/com/android/settings/datausage/BillingCycleSettings.java b/src/com/android/settings/datausage/BillingCycleSettings.java
new file mode 100644
index 0000000..5807992
--- /dev/null
+++ b/src/com/android/settings/datausage/BillingCycleSettings.java
@@ -0,0 +1,357 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.settings.datausage;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.Resources;
+import android.net.NetworkPolicy;
+import android.net.NetworkTemplate;
+import android.os.Bundle;
+import android.support.v14.preference.SwitchPreference;
+import android.support.v7.preference.Preference;
+import android.text.format.Formatter;
+import android.text.format.Time;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.NumberPicker;
+import com.android.settings.InstrumentedFragment;
+import com.android.settings.R;
+import com.android.settingslib.NetworkPolicyEditor;
+import com.android.settingslib.net.DataUsageController;
+
+import static android.net.NetworkPolicy.LIMIT_DISABLED;
+import static android.net.NetworkPolicy.WARNING_DISABLED;
+import static android.net.TrafficStats.GB_IN_BYTES;
+import static android.net.TrafficStats.MB_IN_BYTES;
+
+public class BillingCycleSettings extends DataUsageBase implements
+ Preference.OnPreferenceChangeListener {
+
+ private static final String TAG = "BillingCycleSettings";
+ private static final boolean LOGD = false;
+
+ private static final String TAG_CONFIRM_LIMIT = "confirmLimit";
+ private static final String TAG_CYCLE_EDITOR = "cycleEditor";
+ private static final String TAG_WARNING_EDITOR = "warningEditor";
+
+ private static final String KEY_BILLING_CYCLE = "billing_cycle";
+ private static final String KEY_DATA_WARNING = "data_warning";
+ private static final String KEY_SET_DATA_LIMIT = "set_data_limit";
+ private static final String KEY_DATA_LIMIT = "data_limit";
+
+ private NetworkTemplate mNetworkTemplate;
+ private Preference mBillingCycle;
+ private Preference mDataWarning;
+ private SwitchPreference mEnableDataLimit;
+ private Preference mDataLimit;
+ private DataUsageController mDataUsageController;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ mDataUsageController = new DataUsageController(getContext());
+
+ Bundle args = getArguments();
+ mNetworkTemplate = args.getParcelable(DataUsageList.EXTRA_NETWORK_TEMPLATE);
+
+ addPreferencesFromResource(R.xml.billing_cycle);
+ mBillingCycle = findPreference(KEY_BILLING_CYCLE);
+ mDataWarning = findPreference(KEY_DATA_WARNING);
+ mEnableDataLimit = (SwitchPreference) findPreference(KEY_SET_DATA_LIMIT);
+ mEnableDataLimit.setOnPreferenceChangeListener(this);
+ mDataLimit = findPreference(KEY_DATA_LIMIT);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ updatePrefs();
+ }
+
+ private void updatePrefs() {
+ NetworkPolicy policy = services.mPolicyEditor.getPolicy(mNetworkTemplate);
+ mBillingCycle.setSummary(getString(R.string.billing_cycle_summary, policy != null ?
+ policy.cycleDay : 1));
+ mDataWarning.setSummary(Formatter.formatFileSize(getContext(), policy != null
+ ? policy.warningBytes : DataUsageController.DEFAULT_WARNING_LEVEL));
+ if (policy != null && policy.limitBytes != LIMIT_DISABLED) {
+ mDataLimit.setSummary(Formatter.formatFileSize(getContext(), policy.limitBytes));
+ mDataLimit.setEnabled(true);
+ mEnableDataLimit.setChecked(true);
+ } else {
+ mDataLimit.setSummary(null);
+ mDataLimit.setEnabled(false);
+ mEnableDataLimit.setChecked(false);
+ }
+ }
+
+ @Override
+ public boolean onPreferenceTreeClick(Preference preference) {
+ if (preference == mBillingCycle) {
+ CycleEditorFragment.show(this);
+ return true;
+ } else if (preference == mDataWarning) {
+ BytesEditorFragment.show(this, false);
+ return true;
+ } else if (preference == mDataLimit) {
+ BytesEditorFragment.show(this, true);
+ return true;
+ }
+ return super.onPreferenceTreeClick(preference);
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ if (mEnableDataLimit == preference) {
+ boolean enabled = (Boolean) newValue;
+ if (enabled) {
+ ConfirmLimitFragment.show(this);
+ } else {
+ setPolicyLimitBytes(LIMIT_DISABLED);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ protected int getMetricsCategory() {
+ return InstrumentedFragment.BILLING_CYCLE;
+ }
+
+ private void setPolicyLimitBytes(long limitBytes) {
+ if (LOGD) Log.d(TAG, "setPolicyLimitBytes()");
+ services.mPolicyEditor.setPolicyLimitBytes(mNetworkTemplate, limitBytes);
+ updatePrefs();
+ }
+
+ /**
+ * Dialog to edit {@link NetworkPolicy#warningBytes}.
+ */
+ public static class BytesEditorFragment extends DialogFragment
+ implements DialogInterface.OnClickListener{
+ private static final String EXTRA_TEMPLATE = "template";
+ private static final String EXTRA_LIMIT = "limit";
+ private View mView;
+
+ public static void show(BillingCycleSettings parent, boolean isLimit) {
+ if (!parent.isAdded()) return;
+
+ final Bundle args = new Bundle();
+ args.putParcelable(EXTRA_TEMPLATE, parent.mNetworkTemplate);
+ args.putBoolean(EXTRA_LIMIT, isLimit);
+
+ final BytesEditorFragment dialog = new BytesEditorFragment();
+ dialog.setArguments(args);
+ dialog.setTargetFragment(parent, 0);
+ dialog.show(parent.getFragmentManager(), TAG_WARNING_EDITOR);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Context context = getActivity();
+
+
+ final LayoutInflater dialogInflater = LayoutInflater.from(context);
+ mView = dialogInflater.inflate(R.layout.data_usage_bytes_editor, null, false);
+ setupPicker((NumberPicker) mView.findViewById(R.id.bytes));
+ return new AlertDialog.Builder(context)
+ .setTitle(R.string.data_usage_warning_editor_title)
+ .setView(mView)
+ .setPositiveButton(R.string.data_usage_cycle_editor_positive, this)
+ .create();
+ }
+
+ private void setupPicker(NumberPicker bytesPicker) {
+ final BillingCycleSettings target = (BillingCycleSettings) getTargetFragment();
+ final NetworkPolicyEditor editor = target.services.mPolicyEditor;
+
+ final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE);
+ final boolean isLimit = getArguments().getBoolean(EXTRA_LIMIT);
+ final long warningBytes = editor.getPolicyWarningBytes(template);
+ final long limitBytes = editor.getPolicyLimitBytes(template);
+
+ if (isLimit) {
+ bytesPicker.setMaxValue(Integer.MAX_VALUE);
+ if (warningBytes != WARNING_DISABLED) {
+ bytesPicker.setMinValue((int) (warningBytes / MB_IN_BYTES) + 1);
+ } else {
+ bytesPicker.setMinValue(0);
+ }
+ } else {
+ bytesPicker.setMinValue(0);
+ if (limitBytes != LIMIT_DISABLED) {
+ bytesPicker.setMaxValue((int) (limitBytes / MB_IN_BYTES) - 1);
+ } else {
+ bytesPicker.setMaxValue(Integer.MAX_VALUE);
+ }
+ }
+ bytesPicker.setValue((int) ((isLimit ? limitBytes : warningBytes) / MB_IN_BYTES));
+ bytesPicker.setWrapSelectorWheel(false);
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (which != DialogInterface.BUTTON_POSITIVE) {
+ return;
+ }
+ final BillingCycleSettings target = (BillingCycleSettings) getTargetFragment();
+ final NetworkPolicyEditor editor = target.services.mPolicyEditor;
+
+ final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE);
+ final boolean isLimit = getArguments().getBoolean(EXTRA_LIMIT);
+ NumberPicker bytesPicker = (NumberPicker) mView.findViewById(R.id.bytes);
+ // clear focus to finish pending text edits
+ bytesPicker.clearFocus();
+
+ final long bytes = bytesPicker.getValue() * MB_IN_BYTES;
+ if (isLimit) {
+ editor.setPolicyLimitBytes(template, bytes);
+ } else {
+ editor.setPolicyWarningBytes(template, bytes);
+ }
+ target.updatePrefs();
+ }
+ }
+
+ /**
+ * Dialog to edit {@link NetworkPolicy#cycleDay}.
+ */
+ public static class CycleEditorFragment extends DialogFragment implements
+ DialogInterface.OnClickListener{
+ private static final String EXTRA_TEMPLATE = "template";
+ private NumberPicker mCycleDayPicker;
+
+ public static void show(BillingCycleSettings parent) {
+ if (!parent.isAdded()) return;
+
+ final Bundle args = new Bundle();
+ args.putParcelable(EXTRA_TEMPLATE, parent.mNetworkTemplate);
+
+ final CycleEditorFragment dialog = new CycleEditorFragment();
+ dialog.setArguments(args);
+ dialog.setTargetFragment(parent, 0);
+ dialog.show(parent.getFragmentManager(), TAG_CYCLE_EDITOR);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Context context = getActivity();
+ final BillingCycleSettings target = (BillingCycleSettings) getTargetFragment();
+ final NetworkPolicyEditor editor = target.services.mPolicyEditor;
+
+ final AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
+
+ final View view = dialogInflater.inflate(R.layout.data_usage_cycle_editor, null, false);
+ mCycleDayPicker = (NumberPicker) view.findViewById(R.id.cycle_day);
+
+ final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE);
+ final int cycleDay = editor.getPolicyCycleDay(template);
+
+ mCycleDayPicker.setMinValue(1);
+ mCycleDayPicker.setMaxValue(31);
+ mCycleDayPicker.setValue(cycleDay);
+ mCycleDayPicker.setWrapSelectorWheel(true);
+
+ return builder.setTitle(R.string.data_usage_cycle_editor_title)
+ .setView(view)
+ .setPositiveButton(R.string.data_usage_cycle_editor_positive, this)
+ .create();
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE);
+ final BillingCycleSettings target = (BillingCycleSettings) getTargetFragment();
+ final NetworkPolicyEditor editor = target.services.mPolicyEditor;
+
+ // clear focus to finish pending text edits
+ mCycleDayPicker.clearFocus();
+
+ final int cycleDay = mCycleDayPicker.getValue();
+ final String cycleTimezone = new Time().timezone;
+ editor.setPolicyCycleDay(template, cycleDay, cycleTimezone);
+ target.updatePrefs();
+ }
+ }
+
+ /**
+ * Dialog to request user confirmation before setting
+ * {@link NetworkPolicy#limitBytes}.
+ */
+ public static class ConfirmLimitFragment extends DialogFragment implements
+ DialogInterface.OnClickListener{
+ private static final String EXTRA_MESSAGE = "message";
+ private static final String EXTRA_LIMIT_BYTES = "limitBytes";
+ public static final float FLOAT = 1.2f;
+
+ public static void show(BillingCycleSettings parent) {
+ if (!parent.isAdded()) return;
+
+ final NetworkPolicy policy = parent.services.mPolicyEditor
+ .getPolicy(parent.mNetworkTemplate);
+ if (policy == null) return;
+
+ final Resources res = parent.getResources();
+ final CharSequence message;
+ final long minLimitBytes = (long) (policy.warningBytes * FLOAT);
+ final long limitBytes;
+
+ // TODO: customize default limits based on network template
+ message = res.getString(R.string.data_usage_limit_dialog_mobile);
+ limitBytes = Math.max(5 * GB_IN_BYTES, minLimitBytes);
+
+ final Bundle args = new Bundle();
+ args.putCharSequence(EXTRA_MESSAGE, message);
+ args.putLong(EXTRA_LIMIT_BYTES, limitBytes);
+
+ final ConfirmLimitFragment dialog = new ConfirmLimitFragment();
+ dialog.setArguments(args);
+ dialog.setTargetFragment(parent, 0);
+ dialog.show(parent.getFragmentManager(), TAG_CONFIRM_LIMIT);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Context context = getActivity();
+
+ final CharSequence message = getArguments().getCharSequence(EXTRA_MESSAGE);
+
+ return new AlertDialog.Builder(context)
+ .setTitle(R.string.data_usage_limit_dialog_title)
+ .setMessage(message)
+ .setPositiveButton(android.R.string.ok, this)
+ .setNegativeButton(android.R.string.cancel, null)
+ .create();
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (which != DialogInterface.BUTTON_POSITIVE) return;
+ final long limitBytes = getArguments().getLong(EXTRA_LIMIT_BYTES);
+ final BillingCycleSettings target = (BillingCycleSettings) getTargetFragment();
+ if (target != null) {
+ target.setPolicyLimitBytes(limitBytes);
+ }
+ }
+ }
+}
diff --git a/src/com/android/settings/datausage/CellDataPreference.java b/src/com/android/settings/datausage/CellDataPreference.java
new file mode 100644
index 0000000..0a91be2
--- /dev/null
+++ b/src/com/android/settings/datausage/CellDataPreference.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.settings.datausage;
+
+import android.app.AlertDialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.NetworkTemplate;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.v7.preference.PreferenceViewHolder;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.widget.Checkable;
+import com.android.internal.logging.MetricsLogger;
+import com.android.settings.CustomDialogPreference;
+import com.android.settings.R;
+import com.android.settings.Utils;
+
+import java.util.List;
+
+public class CellDataPreference extends CustomDialogPreference implements TemplatePreference {
+
+ // TODO: Get Telephony to broadcast when this changes, and remove this.
+ static final String ACTION_DATA_ENABLED_CHANGED =
+ "com.android.settings.action.DATA_ENABLED_CHANGED";
+
+ private static final String TAG = "CellDataPreference";
+
+ public int mSubId;
+ public boolean mChecked;
+ public boolean mMultiSimDialog;
+ private TelephonyManager mTelephonyManager;
+ private SubscriptionManager mSubscriptionManager;
+
+ public CellDataPreference(Context context, AttributeSet attrs) {
+ super(context, attrs, android.R.attr.switchPreferenceStyle);
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Parcelable s) {
+ CellDataState state = (CellDataState) s;
+ super.onRestoreInstanceState(state.getSuperState());
+ mTelephonyManager = TelephonyManager.from(getContext());
+ mChecked = state.mChecked;
+ mMultiSimDialog = state.mMultiSimDialog;
+ mSubId = state.mSubId;
+ setKey(getKey() + mSubId);
+ notifyChanged();
+ }
+
+ @Override
+ protected Parcelable onSaveInstanceState() {
+ CellDataState state = new CellDataState(super.onSaveInstanceState());
+ state.mChecked = mChecked;
+ state.mMultiSimDialog = mMultiSimDialog;
+ state.mSubId = mSubId;
+ return state;
+ }
+
+ @Override
+ public void onAttached() {
+ super.onAttached();
+ getContext().registerReceiver(mReceiver, new IntentFilter(ACTION_DATA_ENABLED_CHANGED));
+ }
+
+ @Override
+ public void onDetached() {
+ getContext().unregisterReceiver(mReceiver);
+ super.onDetached();
+ }
+
+ @Override
+ public void setTemplate(NetworkTemplate template, int subId, NetworkServices services) {
+ if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ throw new IllegalArgumentException("CellDataPreference needs a SubscriptionInfo");
+ }
+ mTelephonyManager = TelephonyManager.from(getContext());
+ if (mSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ mSubId = subId;
+ setKey(getKey() + subId);
+ }
+ updateChecked();
+ }
+
+ private void updateChecked() {
+ setChecked(mTelephonyManager.getDataEnabled(mSubId));
+ }
+
+ @Override
+ protected void performClick(View view) {
+ super.performClick(view);
+ MetricsLogger.action(getContext(), MetricsLogger.ACTION_CELL_DATA_TOGGLE, !mChecked);
+ if (mChecked) {
+ // disabling data; show confirmation dialog which eventually
+ // calls setMobileDataEnabled() once user confirms.
+ mMultiSimDialog = false;
+ super.performClick(view);
+ } else {
+ // If we are showing the Sim Card tile then we are a Multi-Sim device.
+ if (Utils.showSimCardTile(getContext())) {
+ mMultiSimDialog = true;
+ super.performClick(view);
+ } else {
+ setMobileDataEnabled(true);
+ }
+ }
+ }
+
+ private void setMobileDataEnabled(boolean enabled) {
+ if (DataUsageSummary.LOGD) Log.d(TAG, "setMobileDataEnabled()");
+ mTelephonyManager.setDataEnabled(mSubId, enabled);
+ setChecked(enabled);
+ getContext().sendBroadcast(new Intent(ACTION_DATA_ENABLED_CHANGED));
+ }
+
+ private void setChecked(boolean checked) {
+ if (mChecked == checked) return;
+ mChecked = checked;
+ notifyChanged();
+ }
+
+ @Override
+ public void onBindViewHolder(PreferenceViewHolder holder) {
+ super.onBindViewHolder(holder);
+ View switchView = holder.findViewById(android.R.id.switch_widget);
+ switchView.setClickable(false);
+ ((Checkable) switchView).setChecked(mChecked);
+ }
+
+ @Override
+ protected void onPrepareDialogBuilder(AlertDialog.Builder builder,
+ DialogInterface.OnClickListener listener) {
+ if (mMultiSimDialog) {
+ showMultiSimDialog(builder, listener);
+ } else {
+ showDisableDialog(builder, listener);
+ }
+ }
+
+ private void showDisableDialog(AlertDialog.Builder builder,
+ DialogInterface.OnClickListener listener) {
+ builder.setTitle(null)
+ .setMessage(R.string.data_usage_disable_mobile)
+ .setPositiveButton(android.R.string.ok, listener)
+ .setNegativeButton(android.R.string.cancel, null);
+ }
+
+ private void showMultiSimDialog(AlertDialog.Builder builder,
+ DialogInterface.OnClickListener listener) {
+ mSubscriptionManager = SubscriptionManager.from(getContext());
+ final SubscriptionInfo currentSir = mSubscriptionManager.getActiveSubscriptionInfo(mSubId);
+
+ final SubscriptionInfo nextSir = mSubscriptionManager.getDefaultDataSubscriptionInfo();
+
+ // If the device is single SIM or is enabling data on the active data SIM then forgo
+ // the pop-up.
+ if (!Utils.showSimCardTile(getContext()) ||
+ (nextSir != null && currentSir != null &&
+ currentSir.getSubscriptionId() == nextSir.getSubscriptionId())) {
+ setMobileDataEnabled(true);
+ if (nextSir != null && currentSir != null &&
+ currentSir.getSubscriptionId() == nextSir.getSubscriptionId()) {
+ disableDataForOtherSubscriptions(currentSir);
+ }
+ return;
+ }
+
+ final String previousName = (nextSir == null)
+ ? getContext().getResources().getString(R.string.sim_selection_required_pref)
+ : nextSir.getDisplayName().toString();
+
+ builder.setTitle(R.string.sim_change_data_title);
+ builder.setMessage(getContext().getString(R.string.sim_change_data_message,
+ currentSir.getDisplayName(), previousName));
+
+ builder.setPositiveButton(R.string.okay, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ mSubscriptionManager.setDefaultDataSubId(currentSir.getSubscriptionId());
+ setMobileDataEnabled(true);
+ disableDataForOtherSubscriptions(currentSir);
+ }
+ });
+ builder.setNegativeButton(R.string.cancel, null);
+
+ builder.create().show();
+ }
+
+ private void disableDataForOtherSubscriptions(SubscriptionInfo currentSir) {
+ List<SubscriptionInfo> subInfoList = mSubscriptionManager.getActiveSubscriptionInfoList();
+ if (subInfoList != null) {
+ for (SubscriptionInfo subInfo : subInfoList) {
+ if (subInfo.getSubscriptionId() != currentSir.getSubscriptionId()) {
+ mTelephonyManager.setDataEnabled(subInfo.getSubscriptionId(), false);
+ }
+ }
+ }
+ getContext().sendBroadcast(new Intent(ACTION_DATA_ENABLED_CHANGED));
+ }
+
+ @Override
+ protected void onClick(DialogInterface dialog, int which) {
+ if (which != DialogInterface.BUTTON_POSITIVE) {
+ return;
+ }
+ if (mMultiSimDialog) {
+ } else {
+ // TODO: extend to modify policy enabled flag.
+ setMobileDataEnabled(false);
+ }
+ }
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ updateChecked();
+ }
+ };
+
+ public static class CellDataState extends BaseSavedState {
+ public int mSubId;
+ public boolean mChecked;
+ public boolean mMultiSimDialog;
+
+ public CellDataState(Parcelable base) {
+ super(base);
+ }
+
+ public CellDataState(Parcel source) {
+ super(source);
+ mChecked = source.readByte() != 0;
+ mMultiSimDialog = source.readByte() != 0;
+ mSubId = source.readInt();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeByte((byte) (mChecked ? 1 : 0));
+ dest.writeByte((byte) (mMultiSimDialog ? 1 : 0));
+ dest.writeInt(mSubId);
+ }
+
+ public static final Creator<CellDataState> CREATOR = new Creator<CellDataState>() {
+ @Override
+ public CellDataState createFromParcel(Parcel source) {
+ return new CellDataState(source);
+ }
+
+ @Override
+ public CellDataState[] newArray(int size) {
+ return new CellDataState[size];
+ }
+ };
+ }
+}
diff --git a/src/com/android/settings/datausage/ChartDataUsagePreference.java b/src/com/android/settings/datausage/ChartDataUsagePreference.java
new file mode 100644
index 0000000..f328cf6
--- /dev/null
+++ b/src/com/android/settings/datausage/ChartDataUsagePreference.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.settings.datausage;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.net.NetworkPolicy;
+import android.net.NetworkStatsHistory;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceViewHolder;
+import android.util.AttributeSet;
+import com.android.settings.R;
+import com.android.settings.widget.ChartDataUsageView;
+import com.android.settings.widget.ChartNetworkSeriesView;
+
+public class ChartDataUsagePreference extends Preference {
+
+ private NetworkPolicy mPolicy;
+ private long mStart;
+ private long mEnd;
+ private NetworkStatsHistory mNetwork;
+ private int mSecondaryColor;
+ private int mSeriesColor;
+
+ public ChartDataUsagePreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ setLayoutResource(R.layout.data_usage_chart);
+ }
+
+ @Override
+ public void onBindViewHolder(PreferenceViewHolder holder) {
+ super.onBindViewHolder(holder);
+ ChartDataUsageView chart = (ChartDataUsageView) holder.itemView;
+ chart.setVisibleRange(mStart, mEnd);
+ chart.bindNetworkPolicy(mPolicy);
+ chart.bindNetworkStats(mNetwork);
+ ChartNetworkSeriesView series = (ChartNetworkSeriesView) holder.findViewById(R.id.series);
+ series.setChartColor(Color.BLACK, mSeriesColor, mSecondaryColor);
+ }
+
+ public void bindNetworkPolicy(NetworkPolicy policy) {
+ mPolicy = policy;
+ notifyChanged();
+ }
+
+ public void setVisibleRange(long start, long end) {
+ mStart = start;
+ mEnd = end;
+ notifyChanged();
+ }
+
+ public long getInspectStart() {
+ return mStart;
+ }
+
+ public long getInspectEnd() {
+ return mEnd;
+ }
+
+ public void bindNetworkStats(NetworkStatsHistory network) {
+ mNetwork = network;
+ notifyChanged();
+ }
+
+ public void setColors(int seriesColor, int secondaryColor) {
+ mSeriesColor = seriesColor;
+ mSecondaryColor = secondaryColor;
+ notifyChanged();
+ }
+}
diff --git a/src/com/android/settings/datausage/CycleAdapter.java b/src/com/android/settings/datausage/CycleAdapter.java
new file mode 100644
index 0000000..682cc8a
--- /dev/null
+++ b/src/com/android/settings/datausage/CycleAdapter.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.android.settings.datausage;
+
+import com.android.settings.Utils;
+import com.android.settingslib.net.ChartData;
+
+import android.content.Context;
+import android.net.NetworkPolicy;
+import android.net.NetworkStatsHistory;
+import android.text.format.DateUtils;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Spinner;
+
+import libcore.util.Objects;
+
+import static android.net.NetworkPolicyManager.computeLastCycleBoundary;
+import static android.net.NetworkPolicyManager.computeNextCycleBoundary;
+
+public class CycleAdapter extends ArrayAdapter<CycleAdapter.CycleItem> {
+
+ private final Spinner mSpinner;
+ private final AdapterView.OnItemSelectedListener mListener;
+
+ public CycleAdapter(Context context, Spinner spinner,
+ AdapterView.OnItemSelectedListener listener) {
+ super(context, com.android.settings.R.layout.filter_spinner_item);
+ setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ mSpinner = spinner;
+ mListener = listener;
+ mSpinner.setAdapter(this);
+ mSpinner.setOnItemSelectedListener(mListener);
+ }
+
+ /**
+ * Find position of {@link CycleItem} in this adapter which is nearest
+ * the given {@link CycleItem}.
+ */
+ public int findNearestPosition(CycleItem target) {
+ if (target != null) {
+ final int count = getCount();
+ for (int i = count - 1; i >= 0; i--) {
+ final CycleItem item = getItem(i);
+ if (item.compareTo(target) >= 0) {
+ return i;
+ }
+ }
+ }
+ return 0;
+ }
+
+ /**
+ * Rebuild list based on {@link NetworkPolicy#cycleDay}
+ * and available {@link NetworkStatsHistory} data. Always selects the newest
+ * item, updating the inspection range on chartData.
+ */
+ public boolean updateCycleList(NetworkPolicy policy, ChartData chartData) {
+ // stash away currently selected cycle to try restoring below
+ final CycleAdapter.CycleItem previousItem = (CycleAdapter.CycleItem)
+ mSpinner.getSelectedItem();
+ clear();
+
+ final Context context = mSpinner.getContext();
+ NetworkStatsHistory.Entry entry = null;
+
+ long historyStart = Long.MAX_VALUE;
+ long historyEnd = Long.MIN_VALUE;
+ if (chartData != null) {
+ historyStart = chartData.network.getStart();
+ historyEnd = chartData.network.getEnd();
+ }
+
+ final long now = System.currentTimeMillis();
+ if (historyStart == Long.MAX_VALUE) historyStart = now;
+ if (historyEnd == Long.MIN_VALUE) historyEnd = now + 1;
+
+ boolean hasCycles = false;
+ if (policy != null) {
+ // find the next cycle boundary
+ long cycleEnd = computeNextCycleBoundary(historyEnd, policy);
+
+ // walk backwards, generating all valid cycle ranges
+ while (cycleEnd > historyStart) {
+ final long cycleStart = computeLastCycleBoundary(cycleEnd, policy);
+
+ final boolean includeCycle;
+ if (chartData != null) {
+ entry = chartData.network.getValues(cycleStart, cycleEnd, entry);
+ includeCycle = (entry.rxBytes + entry.txBytes) > 0;
+ } else {
+ includeCycle = true;
+ }
+
+ if (includeCycle) {
+ add(new CycleAdapter.CycleItem(context, cycleStart, cycleEnd));
+ hasCycles = true;
+ }
+ cycleEnd = cycleStart;
+ }
+ }
+
+ if (!hasCycles) {
+ // no policy defined cycles; show entry for each four-week period
+ long cycleEnd = historyEnd;
+ while (cycleEnd > historyStart) {
+ final long cycleStart = cycleEnd - (DateUtils.WEEK_IN_MILLIS * 4);
+
+ final boolean includeCycle;
+ if (chartData != null) {
+ entry = chartData.network.getValues(cycleStart, cycleEnd, entry);
+ includeCycle = (entry.rxBytes + entry.txBytes) > 0;
+ } else {
+ includeCycle = true;
+ }
+
+ if (includeCycle) {
+ add(new CycleAdapter.CycleItem(context, cycleStart, cycleEnd));
+ }
+ cycleEnd = cycleStart;
+ }
+ }
+
+ // force pick the current cycle (first item)
+ if (getCount() > 0) {
+ final int position = findNearestPosition(previousItem);
+ mSpinner.setSelection(position);
+
+ // only force-update cycle when changed; skipping preserves any
+ // user-defined inspection region.
+ final CycleAdapter.CycleItem selectedItem = getItem(position);
+ if (!Objects.equal(selectedItem, previousItem)) {
+ mListener.onItemSelected(mSpinner, null, position, 0);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * List item that reflects a specific data usage cycle.
+ */
+ public static class CycleItem implements Comparable<CycleItem> {
+ public CharSequence label;
+ public long start;
+ public long end;
+
+ public CycleItem(CharSequence label) {
+ this.label = label;
+ }
+
+ public CycleItem(Context context, long start, long end) {
+ this.label = Utils.formatDateRange(context, start, end);
+ this.start = start;
+ this.end = end;
+ }
+
+ @Override
+ public String toString() {
+ return label.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof CycleItem) {
+ final CycleItem another = (CycleItem) o;
+ return start == another.start && end == another.end;
+ }
+ return false;
+ }
+
+ @Override
+ public int compareTo(CycleItem another) {
+ return Long.compare(start, another.start);
+ }
+ }
+}
diff --git a/src/com/android/settings/datausage/DataUsageBase.java b/src/com/android/settings/datausage/DataUsageBase.java
new file mode 100644
index 0000000..ef565a1
--- /dev/null
+++ b/src/com/android/settings/datausage/DataUsageBase.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.settings.datausage;
+
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settingslib.NetworkPolicyEditor;
+
+import android.content.Context;
+import android.net.INetworkStatsService;
+import android.net.NetworkPolicy;
+import android.net.NetworkPolicyManager;
+import android.os.Bundle;
+import android.os.INetworkManagementService;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+public abstract class DataUsageBase extends SettingsPreferenceFragment {
+
+ private static final String TAG = "DataUsageBase";
+
+ protected final TemplatePreference.NetworkServices services =
+ new TemplatePreference.NetworkServices();
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ final Context context = getActivity();
+
+ services.mNetworkService = INetworkManagementService.Stub.asInterface(
+ ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
+ services.mStatsService = INetworkStatsService.Stub.asInterface(
+ ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
+ services.mPolicyManager = NetworkPolicyManager.from(context);
+
+ services.mPolicyEditor = new NetworkPolicyEditor(services.mPolicyManager);
+
+ services.mTelephonyManager = TelephonyManager.from(context);
+ services.mSubscriptionManager = SubscriptionManager.from(context);
+ services.mUserManager = UserManager.get(context);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ services.mPolicyEditor.read();
+ }
+
+ protected boolean isAdmin() {
+ return services.mUserManager.isAdminUser();
+ }
+
+ protected boolean isMobileDataAvailable(int subId) {
+ return services.mSubscriptionManager.getActiveSubscriptionInfo(subId) != null;
+ }
+
+ protected boolean isNetworkPolicyModifiable(NetworkPolicy policy, int subId) {
+ return policy != null && isBandwidthControlEnabled() && services.mUserManager.isAdminUser()
+ && isDataEnabled(subId);
+ }
+
+ private boolean isDataEnabled(int subId) {
+ if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ return true;
+ }
+ return services.mTelephonyManager.getDataEnabled(subId);
+ }
+
+ protected boolean isBandwidthControlEnabled() {
+ try {
+ return services.mNetworkService.isBandwidthControlEnabled();
+ } catch (RemoteException e) {
+ Log.w(TAG, "problem talking with INetworkManagementService: " + e);
+ return false;
+ }
+ }
+}
diff --git a/src/com/android/settings/datausage/DataUsageList.java b/src/com/android/settings/datausage/DataUsageList.java
new file mode 100644
index 0000000..4aa52ba
--- /dev/null
+++ b/src/com/android/settings/datausage/DataUsageList.java
@@ -0,0 +1,550 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.settings.datausage;
+
+import android.app.ActivityManager;
+import android.app.LoaderManager.LoaderCallbacks;
+import android.content.Context;
+import android.content.Loader;
+import android.content.pm.UserInfo;
+import android.graphics.Color;
+import android.net.ConnectivityManager;
+import android.net.INetworkStatsSession;
+import android.net.NetworkPolicy;
+import android.net.NetworkStats;
+import android.net.NetworkStatsHistory;
+import android.net.NetworkTemplate;
+import android.net.TrafficStats;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceGroup;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.text.format.DateUtils;
+import android.text.format.Formatter;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.Spinner;
+import com.android.settings.InstrumentedFragment;
+import com.android.settings.R;
+import com.android.settingslib.AppItem;
+import com.android.settingslib.net.ChartData;
+import com.android.settingslib.net.ChartDataLoader;
+import com.android.settingslib.net.SummaryForAllUidLoader;
+import com.android.settingslib.net.UidDetailProvider;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
+import static android.net.TrafficStats.UID_REMOVED;
+import static android.net.TrafficStats.UID_TETHERING;
+import static android.telephony.TelephonyManager.SIM_STATE_READY;
+import static com.android.settings.datausage.DataUsageSummary.TEST_RADIOS;
+import static com.android.settings.datausage.DataUsageSummary.TEST_RADIOS_PROP;
+
+/**
+ * Panel showing data usage history across various networks, including options
+ * to inspect based on usage cycle and control through {@link NetworkPolicy}.
+ */
+public class DataUsageList extends DataUsageBase {
+ private static final String TAG = "DataUsage";
+ private static final boolean LOGD = false;
+
+ private static final String KEY_USAGE_AMOUNT = "usage_amount";
+ private static final String KEY_CHART_DATA = "chart_data";
+ private static final String KEY_APPS_GROUP = "apps_group";
+
+ private static final int LOADER_CHART_DATA = 2;
+ private static final int LOADER_SUMMARY = 3;
+ public static final String EXTRA_SUB_ID = "sub_id";
+ public static final String EXTRA_NETWORK_TEMPLATE = "network_template";
+
+ private INetworkStatsSession mStatsSession;
+
+ private ChartDataUsagePreference mChart;
+
+ private NetworkTemplate mTemplate;
+ private int mSubId;
+ private ChartData mChartData;
+
+ /** Flag used to ignore listeners during binding. */
+ private boolean mBinding;
+
+ private UidDetailProvider mUidDetailProvider;
+
+ /**
+ * Local cache of data enabled for subId, used to work around delays.
+ */
+ private final Map<String, Boolean> mMobileDataEnabled = new HashMap<String, Boolean>();
+ private CycleAdapter mCycleAdapter;
+ private Spinner mCycleSpinner;
+ private Preference mUsageAmount;
+ private PreferenceGroup mApps;
+ private View mHeader;
+
+ @Override
+ protected int getMetricsCategory() {
+ return InstrumentedFragment.DATA_USAGE_LIST;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ final Context context = getActivity();
+
+ if (!isBandwidthControlEnabled()) {
+ Log.w(TAG, "No bandwidth control; leaving");
+ getActivity().finish();
+ }
+
+ try {
+ mStatsSession = services.mStatsService.openSession();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+
+ mUidDetailProvider = new UidDetailProvider(context);
+
+ addPreferencesFromResource(R.xml.data_usage_list);
+ mUsageAmount = findPreference(KEY_USAGE_AMOUNT);
+ mChart = (ChartDataUsagePreference) findPreference(KEY_CHART_DATA);
+ mApps = (PreferenceGroup) findPreference(KEY_APPS_GROUP);
+
+ final Bundle args = getArguments();
+ mSubId = args.getInt(EXTRA_SUB_ID, SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+ mTemplate = args.getParcelable(EXTRA_NETWORK_TEMPLATE);
+ }
+
+ @Override
+ public void onViewCreated(View v, Bundle savedInstanceState) {
+ super.onViewCreated(v, savedInstanceState);
+
+ mHeader = setPinnedHeaderView(R.layout.apps_filter_spinner);
+ mCycleSpinner = (Spinner) mHeader.findViewById(R.id.filter_spinner);
+ mCycleAdapter = new CycleAdapter(getContext(), mCycleSpinner, mCycleListener);
+ setLoading(true, false);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ updateBody();
+
+ // kick off background task to update stats
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ try {
+ // wait a few seconds before kicking off
+ Thread.sleep(2 * DateUtils.SECOND_IN_MILLIS);
+ services.mStatsService.forceUpdate();
+ } catch (InterruptedException e) {
+ } catch (RemoteException e) {
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void result) {
+ if (isAdded()) {
+ updateBody();
+ }
+ }
+ }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ }
+
+ @Override
+ public void onDestroy() {
+ mUidDetailProvider.clearCache();
+ mUidDetailProvider = null;
+
+ TrafficStats.closeQuietly(mStatsSession);
+
+ super.onDestroy();
+ }
+
+ /**
+ * Update body content based on current tab. Loads
+ * {@link NetworkStatsHistory} and {@link NetworkPolicy} from system, and
+ * binds them to visible controls.
+ */
+ private void updateBody() {
+ mBinding = true;
+ if (!isAdded()) return;
+
+ final Context context = getActivity();
+
+ // kick off loader for network history
+ // TODO: consider chaining two loaders together instead of reloading
+ // network history when showing app detail.
+ getLoaderManager().restartLoader(LOADER_CHART_DATA,
+ ChartDataLoader.buildArgs(mTemplate, null), mChartDataCallbacks);
+
+ // detail mode can change visible menus, invalidate
+ getActivity().invalidateOptionsMenu();
+
+ mBinding = false;
+
+ int seriesColor = context.getColor(R.color.sim_noitification);
+ if (mSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID){
+ final SubscriptionInfo sir = services.mSubscriptionManager
+ .getActiveSubscriptionInfo(mSubId);
+
+ if (sir != null) {
+ seriesColor = sir.getIconTint();
+ }
+ }
+
+ final int secondaryColor = Color.argb(127, Color.red(seriesColor), Color.green(seriesColor),
+ Color.blue(seriesColor));
+ mChart.setColors(seriesColor, secondaryColor);
+ }
+
+ /**
+ * Update chart sweeps and cycle list to reflect {@link NetworkPolicy} for
+ * current {@link #mTemplate}.
+ */
+ private void updatePolicy(boolean refreshCycle) {
+ final NetworkPolicy policy = services.mPolicyEditor.getPolicy(mTemplate);
+ //SUB SELECT
+ if (isNetworkPolicyModifiable(policy, mSubId) && isMobileDataAvailable(mSubId)) {
+ mChart.bindNetworkPolicy(policy);
+ mHeader.findViewById(R.id.filter_settings).setVisibility(View.VISIBLE);
+ mHeader.findViewById(R.id.filter_settings).setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Bundle args = new Bundle();
+ args.putParcelable(DataUsageList.EXTRA_NETWORK_TEMPLATE, mTemplate);
+ startFragment(DataUsageList.this, BillingCycleSettings.class.getName(),
+ R.string.billing_cycle, 0, args);
+ }
+ });
+ } else {
+ // controls are disabled; don't bind warning/limit sweeps
+ mChart.bindNetworkPolicy(null);
+ mHeader.findViewById(R.id.filter_settings).setVisibility(View.GONE);
+ }
+
+ if (refreshCycle) {
+ // generate cycle list based on policy and available history
+ if (mCycleAdapter.updateCycleList(policy, mChartData)) {
+ updateDetailData();
+ }
+ }
+ }
+
+ /**
+ * Update details based on {@link #mChart} inspection range depending on
+ * current mode. Updates {@link #mAdapter} with sorted list
+ * of applications data usage.
+ */
+ private void updateDetailData() {
+ if (LOGD) Log.d(TAG, "updateDetailData()");
+
+ final long start = mChart.getInspectStart();
+ final long end = mChart.getInspectEnd();
+ final long now = System.currentTimeMillis();
+
+ final Context context = getActivity();
+
+ NetworkStatsHistory.Entry entry = null;
+ if (mChartData != null) {
+ entry = mChartData.network.getValues(start, end, now, null);
+ }
+
+ // kick off loader for detailed stats
+ getLoaderManager().restartLoader(LOADER_SUMMARY,
+ SummaryForAllUidLoader.buildArgs(mTemplate, start, end), mSummaryCallbacks);
+
+ final long totalBytes = entry != null ? entry.rxBytes + entry.txBytes : 0;
+ final String totalPhrase = Formatter.formatFileSize(context, totalBytes);
+ mUsageAmount.setTitle(getString(R.string.data_used_template, totalPhrase));
+ }
+
+ /**
+ * Bind the given {@link NetworkStats}, or {@code null} to clear list.
+ */
+ public void bindStats(NetworkStats stats, int[] restrictedUids) {
+ ArrayList<AppItem> items = new ArrayList<>();
+ long largest = 0;
+
+ final int currentUserId = ActivityManager.getCurrentUser();
+ UserManager userManager = UserManager.get(getContext());
+ final List<UserHandle> profiles = userManager.getUserProfiles();
+ final SparseArray<AppItem> knownItems = new SparseArray<AppItem>();
+
+ NetworkStats.Entry entry = null;
+ final int size = stats != null ? stats.size() : 0;
+ for (int i = 0; i < size; i++) {
+ entry = stats.getValues(i, entry);
+
+ // Decide how to collapse items together
+ final int uid = entry.uid;
+
+ final int collapseKey;
+ final int category;
+ final int userId = UserHandle.getUserId(uid);
+ if (UserHandle.isApp(uid)) {
+ if (profiles.contains(new UserHandle(userId))) {
+ if (userId != currentUserId) {
+ // Add to a managed user item.
+ final int managedKey = UidDetailProvider.buildKeyForUser(userId);
+ largest = accumulate(managedKey, knownItems, entry, AppItem.CATEGORY_USER,
+ items, largest);
+ }
+ // Add to app item.
+ collapseKey = uid;
+ category = AppItem.CATEGORY_APP;
+ } else {
+ // If it is a removed user add it to the removed users' key
+ final UserInfo info = userManager.getUserInfo(userId);
+ if (info == null) {
+ collapseKey = UID_REMOVED;
+ category = AppItem.CATEGORY_APP;
+ } else {
+ // Add to other user item.
+ collapseKey = UidDetailProvider.buildKeyForUser(userId);
+ category = AppItem.CATEGORY_USER;
+ }
+ }
+ } else if (uid == UID_REMOVED || uid == UID_TETHERING) {
+ collapseKey = uid;
+ category = AppItem.CATEGORY_APP;
+ } else {
+ collapseKey = android.os.Process.SYSTEM_UID;
+ category = AppItem.CATEGORY_APP;
+ }
+ largest = accumulate(collapseKey, knownItems, entry, category, items, largest);
+ }
+
+ final int restrictedUidsMax = restrictedUids.length;
+ for (int i = 0; i < restrictedUidsMax; ++i) {
+ final int uid = restrictedUids[i];
+ // Only splice in restricted state for current user or managed users
+ if (!profiles.contains(new UserHandle(UserHandle.getUserId(uid)))) {
+ continue;
+ }
+
+ AppItem item = knownItems.get(uid);
+ if (item == null) {
+ item = new AppItem(uid);
+ item.total = -1;
+ items.add(item);
+ knownItems.put(item.key, item);
+ }
+ item.restricted = true;
+ }
+
+ Collections.sort(items);
+ mApps.removeAll();
+ for (int i = 0; i < items.size(); i++) {
+ final int percentTotal = largest != 0 ? (int) (items.get(i).total * 100 / largest) : 0;
+ AppDataUsagePreference preference = new AppDataUsagePreference(getContext(),
+ items.get(i), percentTotal, mUidDetailProvider);
+ preference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ AppDataUsagePreference pref = (AppDataUsagePreference) preference;
+ AppItem item = pref.getItem();
+ startAppDataUsage(item);
+ return true;
+ }
+ });
+ mApps.addPreference(preference);
+ }
+ }
+
+ private void startAppDataUsage(AppItem item) {
+ Bundle args = new Bundle();
+ args.putParcelable(AppDataUsage.ARG_APP_ITEM, item);
+ args.putParcelable(AppDataUsage.ARG_NETWORK_TEMPLATE, mTemplate);
+ startFragment(this, AppDataUsage.class.getName(), R.string.app_data_usage, 0, args);
+ }
+
+ /**
+ * Accumulate data usage of a network stats entry for the item mapped by the collapse key.
+ * Creates the item if needed.
+ * @param collapseKey the collapse key used to map the item.
+ * @param knownItems collection of known (already existing) items.
+ * @param entry the network stats entry to extract data usage from.
+ * @param itemCategory the item is categorized on the list view by this category. Must be
+ */
+ private static long accumulate(int collapseKey, final SparseArray<AppItem> knownItems,
+ NetworkStats.Entry entry, int itemCategory, ArrayList<AppItem> items, long largest) {
+ final int uid = entry.uid;
+ AppItem item = knownItems.get(collapseKey);
+ if (item == null) {
+ item = new AppItem(collapseKey);
+ item.category = itemCategory;
+ items.add(item);
+ knownItems.put(item.key, item);
+ }
+ item.addUid(uid);
+ item.total += entry.rxBytes + entry.txBytes;
+ return Math.max(largest, item.total);
+ }
+
+ /**
+ * Test if device has a mobile data radio with SIM in ready state.
+ */
+ public static boolean hasReadyMobileRadio(Context context) {
+ if (TEST_RADIOS) {
+ return SystemProperties.get(TEST_RADIOS_PROP).contains("mobile");
+ }
+
+ final ConnectivityManager conn = ConnectivityManager.from(context);
+ final TelephonyManager tele = TelephonyManager.from(context);
+
+ final List<SubscriptionInfo> subInfoList =
+ SubscriptionManager.from(context).getActiveSubscriptionInfoList();
+ // No activated Subscriptions
+ if (subInfoList == null) {
+ if (LOGD) Log.d(TAG, "hasReadyMobileRadio: subInfoList=null");
+ return false;
+ }
+ // require both supported network and ready SIM
+ boolean isReady = true;
+ for (SubscriptionInfo subInfo : subInfoList) {
+ isReady = isReady & tele.getSimState(subInfo.getSimSlotIndex()) == SIM_STATE_READY;
+ if (LOGD) Log.d(TAG, "hasReadyMobileRadio: subInfo=" + subInfo);
+ }
+ boolean retVal = conn.isNetworkSupported(TYPE_MOBILE) && isReady;
+ if (LOGD) {
+ Log.d(TAG, "hasReadyMobileRadio:"
+ + " conn.isNetworkSupported(TYPE_MOBILE)="
+ + conn.isNetworkSupported(TYPE_MOBILE)
+ + " isReady=" + isReady);
+ }
+ return retVal;
+ }
+
+ /*
+ * TODO: consider adding to TelephonyManager or SubscriptionManager.
+ */
+ public static boolean hasReadyMobileRadio(Context context, int subId) {
+ if (TEST_RADIOS) {
+ return SystemProperties.get(TEST_RADIOS_PROP).contains("mobile");
+ }
+
+ final ConnectivityManager conn = ConnectivityManager.from(context);
+ final TelephonyManager tele = TelephonyManager.from(context);
+ final int slotId = SubscriptionManager.getSlotId(subId);
+ final boolean isReady = tele.getSimState(slotId) == SIM_STATE_READY;
+
+ boolean retVal = conn.isNetworkSupported(TYPE_MOBILE) && isReady;
+ if (LOGD) Log.d(TAG, "hasReadyMobileRadio: subId=" + subId
+ + " conn.isNetworkSupported(TYPE_MOBILE)=" + conn.isNetworkSupported(TYPE_MOBILE)
+ + " isReady=" + isReady);
+ return retVal;
+ }
+
+ private OnItemSelectedListener mCycleListener = new OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ final CycleAdapter.CycleItem cycle = (CycleAdapter.CycleItem)
+ parent.getItemAtPosition(position);
+
+ if (LOGD) {
+ Log.d(TAG, "showing cycle " + cycle + ", start=" + cycle.start + ", end="
+ + cycle.end + "]");
+ }
+
+ // update chart to show selected cycle, and update detail data
+ // to match updated sweep bounds.
+ mChart.setVisibleRange(cycle.start, cycle.end);
+
+ updateDetailData();
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+ // ignored
+ }
+ };
+
+ private final LoaderCallbacks<ChartData> mChartDataCallbacks = new LoaderCallbacks<
+ ChartData>() {
+ @Override
+ public Loader<ChartData> onCreateLoader(int id, Bundle args) {
+ return new ChartDataLoader(getActivity(), mStatsSession, args);
+ }
+
+ @Override
+ public void onLoadFinished(Loader<ChartData> loader, ChartData data) {
+ setLoading(false, true);
+ mChartData = data;
+ mChart.bindNetworkStats(mChartData.network);
+
+ // calcuate policy cycles based on available data
+ updatePolicy(true);
+ }
+
+ @Override
+ public void onLoaderReset(Loader<ChartData> loader) {
+ mChartData = null;
+ mChart.bindNetworkStats(null);
+ }
+ };
+
+ private final LoaderCallbacks<NetworkStats> mSummaryCallbacks = new LoaderCallbacks<
+ NetworkStats>() {
+ @Override
+ public Loader<NetworkStats> onCreateLoader(int id, Bundle args) {
+ return new SummaryForAllUidLoader(getActivity(), mStatsSession, args);
+ }
+
+ @Override
+ public void onLoadFinished(Loader<NetworkStats> loader, NetworkStats data) {
+ final int[] restrictedUids = services.mPolicyManager.getUidsWithPolicy(
+ POLICY_REJECT_METERED_BACKGROUND);
+ bindStats(data, restrictedUids);
+ updateEmptyVisible();
+ }
+
+ @Override
+ public void onLoaderReset(Loader<NetworkStats> loader) {
+ bindStats(null, new int[0]);
+ updateEmptyVisible();
+ }
+
+ private void updateEmptyVisible() {
+ if ((mApps.getPreferenceCount() != 0) !=
+ (getPreferenceScreen().getPreferenceCount() != 0)) {
+ if (mApps.getPreferenceCount() != 0) {
+ getPreferenceScreen().addPreference(mUsageAmount);
+ getPreferenceScreen().addPreference(mApps);
+ } else {
+ getPreferenceScreen().removeAll();
+ }
+ }
+ }
+ };
+}
diff --git a/src/com/android/settings/net/DataUsageMeteredSettings.java b/src/com/android/settings/datausage/DataUsageMeteredSettings.java
similarity index 93%
rename from src/com/android/settings/net/DataUsageMeteredSettings.java
rename to src/com/android/settings/datausage/DataUsageMeteredSettings.java
index ef3a705..9790704 100644
--- a/src/com/android/settings/net/DataUsageMeteredSettings.java
+++ b/src/com/android/settings/datausage/DataUsageMeteredSettings.java
@@ -1,20 +1,18 @@
/*
- * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2016 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
*/
-package com.android.settings.net;
+package com.android.settings.datausage;
import android.content.Context;
import android.content.res.Resources;
@@ -42,8 +40,8 @@
import static android.net.NetworkPolicy.LIMIT_DISABLED;
import static android.net.wifi.WifiInfo.removeDoubleQuotes;
-import static com.android.settings.DataUsageSummary.hasReadyMobileRadio;
-import static com.android.settings.DataUsageSummary.hasWifiRadio;
+import static com.android.settings.datausage.DataUsageList.hasReadyMobileRadio;
+import static com.android.settings.datausage.DataUsageSummary.hasWifiRadio;
/**
* Panel to configure {@link NetworkPolicy#metered} for networks.
diff --git a/src/com/android/settings/datausage/DataUsagePreference.java b/src/com/android/settings/datausage/DataUsagePreference.java
new file mode 100644
index 0000000..bd865b5
--- /dev/null
+++ b/src/com/android/settings/datausage/DataUsagePreference.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.settings.datausage;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.NetworkTemplate;
+import android.os.Bundle;
+import android.support.v7.preference.Preference;
+import android.telephony.SubscriptionManager;
+import android.text.format.Formatter;
+import android.util.AttributeSet;
+import com.android.settings.Utils;
+import com.android.settingslib.net.DataUsageController;
+import com.android.settings.R;
+
+public class DataUsagePreference extends Preference implements TemplatePreference {
+
+ private NetworkTemplate mTemplate;
+ private int mSubId;
+
+ public DataUsagePreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public void setTemplate(NetworkTemplate template, int subId,
+ NetworkServices services) {
+ mTemplate = template;
+ mSubId = subId;
+ DataUsageController controller = new DataUsageController(getContext());
+ DataUsageController.DataUsageInfo usageInfo = controller.getDataUsageInfo(mTemplate);
+ setSummary(getContext().getString(R.string.data_usage_template,
+ Formatter.formatFileSize(getContext(), usageInfo.usageLevel), usageInfo.period));
+ setIntent(getIntent());
+ }
+
+ @Override
+ public Intent getIntent() {
+ Bundle args = new Bundle();
+ args.putParcelable(DataUsageList.EXTRA_NETWORK_TEMPLATE, mTemplate);
+ args.putInt(DataUsageList.EXTRA_SUB_ID, mSubId);
+ return Utils.onBuildStartFragmentIntent(getContext(), DataUsageList.class.getName(), args,
+ getContext().getPackageName(), 0, getTitle(), false);
+ }
+}
diff --git a/src/com/android/settings/datausage/DataUsageSummary.java b/src/com/android/settings/datausage/DataUsageSummary.java
new file mode 100644
index 0000000..de98008
--- /dev/null
+++ b/src/com/android/settings/datausage/DataUsageSummary.java
@@ -0,0 +1,375 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.settings.datausage;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.INetworkStatsSession;
+import android.net.NetworkTemplate;
+import android.net.TrafficStats;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.provider.SearchIndexableResource;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.text.format.Formatter;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import com.android.internal.logging.MetricsLogger;
+import com.android.settings.R;
+import com.android.settings.SummaryPreference;
+import com.android.settings.Utils;
+import com.android.settings.dashboard.SummaryLoader;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settings.search.Indexable;
+import com.android.settingslib.net.DataUsageController;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static android.net.ConnectivityManager.TYPE_ETHERNET;
+import static android.net.ConnectivityManager.TYPE_WIFI;
+
+public class DataUsageSummary extends DataUsageBase implements Indexable {
+
+ private static final String TAG = "DataUsageSummary";
+ static final boolean LOGD = false;
+
+ public static final boolean TEST_RADIOS = false;
+ public static final String TEST_RADIOS_PROP = "test.radios";
+
+ private static final String KEY_STATUS_HEADER = "status_header";
+ private static final String KEY_LIMIT_SUMMARY = "limit_summary";
+ private static final String KEY_RESTRICT_BACKGROUND = "restrict_background";
+
+ private DataUsageController mDataUsageController;
+ private SummaryPreference mSummaryPreference;
+ private Preference mLimitPreference;
+ private NetworkTemplate mDefaultTemplate;
+ private int mDataUsageTemplate;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ boolean hasMobileData = hasMobileData(getContext());
+ mDataUsageController = new DataUsageController(getContext());
+ addPreferencesFromResource(R.xml.data_usage);
+
+ int defaultSubId = getDefaultSubscriptionId(getContext());
+ mDefaultTemplate = getDefaultTemplate(getContext(), defaultSubId);
+ if (hasMobileData) {
+ mLimitPreference = findPreference(KEY_LIMIT_SUMMARY);
+ } else {
+ removePreference(KEY_LIMIT_SUMMARY);
+ }
+ if (!hasMobileData || !isAdmin()) {
+ removePreference(KEY_RESTRICT_BACKGROUND);
+ }
+ if (hasMobileData) {
+ List<SubscriptionInfo> subscriptions =
+ services.mSubscriptionManager.getActiveSubscriptionInfoList();
+ if (subscriptions.size() == 0) {
+ addMobileSection(defaultSubId);
+ }
+ for (int i = 0; i < subscriptions.size(); i++) {
+ addMobileSection(subscriptions.get(i).getSubscriptionId());
+ }
+ }
+ boolean hasWifiRadio = hasWifiRadio(getContext());
+ if (hasWifiRadio) {
+ addWifiSection();
+ }
+ if (hasEthernet(getContext())) {
+ addEthernetSection();
+ }
+ mDataUsageTemplate = hasMobileData ? R.string.cell_data_template
+ : hasWifiRadio ? R.string.wifi_data_template
+ : R.string.ethernet_data_template;
+
+ mSummaryPreference = (SummaryPreference) findPreference(KEY_STATUS_HEADER);
+ setHasOptionsMenu(true);
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ inflater.inflate(R.menu.data_usage, menu);
+ super.onCreateOptionsMenu(menu, inflater);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.data_usage_menu_cellular_networks: {
+ final Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setComponent(new ComponentName("com.android.phone",
+ "com.android.phone.MobileNetworkSettings"));
+ startActivity(intent);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void addMobileSection(int subId) {
+ TemplatePreferenceCategory category = (TemplatePreferenceCategory)
+ inflatePreferences(R.xml.data_usage_cellular);
+ category.setTemplate(getNetworkTemplate(subId), subId, services);
+ }
+
+ private void addWifiSection() {
+ TemplatePreferenceCategory category = (TemplatePreferenceCategory)
+ inflatePreferences(R.xml.data_usage_wifi);
+ category.setTemplate(NetworkTemplate.buildTemplateWifiWildcard(), 0, services);
+ }
+
+ private void addEthernetSection() {
+ TemplatePreferenceCategory category = (TemplatePreferenceCategory)
+ inflatePreferences(R.xml.data_usage_ethernet);
+ category.setTemplate(NetworkTemplate.buildTemplateEthernet(), 0, services);
+ }
+
+ private Preference inflatePreferences(int resId) {
+ PreferenceScreen rootPreferences = getPreferenceManager().inflateFromResource(
+ getPrefContext(), resId, null);
+ Preference pref = rootPreferences.getPreference(0);
+ rootPreferences.removeAll();
+
+ PreferenceScreen screen = getPreferenceScreen();
+ pref.setOrder(screen.getPreferenceCount());
+ screen.addPreference(pref);
+
+ return pref;
+ }
+
+ private NetworkTemplate getNetworkTemplate(int subscriptionId) {
+ NetworkTemplate mobileAll = NetworkTemplate.buildTemplateMobileAll(
+ services.mTelephonyManager.getSubscriberId(subscriptionId));
+ return NetworkTemplate.normalize(mobileAll,
+ services.mTelephonyManager.getMergedSubscriberIds());
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ updateState();
+ }
+
+ private void updateState() {
+ DataUsageController.DataUsageInfo info = mDataUsageController.getDataUsageInfo(
+ mDefaultTemplate);
+ Context context = getContext();
+ if (mSummaryPreference != null) {
+ Formatter.BytesResult usedResult = Formatter.formatBytes(context.getResources(),
+ info.usageLevel, Formatter.FLAG_SHORTER);
+ mSummaryPreference.setAmount(usedResult.value);
+ mSummaryPreference.setUnits(getString(mDataUsageTemplate, usedResult.units));
+ long limit = info.limitLevel;
+ if (limit <= 0) {
+ limit = info.warningLevel;
+ }
+ if (info.usageLevel > limit) {
+ limit = info.usageLevel;
+ }
+ mSummaryPreference.setSummary(info.period);
+ mSummaryPreference.setLabels(Formatter.formatFileSize(context, 0),
+ Formatter.formatFileSize(context, limit));
+ mSummaryPreference.setRatios(info.usageLevel / (float) limit, 0,
+ (limit - info.usageLevel) / (float) limit);
+ }
+ if (mLimitPreference != null) {
+ String warning = Formatter.formatFileSize(context, info.warningLevel);
+ String limit = Formatter.formatFileSize(context, info.limitLevel);
+ mLimitPreference.setSummary(getString(info.limitLevel <= 0 ? R.string.cell_warning_only
+ : R.string.cell_warning_and_limit, warning, limit));
+ }
+
+ PreferenceScreen screen = getPreferenceScreen();
+ for (int i = 1; i < screen.getPreferenceCount(); i++) {
+ ((TemplatePreferenceCategory) screen.getPreference(i)).pushTemplates(services);
+ }
+ }
+
+ @Override
+ protected int getMetricsCategory() {
+ return MetricsLogger.DATA_USAGE_SUMMARY;
+ }
+
+ /**
+ * Test if device has an ethernet network connection.
+ */
+ public boolean hasEthernet(Context context) {
+ if (TEST_RADIOS) {
+ return SystemProperties.get(TEST_RADIOS_PROP).contains("ethernet");
+ }
+
+ final ConnectivityManager conn = ConnectivityManager.from(context);
+ final boolean hasEthernet = conn.isNetworkSupported(TYPE_ETHERNET);
+
+ final long ethernetBytes;
+ try {
+ INetworkStatsSession statsSession = services.mStatsService.openSession();
+ if (statsSession != null) {
+ ethernetBytes = statsSession.getSummaryForNetwork(
+ NetworkTemplate.buildTemplateEthernet(), Long.MIN_VALUE, Long.MAX_VALUE)
+ .getTotalBytes();
+ TrafficStats.closeQuietly(statsSession);
+ } else {
+ ethernetBytes = 0;
+ }
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+
+ // only show ethernet when both hardware present and traffic has occurred
+ return hasEthernet && ethernetBytes > 0;
+ }
+
+ public static boolean hasMobileData(Context context) {
+ return ConnectivityManager.from(context).isNetworkSupported(
+ ConnectivityManager.TYPE_MOBILE);
+ }
+
+ /**
+ * Test if device has a Wi-Fi data radio.
+ */
+ public static boolean hasWifiRadio(Context context) {
+ if (TEST_RADIOS) {
+ return SystemProperties.get(TEST_RADIOS_PROP).contains("wifi");
+ }
+
+ final ConnectivityManager conn = ConnectivityManager.from(context);
+ return conn.isNetworkSupported(TYPE_WIFI);
+ }
+
+ public static int getDefaultSubscriptionId(Context context) {
+ SubscriptionManager subManager = SubscriptionManager.from(context);
+ if (subManager == null) {
+ return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ }
+ SubscriptionInfo subscriptionInfo = subManager.getDefaultDataSubscriptionInfo();
+ if (subscriptionInfo == null) {
+ List<SubscriptionInfo> list = subManager.getAllSubscriptionInfoList();
+ if (list.size() == 0) {
+ return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ }
+ subscriptionInfo = list.get(0);
+ }
+ return subscriptionInfo.getSubscriptionId();
+ }
+
+ public static NetworkTemplate getDefaultTemplate(Context context, int defaultSubId) {
+ if (hasMobileData(context)) {
+ TelephonyManager telephonyManager = TelephonyManager.from(context);
+ NetworkTemplate mobileAll = NetworkTemplate.buildTemplateMobileAll(
+ telephonyManager.getSubscriberId(defaultSubId));
+ return NetworkTemplate.normalize(mobileAll,
+ telephonyManager.getMergedSubscriberIds());
+ } else if (hasWifiRadio(context)) {
+ return NetworkTemplate.buildTemplateWifiWildcard();
+ } else {
+ return NetworkTemplate.buildTemplateEthernet();
+ }
+ }
+
+ private static class SummaryProvider
+ implements SummaryLoader.SummaryProvider {
+
+ private final Activity mActivity;
+ private final SummaryLoader mSummaryLoader;
+ private final DataUsageController mDataController;
+
+ public SummaryProvider(Activity activity, SummaryLoader summaryLoader) {
+ mActivity = activity;
+ mSummaryLoader = summaryLoader;
+ mDataController = new DataUsageController(activity);
+ }
+
+ @Override
+ public void setListening(boolean listening) {
+ if (listening) {
+ DataUsageController.DataUsageInfo info = mDataController.getDataUsageInfo();
+ String used;
+ if (info == null) {
+ used = Formatter.formatFileSize(mActivity, 0);
+ } else if (info.limitLevel <= 0) {
+ used = Formatter.formatFileSize(mActivity, info.usageLevel);
+ } else {
+ used = Utils.formatPercentage(info.usageLevel, info.limitLevel);
+ }
+ mSummaryLoader.setSummary(this,
+ mActivity.getString(R.string.data_usage_summary_format, used));
+ }
+ }
+ }
+
+ public static final SummaryLoader.SummaryProviderFactory SUMMARY_PROVIDER_FACTORY
+ = new SummaryLoader.SummaryProviderFactory() {
+ @Override
+ public SummaryLoader.SummaryProvider createSummaryProvider(Activity activity,
+ SummaryLoader summaryLoader) {
+ return new SummaryProvider(activity, summaryLoader);
+ }
+ };
+
+ /**
+ * For search
+ */
+ public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider() {
+
+ @Override
+ public List<SearchIndexableResource> getXmlResourcesToIndex(Context context,
+ boolean enabled) {
+ ArrayList<SearchIndexableResource> resources = new ArrayList<>();
+ SearchIndexableResource resource = new SearchIndexableResource(context);
+ resource.xmlResId = R.xml.data_usage;
+ resources.add(resource);
+
+ if (hasMobileData(context)) {
+ resource = new SearchIndexableResource(context);
+ resource.xmlResId = R.xml.data_usage_cellular;
+ resources.add(resource);
+ }
+ if (hasWifiRadio(context)) {
+ resource = new SearchIndexableResource(context);
+ resource.xmlResId = R.xml.data_usage_wifi;
+ resources.add(resource);
+ }
+ return resources;
+ }
+
+ @Override
+ public List<String> getNonIndexableKeys(Context context) {
+ ArrayList<String> keys = new ArrayList<>();
+ boolean hasMobileData = ConnectivityManager.from(context).isNetworkSupported(
+ ConnectivityManager.TYPE_MOBILE);
+
+ if (hasMobileData) {
+ keys.add(KEY_RESTRICT_BACKGROUND);
+ }
+
+ return keys;
+ }
+ };
+}
diff --git a/src/com/android/settings/datausage/NetworkRestrictionsPreference.java b/src/com/android/settings/datausage/NetworkRestrictionsPreference.java
new file mode 100644
index 0000000..e2e9d1e
--- /dev/null
+++ b/src/com/android/settings/datausage/NetworkRestrictionsPreference.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.settings.datausage;
+
+import android.content.Context;
+import android.net.NetworkTemplate;
+import android.support.v7.preference.Preference;
+import android.util.AttributeSet;
+
+public class NetworkRestrictionsPreference extends Preference implements TemplatePreference {
+
+ public NetworkRestrictionsPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public void setTemplate(NetworkTemplate template, int subId,
+ NetworkServices services) {
+ // TODO: Summary
+ }
+}
diff --git a/src/com/android/settings/datausage/RestrictBackgroundDataPreference.java b/src/com/android/settings/datausage/RestrictBackgroundDataPreference.java
new file mode 100644
index 0000000..67bce03
--- /dev/null
+++ b/src/com/android/settings/datausage/RestrictBackgroundDataPreference.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.settings.datausage;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.net.NetworkPolicyManager;
+import android.support.v7.preference.PreferenceViewHolder;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.Checkable;
+import com.android.settings.CustomDialogPreference;
+import com.android.settings.R;
+import com.android.settings.Utils;
+import com.android.settings.dashboard.conditional.BackgroundDataCondition;
+import com.android.settings.dashboard.conditional.ConditionManager;
+
+public class RestrictBackgroundDataPreference extends CustomDialogPreference {
+
+ private NetworkPolicyManager mPolicyManager;
+ private boolean mChecked;
+
+ public RestrictBackgroundDataPreference(Context context, AttributeSet attrs) {
+ super(context, attrs, android.R.attr.switchPreferenceStyle);
+ }
+
+ @Override
+ public void onAttached() {
+ super.onAttached();
+ mPolicyManager = NetworkPolicyManager.from(getContext());
+ setChecked(mPolicyManager.getRestrictBackground());
+ }
+
+ public void setRestrictBackground(boolean restrictBackground) {
+ mPolicyManager.setRestrictBackground(restrictBackground);
+ setChecked(restrictBackground);
+ ConditionManager.get(getContext()).getCondition(BackgroundDataCondition.class)
+ .refreshState();
+ }
+
+ private void setChecked(boolean checked) {
+ if (mChecked == checked) return;
+ mChecked = checked;
+ notifyChanged();
+ }
+
+ @Override
+ public void onBindViewHolder(PreferenceViewHolder holder) {
+ super.onBindViewHolder(holder);
+ View switchView = holder.findViewById(android.R.id.switch_widget);
+ switchView.setClickable(false);
+ ((Checkable) switchView).setChecked(mChecked);
+ }
+
+ @Override
+ protected void performClick(View view) {
+ if (mChecked) {
+ setRestrictBackground(false);
+ } else {
+ super.performClick(view);
+ }
+ }
+
+ @Override
+ protected void onPrepareDialogBuilder(AlertDialog.Builder builder,
+ DialogInterface.OnClickListener listener) {
+ super.onPrepareDialogBuilder(builder, listener);
+ builder.setTitle(R.string.data_usage_restrict_background_title);
+ if (Utils.hasMultipleUsers(getContext())) {
+ builder.setMessage(R.string.data_usage_restrict_background_multiuser);
+ } else {
+ builder.setMessage(R.string.data_usage_restrict_background);
+ }
+
+ builder.setPositiveButton(android.R.string.ok, listener);
+ builder.setNegativeButton(android.R.string.cancel, null);
+ }
+
+ @Override
+ protected void onClick(DialogInterface dialog, int which) {
+ if (which != DialogInterface.BUTTON_POSITIVE) {
+ return;
+ }
+ setRestrictBackground(true);
+ }
+}
diff --git a/src/com/android/settings/datausage/TemplatePreference.java b/src/com/android/settings/datausage/TemplatePreference.java
new file mode 100644
index 0000000..4b1cd0c
--- /dev/null
+++ b/src/com/android/settings/datausage/TemplatePreference.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.settings.datausage;
+
+import android.net.INetworkStatsService;
+import android.net.NetworkPolicyManager;
+import android.net.NetworkTemplate;
+import android.os.INetworkManagementService;
+import android.os.UserManager;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import com.android.settingslib.NetworkPolicyEditor;
+
+public interface TemplatePreference {
+
+ void setTemplate(NetworkTemplate template, int subId, NetworkServices services);
+
+ class NetworkServices {
+ INetworkManagementService mNetworkService;
+ INetworkStatsService mStatsService;
+ NetworkPolicyManager mPolicyManager;
+ TelephonyManager mTelephonyManager;
+ SubscriptionManager mSubscriptionManager;
+ UserManager mUserManager;
+ NetworkPolicyEditor mPolicyEditor;
+ }
+
+}
diff --git a/src/com/android/settings/datausage/TemplatePreferenceCategory.java b/src/com/android/settings/datausage/TemplatePreferenceCategory.java
new file mode 100644
index 0000000..0be5c73
--- /dev/null
+++ b/src/com/android/settings/datausage/TemplatePreferenceCategory.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.settings.datausage;
+
+import android.content.Context;
+import android.net.NetworkTemplate;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceCategory;
+import android.util.AttributeSet;
+
+public class TemplatePreferenceCategory extends PreferenceCategory implements TemplatePreference {
+
+ private NetworkTemplate mTemplate;
+ private int mSubId;
+
+ public TemplatePreferenceCategory(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public void setTemplate(NetworkTemplate template, int subId,
+ NetworkServices services) {
+ mTemplate = template;
+ mSubId = subId;
+ }
+
+ @Override
+ public boolean addPreference(Preference preference) {
+ if (!(preference instanceof TemplatePreference)) {
+ throw new IllegalArgumentException(
+ "TemplatePreferenceCategories can only hold TemplatePreferences");
+ }
+ return super.addPreference(preference);
+ }
+
+ public void pushTemplates(NetworkServices services) {
+ if (mTemplate == null) {
+ throw new RuntimeException("null mTemplate for " + getKey());
+ }
+ for (int i = 0; i < getPreferenceCount(); i++) {
+ ((TemplatePreference) getPreference(i)).setTemplate(mTemplate, mSubId, services);
+ }
+ }
+
+}
diff --git a/src/com/android/settings/search/Ranking.java b/src/com/android/settings/search/Ranking.java
index 3d5ff35..59f83ec 100644
--- a/src/com/android/settings/search/Ranking.java
+++ b/src/com/android/settings/search/Ranking.java
@@ -17,7 +17,6 @@
package com.android.settings.search;
import com.android.settings.ChooseLockGeneric;
-import com.android.settings.DataUsageSummary;
import com.android.settings.DateTimeSettings;
import com.android.settings.DevelopmentSettings;
import com.android.settings.DeviceInfoSettings;
@@ -35,6 +34,8 @@
import com.android.settings.applications.AdvancedAppSettings;
import com.android.settings.applications.ManageDefaultApps;
import com.android.settings.bluetooth.BluetoothSettings;
+import com.android.settings.datausage.DataUsageMeteredSettings;
+import com.android.settings.datausage.DataUsageSummary;
import com.android.settings.deviceinfo.StorageSettings;
import com.android.settings.display.ScreenZoomSettings;
import com.android.settings.fuelgauge.BatterySaverSettings;
@@ -42,7 +43,6 @@
import com.android.settings.inputmethod.InputMethodAndLanguageSettings;
import com.android.settings.location.LocationSettings;
import com.android.settings.location.ScanningSettings;
-import com.android.settings.net.DataUsageMeteredSettings;
import com.android.settings.notification.ConfigureNotificationSettings;
import com.android.settings.notification.OtherSoundSettings;
import com.android.settings.notification.SoundSettings;
diff --git a/src/com/android/settings/search/SearchIndexableResources.java b/src/com/android/settings/search/SearchIndexableResources.java
index f15ff63..1d55b55 100644
--- a/src/com/android/settings/search/SearchIndexableResources.java
+++ b/src/com/android/settings/search/SearchIndexableResources.java
@@ -16,9 +16,6 @@
package com.android.settings.search;
-import android.provider.SearchIndexableResource;
-
-import com.android.settings.DataUsageSummary;
import com.android.settings.DateTimeSettings;
import com.android.settings.DevelopmentSettings;
import com.android.settings.DeviceInfoSettings;
@@ -37,6 +34,8 @@
import com.android.settings.applications.AdvancedAppSettings;
import com.android.settings.applications.ManageDefaultApps;
import com.android.settings.bluetooth.BluetoothSettings;
+import com.android.settings.datausage.DataUsageMeteredSettings;
+import com.android.settings.datausage.DataUsageSummary;
import com.android.settings.deviceinfo.StorageSettings;
import com.android.settings.display.ScreenZoomSettings;
import com.android.settings.fuelgauge.BatterySaverSettings;
@@ -44,7 +43,6 @@
import com.android.settings.inputmethod.InputMethodAndLanguageSettings;
import com.android.settings.location.LocationSettings;
import com.android.settings.location.ScanningSettings;
-import com.android.settings.net.DataUsageMeteredSettings;
import com.android.settings.notification.ConfigureNotificationSettings;
import com.android.settings.notification.OtherSoundSettings;
import com.android.settings.notification.SoundSettings;
@@ -58,6 +56,8 @@
import com.android.settings.wifi.SavedAccessPointsWifiSettings;
import com.android.settings.wifi.WifiSettings;
+import android.provider.SearchIndexableResource;
+
import java.util.Collection;
import java.util.HashMap;
diff --git a/src/com/android/settings/widget/ChartGridView.java b/src/com/android/settings/widget/ChartGridView.java
index c85d4fc..2091719 100644
--- a/src/com/android/settings/widget/ChartGridView.java
+++ b/src/com/android/settings/widget/ChartGridView.java
@@ -32,7 +32,7 @@
import com.android.internal.util.Preconditions;
import com.android.settings.R;
-import static com.android.settings.DataUsageSummary.formatDateRange;
+import static com.android.settings.Utils.formatDateRange;
/**
* Background of {@link ChartView} that renders grid lines as requested by
diff --git a/src/com/android/settings/widget/ChartSweepView.java b/src/com/android/settings/widget/ChartSweepView.java
index e8e24aa..cdcd0a7 100644
--- a/src/com/android/settings/widget/ChartSweepView.java
+++ b/src/com/android/settings/widget/ChartSweepView.java
@@ -143,7 +143,6 @@
a.recycle();
setClickable(true);
- setFocusable(true);
setOnClickListener(mClickListener);
setWillNotDraw(false);