Add data saver to settings
Bug: 22817899
Change-Id: Ic3055aa6a5baae1653db350313366f180c049cc7
diff --git a/proguard.flags b/proguard.flags
index 578ff4d..448cd72 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 0000000..426238c
--- /dev/null
+++ b/res/drawable/ic_data_saver.xml
@@ -0,0 +1,28 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="
+ M9.0,16.0l2.0,0.0L11.0,8.0L9.0,8.0l0.0,8.0z
+ m3.0,-14.0C6.48,2.0 2.0,6.48 2.0,12.0s4.48,10.0 10.0,10.0 10.0,-4.48 10.0,-10.0S17.52,2.0 12.0,2.0z
+ m0.0,18.0c-4.41,0.0 -8.0,-3.59 -8.0,-8.0s3.59,-8.0 8.0,-8.0 8.0,3.59 8.0,8.0 -3.59,8.0 -8.0,8.0z
+ m1.0,-4.0l2.0,0.0l0.0,-8.0l-2.0,0.0l0.0,8.0z"/>
+</vector>
diff --git a/res/layout/data_usage_app_header.xml b/res/layout/data_usage_app_header.xml
deleted file mode 100644
index 8ca391a..0000000
--- a/res/layout/data_usage_app_header.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<!--
- 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.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical">
-
- <include layout="@layout/app_header" />
-
- <View
- android:layout_width="match_parent"
- android:layout_height=".5dp"
- android:background="@android:color/white" />
-
- <include layout="@layout/apps_filter_spinner" />
-
-</LinearLayout>
diff --git a/res/layout/data_usage_cycles.xml b/res/layout/data_usage_cycles.xml
index 5267e26..9c6cc31 100644
--- a/res/layout/data_usage_cycles.xml
+++ b/res/layout/data_usage_cycles.xml
@@ -17,7 +17,7 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:minHeight="?android:attr/listPreferredItemHeight"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:orientation="horizontal">
diff --git a/res/values/strings.xml b/res/values/strings.xml
index e4abbca..b334c57 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -4914,9 +4914,9 @@
<!-- Button title for launching application-specific data usage settings. [CHAR LIMIT=32] -->
<string name="data_usage_app_settings">App settings</string>
<!-- Checkbox label that restricts background data usage of a specific application. [CHAR LIMIT=40] -->
- <string name="data_usage_app_restrict_background">Restrict app background data</string>
- <!-- Summary message for checkbox that restricts background data usage of a specific application. [CHAR LIMIT=64] -->
- <string name="data_usage_app_restrict_background_summary">Disable background data on cellular networks.</string>
+ <string name="data_usage_app_restrict_background">Background data</string>
+ <!-- Summary message for checkbox that restricts background data usage of a specific application. [CHAR LIMIT=NONE] -->
+ <string name="data_usage_app_restrict_background_summary">Enable usage of cellular data in the background</string>
<!-- Summary message for checkbox that restricts background data usage of a specific application when no networks have been limited. [CHAR LIMIT=84] -->
<string name="data_usage_app_restrict_background_summary_disabled">To restrict background data for this app, first set a cellular data limit.</string>
<!-- Title of dialog shown when user restricts background data usage of a specific application. [CHAR LIMIT=48] -->
@@ -6870,7 +6870,7 @@
<string name="condition_cellular_summary">Internet is available only via Wi-Fi</string>
<!-- Title of condition that background data is off [CHAR LIMIT=30] -->
- <string name="condition_bg_data_title">Background data is off</string>
+ <string name="condition_bg_data_title">Data Saver is on</string>
<!-- Summary of condition that background data is off [CHAR LIMIT=NONE] -->
<string name="condition_bg_data_summary">Background data is only available via Wi-Fi. This may affect some apps or services when Wi-Fi is not available.</string>
@@ -6966,4 +6966,28 @@
the code to do that -->
<string name="data_usage_other_apps" translatable="false">Other apps included in usage</string>
+ <!-- Description of number of apps allowed to ignore data saver [CHAR LIMIT=NONE] -->
+ <plurals name="data_saver_unrestricted_summary">
+ <item quantity="one">1 app allowed to use unrestricted data when Data Saver is on</item>
+ <item quantity="other"><xliff:g id="count" example="10">%1$d</xliff:g> apps allowed to use unrestricted data when Data Saver is on</item>
+ </plurals>
+
+ <!-- Name of Data Saver screens [CHAR LIMIT=30] -->
+ <string name="data_saver_title">Data Saver</string>
+
+ <!-- Button that leads to list of apps with unrestricted data access [CHAR LIMIT=60] -->
+ <string name="unrestricted_data_saver">Unrestricted data access</string>
+
+ <!-- Summary for the data saver feature being on [CHAR LIMIT=NONE] -->
+ <string name="data_saver_on">On</string>
+
+ <!-- Summary for the data saver feature being off [CHAR LIMIT=NONE] -->
+ <string name="data_saver_off">Off</string>
+
+ <!-- Title for switch to allow app unrestricted data usage [CHAR LIMIT=30] -->
+ <string name="unrestricted_app_title">Unrestricted data usage</string>
+
+ <!-- Title for switch to allow app unrestricted data usage [CHAR LIMIT=30] -->
+ <string name="unrestricted_app_summary">Allow unrestricted data access when Data Saver is on</string>
+
</resources>
diff --git a/res/xml/app_data_usage.xml b/res/xml/app_data_usage.xml
index b082b56..520b93b 100644
--- a/res/xml/app_data_usage.xml
+++ b/res/xml/app_data_usage.xml
@@ -17,6 +17,9 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
android:title="@string/data_usage_summary_title">
+ <com.android.settings.datausage.SpinnerPreference
+ android:key="cycle" />
+
<com.android.settings.applications.SpacePreference
android:layout_height="8dp" />
@@ -50,6 +53,11 @@
android:title="@string/data_usage_app_restrict_background"
android:summary="@string/data_usage_app_restrict_background_summary" />
+ <SwitchPreference
+ android:key="unrestricted_data_saver"
+ android:title="@string/unrestricted_app_title"
+ android:summary="@string/unrestricted_app_summary" />
+
<PreferenceCategory
android:key="app_list"
android:title="@string/data_usage_other_apps" />
diff --git a/res/xml/data_saver.xml b/res/xml/data_saver.xml
new file mode 100644
index 0000000..5b69cbb
--- /dev/null
+++ b/res/xml/data_saver.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+ android:title="@string/data_saver_title">
+
+ <Preference
+ android:key="unrestricted_access"
+ android:title="@string/unrestricted_data_saver"
+ android:fragment="com.android.settings.datausage.UnrestrictedDataAccess" />
+
+</PreferenceScreen>
diff --git a/res/xml/data_usage.xml b/res/xml/data_usage.xml
index 378496e..0626da9 100644
--- a/res/xml/data_usage.xml
+++ b/res/xml/data_usage.xml
@@ -29,9 +29,10 @@
android:key="limit_summary"
android:selectable="false" />
- <com.android.settings.datausage.RestrictBackgroundDataPreference
+ <com.android.settings.datausage.DataSaverPreference
android:key="restrict_background"
- android:title="@string/data_usage_menu_restrict_background" />
+ android:title="@string/data_saver_title"
+ android:fragment="com.android.settings.datausage.DataSaverSummary" />
</PreferenceCategory>
diff --git a/src/com/android/settings/InstrumentedFragment.java b/src/com/android/settings/InstrumentedFragment.java
index ea39cf3..884e07b 100644
--- a/src/com/android/settings/InstrumentedFragment.java
+++ b/src/com/android/settings/InstrumentedFragment.java
@@ -41,6 +41,8 @@
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 d1bcb12..6bfc538 100644
--- a/src/com/android/settings/dashboard/conditional/BackgroundDataCondition.java
+++ b/src/com/android/settings/dashboard/conditional/BackgroundDataCondition.java
@@ -34,7 +34,7 @@
@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 9ca066f..a65f007 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.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 @@
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 @@
private Drawable mIcon;
private CharSequence mLabel;
private INetworkStatsSession mStatsSession;
- private Spinner mCycleSpinner;
private CycleAdapter mCycleAdapter;
private long mStart;
@@ -86,6 +84,9 @@
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 @@
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 @@
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 @@
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 @@
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 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 @@
}
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 @@
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 0000000..1aff496
--- /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<AppEntry> 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 682cc8a..67e62cb 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 @@
public class CycleAdapter extends ArrayAdapter<CycleAdapter.CycleItem> {
- 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 @@
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 @@
// 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 @@
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 0000000..c38a05c
--- /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<Listener> 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 0000000..c286d95
--- /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 0000000..fa24fa3
--- /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 4aa52ba..bb24aef 100644
--- a/src/com/android/settings/datausage/DataUsageList.java
+++ b/src/com/android/settings/datausage/DataUsageList.java
@@ -147,7 +147,27 @@
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 @@
@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 0000000..8372883
--- /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 0000000..a88da88
--- /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<ApplicationsState.AppEntry> 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<ApplicationsState.AppEntry> 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);
+ }
+ }
+}