OSDN Git Service

Add data saver to settings
authorJason Monk <jmonk@google.com>
Thu, 21 Jan 2016 19:12:11 +0000 (14:12 -0500)
committerJason Monk <jmonk@google.com>
Mon, 25 Jan 2016 20:24:29 +0000 (15:24 -0500)
Bug: 22817899

Change-Id: Ic3055aa6a5baae1653db350313366f180c049cc7

18 files changed:
proguard.flags
res/drawable/ic_data_saver.xml [new file with mode: 0644]
res/layout/data_usage_cycles.xml
res/values/strings.xml
res/xml/app_data_usage.xml
res/xml/data_saver.xml [moved from res/layout/data_usage_app_header.xml with 53% similarity]
res/xml/data_usage.xml
src/com/android/settings/InstrumentedFragment.java
src/com/android/settings/dashboard/conditional/BackgroundDataCondition.java
src/com/android/settings/datausage/AppDataUsage.java
src/com/android/settings/datausage/AppStateDataUsageBridge.java [new file with mode: 0644]
src/com/android/settings/datausage/CycleAdapter.java
src/com/android/settings/datausage/DataSaverBackend.java [new file with mode: 0644]
src/com/android/settings/datausage/DataSaverPreference.java [new file with mode: 0644]
src/com/android/settings/datausage/DataSaverSummary.java [new file with mode: 0644]
src/com/android/settings/datausage/DataUsageList.java
src/com/android/settings/datausage/SpinnerPreference.java [new file with mode: 0644]
src/com/android/settings/datausage/UnrestrictedDataAccess.java [new file with mode: 0644]

index 578ff4d..448cd72 100644 (file)
@@ -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 (file)
index 0000000..426238c
--- /dev/null
@@ -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>
index 5267e26..9c6cc31 100644 (file)
@@ -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">
index e4abbca..b334c57 100644 (file)
     <!-- 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] -->
     <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>
          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>
index b082b56..520b93b 100644 (file)
@@ -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" />
 
         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" />
similarity index 53%
rename from res/layout/data_usage_app_header.xml
rename to res/xml/data_saver.xml
index 8ca391a..5b69cbb 100644 (file)
@@ -1,5 +1,5 @@
-<!--
-     Copyright (C) 2016 The Android Open Source Project
+<?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.
      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">
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    android:title="@string/data_saver_title">
 
-    <include layout="@layout/app_header" />
+    <Preference
+        android:key="unrestricted_access"
+        android:title="@string/unrestricted_data_saver"
+        android:fragment="com.android.settings.datausage.UnrestrictedDataAccess" />
 
-    <View
-        android:layout_width="match_parent"
-        android:layout_height=".5dp"
-        android:background="@android:color/white" />
-
-    <include layout="@layout/apps_filter_spinner" />
-
-</LinearLayout>
+</PreferenceScreen>
index 378496e..0626da9 100644 (file)
             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>
 
index ea39cf3..884e07b 100644 (file)
@@ -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.
index d1bcb12..6bfc538 100644 (file)
@@ -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
index 9ca066f..a65f007 100644 (file)
 
 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 (file)
index 0000000..1aff496
--- /dev/null
@@ -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;
+        }
+    }
+}
index 682cc8a..67e62cb 100644 (file)
  */
 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<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 @@ public class CycleAdapter extends ArrayAdapter<CycleAdapter.CycleItem> {
                 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<CycleAdapter.CycleItem> {
             // 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<CycleAdapter.CycleItem> {
             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 (file)
index 0000000..c38a05c
--- /dev/null
@@ -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 (file)
index 0000000..c286d95
--- /dev/null
@@ -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 (file)
index 0000000..fa24fa3
--- /dev/null
@@ -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);
+    }
+}
index 4aa52ba..bb24aef 100644 (file)
@@ -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 (file)
index 0000000..8372883
--- /dev/null
@@ -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 (file)
index 0000000..a88da88
--- /dev/null
@@ -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);
+        }
+    }
+}