diff --git a/proguard.flags b/proguard.flags index 578ff4d5f31..448cd729b84 100644 --- a/proguard.flags +++ b/proguard.flags @@ -5,6 +5,7 @@ -keep class com.android.settings.wifi.*Settings -keep class com.android.settings.deviceinfo.* -keep class com.android.settings.bluetooth.* +-keep class com.android.settings.datausage.* -keep class com.android.settings.applications.* -keep class com.android.settings.inputmethod.* -keep class com.android.settings.ResetNetwork diff --git a/res/drawable/ic_data_saver.xml b/res/drawable/ic_data_saver.xml new file mode 100644 index 00000000000..426238c05f4 --- /dev/null +++ b/res/drawable/ic_data_saver.xml @@ -0,0 +1,28 @@ + + + + diff --git a/res/layout/data_usage_cycles.xml b/res/layout/data_usage_cycles.xml index 5267e26a602..9c6cc31b199 100644 --- a/res/layout/data_usage_cycles.xml +++ b/res/layout/data_usage_cycles.xml @@ -17,7 +17,7 @@ diff --git a/res/values/strings.xml b/res/values/strings.xml index e4abbca62e8..b334c57ea93 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -4914,9 +4914,9 @@ App settings - Restrict app background data - - Disable background data on cellular networks. + Background data + + Enable usage of cellular data in the background To restrict background data for this app, first set a cellular data limit. @@ -6870,7 +6870,7 @@ Internet is available only via Wi-Fi - Background data is off + Data Saver is on Background data is only available via Wi-Fi. This may affect some apps or services when Wi-Fi is not available. @@ -6966,4 +6966,28 @@ the code to do that --> Other apps included in usage + + + 1 app allowed to use unrestricted data when Data Saver is on + %1$d apps allowed to use unrestricted data when Data Saver is on + + + + Data Saver + + + Unrestricted data access + + + On + + + Off + + + Unrestricted data usage + + + Allow unrestricted data access when Data Saver is on + diff --git a/res/xml/app_data_usage.xml b/res/xml/app_data_usage.xml index b082b56b10e..520b93b9d4e 100644 --- a/res/xml/app_data_usage.xml +++ b/res/xml/app_data_usage.xml @@ -17,6 +17,9 @@ + + @@ -50,6 +53,11 @@ android:title="@string/data_usage_app_restrict_background" android:summary="@string/data_usage_app_restrict_background_summary" /> + + diff --git a/res/layout/data_usage_app_header.xml b/res/xml/data_saver.xml similarity index 53% rename from res/layout/data_usage_app_header.xml rename to res/xml/data_saver.xml index 8ca391a051c..5b69cbb4865 100644 --- a/res/layout/data_usage_app_header.xml +++ b/res/xml/data_saver.xml @@ -1,5 +1,5 @@ - - + - + - - - - - + diff --git a/res/xml/data_usage.xml b/res/xml/data_usage.xml index 378496ed90a..0626da905ac 100644 --- a/res/xml/data_usage.xml +++ b/res/xml/data_usage.xml @@ -29,9 +29,10 @@ android:key="limit_summary" android:selectable="false" /> - + android:title="@string/data_saver_title" + android:fragment="com.android.settings.datausage.DataSaverSummary" /> diff --git a/src/com/android/settings/InstrumentedFragment.java b/src/com/android/settings/InstrumentedFragment.java index ea39cf3e8d7..884e07b35c2 100644 --- a/src/com/android/settings/InstrumentedFragment.java +++ b/src/com/android/settings/InstrumentedFragment.java @@ -41,6 +41,8 @@ public abstract class InstrumentedFragment extends PreferenceFragment { public static final int VIRTUAL_KEYBOARDS = UNDECLARED + 11; public static final int PHYSICAL_KEYBOARDS = UNDECLARED + 12; public static final int ENABLE_VIRTUAL_KEYBOARDS = UNDECLARED + 13; + public static final int DATA_SAVER_SUMMARY = UNDECLARED + 14; + public static final int DATA_USAGE_UNRESTRICTED_ACCESS = UNDECLARED + 15; /** * Declare the view of this category. diff --git a/src/com/android/settings/dashboard/conditional/BackgroundDataCondition.java b/src/com/android/settings/dashboard/conditional/BackgroundDataCondition.java index d1bcb124cc7..6bfc5384e13 100644 --- a/src/com/android/settings/dashboard/conditional/BackgroundDataCondition.java +++ b/src/com/android/settings/dashboard/conditional/BackgroundDataCondition.java @@ -34,7 +34,7 @@ public class BackgroundDataCondition extends Condition { @Override public Icon getIcon() { - return Icon.createWithResource(mManager.getContext(), R.drawable.ic_cellular_off); + return Icon.createWithResource(mManager.getContext(), R.drawable.ic_data_saver); } @Override diff --git a/src/com/android/settings/datausage/AppDataUsage.java b/src/com/android/settings/datausage/AppDataUsage.java index 9ca066f002b..a65f0074e6f 100644 --- a/src/com/android/settings/datausage/AppDataUsage.java +++ b/src/com/android/settings/datausage/AppDataUsage.java @@ -14,14 +14,6 @@ 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; @@ -43,10 +35,15 @@ 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 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 static android.net.NetworkPolicyManager.POLICY_NONE; import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND; @@ -62,6 +59,8 @@ public class AppDataUsage extends DataUsageBase implements Preference.OnPreferen 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 String KEY_CYCLE = "cycle"; + private static final String KEY_UNRESTRICTED_DATA = "unrestricted_data_saver"; private static final int LOADER_CHART_DATA = 2; @@ -76,7 +75,6 @@ public class AppDataUsage extends DataUsageBase implements Preference.OnPreferen private Drawable mIcon; private CharSequence mLabel; private INetworkStatsSession mStatsSession; - private Spinner mCycleSpinner; private CycleAdapter mCycleAdapter; private long mStart; @@ -86,6 +84,9 @@ public class AppDataUsage extends DataUsageBase implements Preference.OnPreferen private NetworkPolicy mPolicy; private AppItem mAppItem; private Intent mAppSettingsIntent; + private SpinnerPreference mCycle; + private SwitchPreference mUnrestrictedData; + private DataSaverBackend mDataSaverBackend; @Override public void onCreate(Bundle icicle) { @@ -137,9 +138,15 @@ public class AppDataUsage extends DataUsageBase implements Preference.OnPreferen mForegroundUsage = findPreference(KEY_FOREGROUND_USAGE); mBackgroundUsage = findPreference(KEY_BACKGROUND_USAGE); + mCycle = (SpinnerPreference) findPreference(KEY_CYCLE); + mCycleAdapter = new CycleAdapter(getContext(), mCycle, mCycleListener, false); + if (UserHandle.isApp(mAppItem.key)) { mRestrictBackground = (SwitchPreference) findPreference(KEY_RESTRICT_BACKGROUND); mRestrictBackground.setOnPreferenceChangeListener(this); + mUnrestrictedData = (SwitchPreference) findPreference(KEY_UNRESTRICTED_DATA); + mUnrestrictedData.setOnPreferenceChangeListener(this); + mDataSaverBackend = new DataSaverBackend(getContext()); mAppSettings = findPreference(KEY_APP_SETTINGS); mAppSettingsIntent = new Intent(Intent.ACTION_MANAGE_NETWORK_USAGE); @@ -169,6 +176,7 @@ public class AppDataUsage extends DataUsageBase implements Preference.OnPreferen removePreference(KEY_APP_LIST); } } else { + removePreference(KEY_UNRESTRICTED_DATA); removePreference(KEY_APP_SETTINGS); removePreference(KEY_RESTRICT_BACKGROUND); removePreference(KEY_APP_LIST); @@ -195,6 +203,9 @@ public class AppDataUsage extends DataUsageBase implements Preference.OnPreferen if (preference == mRestrictBackground) { setAppRestrictBackground((Boolean) newValue); return true; + } else if (preference == mUnrestrictedData) { + mDataSaverBackend.setIsWhitelisted(mAppItem.key, (Boolean) newValue); + return true; } return false; } @@ -214,6 +225,9 @@ public class AppDataUsage extends DataUsageBase implements Preference.OnPreferen if (mRestrictBackground != null) { mRestrictBackground.setChecked(getAppRestrictBackground()); } + if (mUnrestrictedData != null) { + mUnrestrictedData.setChecked(mDataSaverBackend.isWhitelisted(mAppItem.key)); + } } private void addUid(int uid) { @@ -260,7 +274,7 @@ public class AppDataUsage extends DataUsageBase implements Preference.OnPreferen public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - View header = setPinnedHeaderView(R.layout.data_usage_app_header); + View header = setPinnedHeaderView(R.layout.app_header); String pkg = mPackages.size() != 0 ? mPackages.valueAt(0) : null; int uid = 0; try { @@ -269,9 +283,6 @@ public class AppDataUsage extends DataUsageBase implements Preference.OnPreferen } 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 @@ -283,8 +294,7 @@ public class AppDataUsage extends DataUsageBase implements Preference.OnPreferen new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView parent, View view, int position, long id) { - final CycleAdapter.CycleItem cycle = - (CycleAdapter.CycleItem) parent.getItemAtPosition(position); + final CycleAdapter.CycleItem cycle = (CycleAdapter.CycleItem) mCycle.getSelectedItem(); mStart = cycle.start; mEnd = cycle.end; diff --git a/src/com/android/settings/datausage/AppStateDataUsageBridge.java b/src/com/android/settings/datausage/AppStateDataUsageBridge.java new file mode 100644 index 00000000000..1aff49604f2 --- /dev/null +++ b/src/com/android/settings/datausage/AppStateDataUsageBridge.java @@ -0,0 +1,56 @@ +/* + * 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.applications.AppStateBaseBridge; +import com.android.settingslib.applications.ApplicationsState; +import com.android.settingslib.applications.ApplicationsState.AppEntry; + +import java.util.ArrayList; + +public class AppStateDataUsageBridge extends AppStateBaseBridge { + + private static final String TAG = "AppStateDataUsageBridge"; + + private final DataSaverBackend mDataSaverBackend; + + public AppStateDataUsageBridge(ApplicationsState appState, Callback callback, + DataSaverBackend backend) { + super(appState, callback); + mDataSaverBackend = backend; + } + + @Override + protected void loadAllExtraInfo() { + ArrayList apps = mAppSession.getAllApps(); + final int N = apps.size(); + for (int i = 0; i < N; i++) { + AppEntry app = apps.get(i); + app.extraInfo = new DataUsageState(mDataSaverBackend.isWhitelisted(app.info.uid)); + } + } + + @Override + protected void updateExtraInfo(AppEntry app, String pkg, int uid) { + app.extraInfo = new DataUsageState(mDataSaverBackend.isWhitelisted(uid)); + } + + public static class DataUsageState { + public boolean isDataSaverWhitelisted; + + public DataUsageState(boolean isDataSaverWhitelisted) { + this.isDataSaverWhitelisted = isDataSaverWhitelisted; + } + } +} diff --git a/src/com/android/settings/datausage/CycleAdapter.java b/src/com/android/settings/datausage/CycleAdapter.java index 682cc8aca83..67e62cbbaa9 100644 --- a/src/com/android/settings/datausage/CycleAdapter.java +++ b/src/com/android/settings/datausage/CycleAdapter.java @@ -13,17 +13,15 @@ */ 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 com.android.settings.R; +import com.android.settings.Utils; +import com.android.settingslib.net.ChartData; import libcore.util.Objects; import static android.net.NetworkPolicyManager.computeLastCycleBoundary; @@ -31,12 +29,13 @@ import static android.net.NetworkPolicyManager.computeNextCycleBoundary; public class CycleAdapter extends ArrayAdapter { - private final Spinner mSpinner; + private final SpinnerInterface 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); + public CycleAdapter(Context context, SpinnerInterface spinner, + AdapterView.OnItemSelectedListener listener, boolean isHeader) { + super(context, isHeader ? R.layout.filter_spinner_item + : R.layout.data_usage_cycle_item); setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); mSpinner = spinner; mListener = listener; @@ -72,7 +71,7 @@ public class CycleAdapter extends ArrayAdapter { mSpinner.getSelectedItem(); clear(); - final Context context = mSpinner.getContext(); + final Context context = getContext(); NetworkStatsHistory.Entry entry = null; long historyStart = Long.MAX_VALUE; @@ -141,7 +140,7 @@ public class CycleAdapter extends ArrayAdapter { // user-defined inspection region. final CycleAdapter.CycleItem selectedItem = getItem(position); if (!Objects.equal(selectedItem, previousItem)) { - mListener.onItemSelected(mSpinner, null, position, 0); + mListener.onItemSelected(null, null, position, 0); return false; } } @@ -185,4 +184,11 @@ public class CycleAdapter extends ArrayAdapter { return Long.compare(start, another.start); } } + + public interface SpinnerInterface { + void setAdapter(CycleAdapter cycleAdapter); + void setOnItemSelectedListener(AdapterView.OnItemSelectedListener listener); + Object getSelectedItem(); + void setSelection(int position); + } } diff --git a/src/com/android/settings/datausage/DataSaverBackend.java b/src/com/android/settings/datausage/DataSaverBackend.java new file mode 100644 index 00000000000..c38a05c62a0 --- /dev/null +++ b/src/com/android/settings/datausage/DataSaverBackend.java @@ -0,0 +1,148 @@ +/* + * 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.INetworkPolicyListener; +import android.net.INetworkPolicyManager; +import android.net.NetworkPolicyManager; +import android.os.Handler; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Log; +import android.util.SparseBooleanArray; + +import java.util.ArrayList; + +public class DataSaverBackend { + + private static final String TAG = "DataSaverBackend"; + + private final Context mContext; + + private final Handler mHandler = new Handler(); + private final NetworkPolicyManager mPolicyManager; + private final INetworkPolicyManager mIPolicyManager; + private final ArrayList mListeners = new ArrayList<>(); + private SparseBooleanArray mWhitelist; + + // TODO: Staticize into only one. + public DataSaverBackend(Context context) { + mContext = context; + mIPolicyManager = INetworkPolicyManager.Stub.asInterface( + ServiceManager.getService(Context.NETWORK_POLICY_SERVICE)); + mPolicyManager = NetworkPolicyManager.from(context); + } + + public void addListener(Listener listener) { + mListeners.add(listener); + if (mListeners.size() == 1) { + mPolicyManager.registerListener(mPolicyListener); + } + listener.onDataSaverChanged(isDataSaverEnabled()); + } + + public void remListener(Listener listener) { + mListeners.remove(listener); + if (mListeners.size() == 0) { + mPolicyManager.unregisterListener(mPolicyListener); + } + } + + public boolean isDataSaverEnabled() { + return mPolicyManager.getRestrictBackground(); + } + + public void setDataSaverEnabled(boolean enabled) { + mPolicyManager.setRestrictBackground(enabled); + } + + public void refreshWhitelist() { + loadWhitelist(); + } + + public void setIsWhitelisted(int uid, boolean whitelisted) { + mWhitelist.put(uid, whitelisted); + try { + if (whitelisted) { + mIPolicyManager.addRestrictBackgroundWhitelistedUid(uid); + } else { + mIPolicyManager.removeRestrictBackgroundWhitelistedUid(uid); + } + } catch (RemoteException e) { + Log.w(TAG, "Can't reach policy manager", e); + } + } + + public boolean isWhitelisted(int uid) { + if (mWhitelist == null) { + loadWhitelist(); + } + return mWhitelist.get(uid); + } + + public int getWhitelistedCount() { + int count = 0; + if (mWhitelist == null) { + loadWhitelist(); + } + for (int i = 0; i < mWhitelist.size(); i++) { + if (mWhitelist.valueAt(i)) { + count++; + } + } + return count; + } + + private void loadWhitelist() { + mWhitelist = new SparseBooleanArray(); + try { + for (int uid : mIPolicyManager.getRestrictBackgroundWhitelistedUids()) { + mWhitelist.put(uid, true); + } + } catch (RemoteException e) { + } + } + + private void handleRestrictBackgroundChanged(boolean isDataSaving) { + for (int i = 0; i < mListeners.size(); i++) { + mListeners.get(i).onDataSaverChanged(isDataSaving); + } + } + + private final INetworkPolicyListener mPolicyListener = new INetworkPolicyListener.Stub() { + @Override + public void onUidRulesChanged(int i, int i1) throws RemoteException { + } + + @Override + public void onMeteredIfacesChanged(String[] strings) throws RemoteException { + } + + @Override + public void onRestrictBackgroundChanged(final boolean isDataSaving) throws RemoteException { + mHandler.post(new Runnable() { + @Override + public void run() { + handleRestrictBackgroundChanged(isDataSaving); + } + }); + } + }; + + public interface Listener { + void onDataSaverChanged(boolean isDataSaving); + } +} diff --git a/src/com/android/settings/datausage/DataSaverPreference.java b/src/com/android/settings/datausage/DataSaverPreference.java new file mode 100644 index 00000000000..c286d9583eb --- /dev/null +++ b/src/com/android/settings/datausage/DataSaverPreference.java @@ -0,0 +1,47 @@ +/* + * 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.support.v7.preference.Preference; +import android.util.AttributeSet; +import com.android.settings.R; + +public class DataSaverPreference extends Preference implements DataSaverBackend.Listener { + + private final DataSaverBackend mDataSaverBackend; + + public DataSaverPreference(Context context, AttributeSet attrs) { + super(context, attrs); + mDataSaverBackend = new DataSaverBackend(context); + } + + @Override + public void onAttached() { + super.onAttached(); + mDataSaverBackend.addListener(this); + } + + @Override + public void onDetached() { + super.onDetached(); + mDataSaverBackend.addListener(this); + } + + @Override + public void onDataSaverChanged(boolean isDataSaving) { + setSummary(isDataSaving ? R.string.data_saver_on : R.string.data_saver_off); + } +} diff --git a/src/com/android/settings/datausage/DataSaverSummary.java b/src/com/android/settings/datausage/DataSaverSummary.java new file mode 100644 index 00000000000..fa24fa397fa --- /dev/null +++ b/src/com/android/settings/datausage/DataSaverSummary.java @@ -0,0 +1,83 @@ +/* + * 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.os.Bundle; +import android.support.v7.preference.Preference; +import android.util.Log; +import android.widget.Switch; +import com.android.settings.InstrumentedFragment; +import com.android.settings.R; +import com.android.settings.SettingsActivity; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.widget.SwitchBar; + +public class DataSaverSummary extends SettingsPreferenceFragment + implements SwitchBar.OnSwitchChangeListener, DataSaverBackend.Listener { + + private static final String KEY_UNRESTRICTED_ACCESS = "unrestricted_access"; + + private SwitchBar mSwitchBar; + private DataSaverBackend mDataSaverBackend; + private Preference mUnrestrictedAccess; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + addPreferencesFromResource(R.xml.data_saver); + mUnrestrictedAccess = findPreference(KEY_UNRESTRICTED_ACCESS); + mDataSaverBackend = new DataSaverBackend(getContext()); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + mSwitchBar = ((SettingsActivity) getActivity()).getSwitchBar(); + mSwitchBar.show(); + mSwitchBar.addOnSwitchChangeListener(this); + } + + @Override + public void onResume() { + super.onResume(); + mDataSaverBackend.addListener(this); + mDataSaverBackend.refreshWhitelist(); + int count = mDataSaverBackend.getWhitelistedCount(); + mUnrestrictedAccess.setSummary(getResources().getQuantityString( + R.plurals.data_saver_unrestricted_summary, count, count)); + } + + @Override + public void onPause() { + super.onPause(); + mDataSaverBackend.remListener(this); + } + + @Override + public void onSwitchChanged(Switch switchView, boolean isChecked) { + mDataSaverBackend.setDataSaverEnabled(isChecked); + } + + @Override + protected int getMetricsCategory() { + return InstrumentedFragment.DATA_SAVER_SUMMARY; + } + + @Override + public void onDataSaverChanged(boolean isDataSaving) { + mSwitchBar.setChecked(isDataSaving); + } +} diff --git a/src/com/android/settings/datausage/DataUsageList.java b/src/com/android/settings/datausage/DataUsageList.java index 4aa52ba371a..bb24aef8dc4 100644 --- a/src/com/android/settings/datausage/DataUsageList.java +++ b/src/com/android/settings/datausage/DataUsageList.java @@ -147,7 +147,27 @@ public class DataUsageList extends DataUsageBase { mHeader = setPinnedHeaderView(R.layout.apps_filter_spinner); mCycleSpinner = (Spinner) mHeader.findViewById(R.id.filter_spinner); - mCycleAdapter = new CycleAdapter(getContext(), mCycleSpinner, mCycleListener); + mCycleAdapter = new CycleAdapter(getContext(), new CycleAdapter.SpinnerInterface() { + @Override + public void setAdapter(CycleAdapter cycleAdapter) { + mCycleSpinner.setAdapter(cycleAdapter); + } + + @Override + public void setOnItemSelectedListener(OnItemSelectedListener listener) { + mCycleSpinner.setOnItemSelectedListener(listener); + } + + @Override + public Object getSelectedItem() { + return mCycleSpinner.getSelectedItem(); + } + + @Override + public void setSelection(int position) { + mCycleSpinner.setSelection(position); + } + }, mCycleListener, true); setLoading(true, false); } @@ -470,7 +490,7 @@ public class DataUsageList extends DataUsageBase { @Override public void onItemSelected(AdapterView parent, View view, int position, long id) { final CycleAdapter.CycleItem cycle = (CycleAdapter.CycleItem) - parent.getItemAtPosition(position); + mCycleSpinner.getSelectedItem(); if (LOGD) { Log.d(TAG, "showing cycle " + cycle + ", start=" + cycle.start + ", end=" diff --git a/src/com/android/settings/datausage/SpinnerPreference.java b/src/com/android/settings/datausage/SpinnerPreference.java new file mode 100644 index 00000000000..837288349f3 --- /dev/null +++ b/src/com/android/settings/datausage/SpinnerPreference.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.Context; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceViewHolder; +import android.util.AttributeSet; +import android.view.View; +import android.widget.AdapterView; +import android.widget.Spinner; +import com.android.settings.R; + +public class SpinnerPreference extends Preference implements CycleAdapter.SpinnerInterface { + + private CycleAdapter mAdapter; + private AdapterView.OnItemSelectedListener mListener; + private Object mCurrentObject; + private int mPosition; + + public SpinnerPreference(Context context, AttributeSet attrs) { + super(context, attrs); + setLayoutResource(R.layout.data_usage_cycles); + } + + @Override + public void setAdapter(CycleAdapter cycleAdapter) { + mAdapter = cycleAdapter; + notifyChanged(); + } + + @Override + public void setOnItemSelectedListener(AdapterView.OnItemSelectedListener listener) { + mListener = listener; + } + + @Override + public Object getSelectedItem() { + return mCurrentObject; + } + + @Override + public void setSelection(int position) { + mPosition = position; + mCurrentObject = mAdapter.getItem(mPosition); + notifyChanged(); + } + + @Override + public void onBindViewHolder(PreferenceViewHolder holder) { + super.onBindViewHolder(holder); + Spinner spinner = (Spinner) holder.findViewById(R.id.cycles_spinner); + spinner.setAdapter(mAdapter); + spinner.setSelection(mPosition); + spinner.setOnItemSelectedListener(mOnSelectedListener); + } + + @Override + protected void performClick(View view) { + view.findViewById(R.id.cycles_spinner).performClick(); + } + + private final AdapterView.OnItemSelectedListener mOnSelectedListener + = new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + if (mPosition == position) return; + mPosition = position; + mCurrentObject = mAdapter.getItem(position); + mListener.onItemSelected(parent, view, position, id); + } + + @Override + public void onNothingSelected(AdapterView parent) { + mListener.onNothingSelected(parent); + } + }; +} diff --git a/src/com/android/settings/datausage/UnrestrictedDataAccess.java b/src/com/android/settings/datausage/UnrestrictedDataAccess.java new file mode 100644 index 00000000000..a88da882e8a --- /dev/null +++ b/src/com/android/settings/datausage/UnrestrictedDataAccess.java @@ -0,0 +1,184 @@ +/* + * 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.Application; +import android.content.Context; +import android.os.Bundle; +import android.support.v14.preference.SwitchPreference; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceViewHolder; +import android.view.View; +import com.android.settings.InstrumentedFragment; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.applications.AppStateBaseBridge; +import com.android.settingslib.applications.ApplicationsState; + +import java.util.ArrayList; + +public class UnrestrictedDataAccess extends SettingsPreferenceFragment + implements ApplicationsState.Callbacks, AppStateBaseBridge.Callback, Preference.OnPreferenceChangeListener { + + private ApplicationsState mApplicationsState; + private AppStateDataUsageBridge mDataUsageBridge; + private ApplicationsState.Session mSession; + private DataSaverBackend mDataSaverBackend; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + setPreferenceScreen(getPreferenceManager().createPreferenceScreen(getContext())); + getPreferenceScreen().setOrderingAsAdded(false); + mApplicationsState = ApplicationsState.getInstance( + (Application) getContext().getApplicationContext()); + mDataSaverBackend = new DataSaverBackend(getContext()); + mDataUsageBridge = new AppStateDataUsageBridge(mApplicationsState, this, mDataSaverBackend); + mSession = mApplicationsState.newSession(this); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + setLoading(true, false); + } + + @Override + public void onResume() { + super.onResume(); + mSession.resume(); + mDataUsageBridge.resume(); + } + + @Override + public void onPause() { + super.onPause(); + mDataUsageBridge.pause(); + mSession.pause(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + mSession.release(); + mDataUsageBridge.release(); + } + + @Override + public void onExtraInfoUpdated() { + ArrayList apps = mSession.getAllApps(); + final int N = apps.size(); + for (int i = 0; i < N; i++) { + ApplicationsState.AppEntry entry = apps.get(i); + String key = entry.info.packageName + "|" + entry.info.uid; + AccessPreference preference = (AccessPreference) findPreference(key); + if (preference == null) { + preference = new AccessPreference(getContext(), entry); + preference.setKey(key); + preference.setOnPreferenceChangeListener(this); + getPreferenceScreen().addPreference(preference); + } + AppStateDataUsageBridge.DataUsageState state = + (AppStateDataUsageBridge.DataUsageState) entry.extraInfo; + preference.setChecked(state.isDataSaverWhitelisted); + } + setLoading(false, true); + } + + @Override + public void onRunningStateChanged(boolean running) { + + } + + @Override + public void onPackageListChanged() { + + } + + @Override + public void onRebuildComplete(ArrayList apps) { + + } + + @Override + public void onPackageIconChanged() { + + } + + @Override + public void onPackageSizeChanged(String packageName) { + + } + + @Override + public void onAllSizesComputed() { + + } + + @Override + public void onLauncherInfoChanged() { + + } + + @Override + public void onLoadEntriesCompleted() { + + } + + @Override + protected int getMetricsCategory() { + return InstrumentedFragment.DATA_USAGE_UNRESTRICTED_ACCESS; + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (preference instanceof AccessPreference) { + AccessPreference accessPreference = (AccessPreference) preference; + boolean whitelisted = newValue == Boolean.TRUE; + mDataSaverBackend.setIsWhitelisted(accessPreference.mEntry.info.uid, whitelisted); + ((AppStateDataUsageBridge.DataUsageState) accessPreference.mEntry.extraInfo) + .isDataSaverWhitelisted = whitelisted; + return true; + } + return false; + } + + private class AccessPreference extends SwitchPreference { + private final ApplicationsState.AppEntry mEntry; + + public AccessPreference(Context context, ApplicationsState.AppEntry entry) { + super(context); + mEntry = entry; + mEntry.ensureLabel(getContext()); + setTitle(entry.label); + setChecked(((AppStateDataUsageBridge.DataUsageState) entry.extraInfo) + .isDataSaverWhitelisted); + } + + @Override + public void onBindViewHolder(PreferenceViewHolder holder) { + holder.itemView.post(new Runnable() { + @Override + public void run() { + // Ensure we have an icon before binding. + mApplicationsState.ensureIcon(mEntry); + // This might trigger us to bind again, but it gives an easy way to only load the icon + // once its needed, so its probably worth it. + setIcon(mEntry.icon); + } + }); + super.onBindViewHolder(holder); + } + } +}