Merge "Background data notification, API clean up."
diff --git a/api/current.txt b/api/current.txt
index d377109..4332833 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -11431,7 +11431,7 @@
     method public android.net.NetworkInfo getActiveNetworkInfo();
     method public android.net.NetworkQuotaInfo getActiveNetworkQuotaInfo();
     method public android.net.NetworkInfo[] getAllNetworkInfo();
-    method public boolean getBackgroundDataSetting();
+    method public deprecated boolean getBackgroundDataSetting();
     method public android.net.NetworkInfo getNetworkInfo(int);
     method public int getNetworkPreference();
     method public static boolean isNetworkTypeValid(int);
@@ -17087,7 +17087,7 @@
     field public static final java.lang.String ALLOWED_GEOLOCATION_ORIGINS = "allowed_geolocation_origins";
     field public static final java.lang.String ALLOW_MOCK_LOCATION = "mock_location";
     field public static final java.lang.String ANDROID_ID = "android_id";
-    field public static final java.lang.String BACKGROUND_DATA = "background_data";
+    field public static final deprecated java.lang.String BACKGROUND_DATA = "background_data";
     field public static final java.lang.String BLUETOOTH_ON = "bluetooth_on";
     field public static final android.net.Uri CONTENT_URI;
     field public static final java.lang.String DATA_ROAMING = "data_roaming";
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 53fd50d..9c96883 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -21,6 +21,7 @@
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.os.Binder;
+import android.os.Build.VERSION_CODES;
 import android.os.RemoteException;
 
 import java.net.InetAddress;
@@ -503,16 +504,19 @@
      * <p>
      * All applications that have background services that use the network
      * should listen to {@link #ACTION_BACKGROUND_DATA_SETTING_CHANGED}.
+     * <p>
+     * As of {@link VERSION_CODES#ICE_CREAM_SANDWICH}, availability of
+     * background data depends on several combined factors, and this method will
+     * always return {@code true}. Instead, when background data is unavailable,
+     * {@link #getActiveNetworkInfo()} will now appear disconnected.
      *
      * @return Whether background data usage is allowed.
      */
+    @Deprecated
     public boolean getBackgroundDataSetting() {
-        try {
-            return mService.getBackgroundDataSetting();
-        } catch (RemoteException e) {
-            // Err on the side of safety
-            return false;
-        }
+        // assume that background data is allowed; final authority is
+        // NetworkInfo which may be blocked.
+        return true;
     }
 
     /**
@@ -525,11 +529,9 @@
      * @see #getBackgroundDataSetting()
      * @hide
      */
+    @Deprecated
     public void setBackgroundDataSetting(boolean allowBackgroundData) {
-        try {
-            mService.setBackgroundDataSetting(allowBackgroundData);
-        } catch (RemoteException e) {
-        }
+        // ignored
     }
 
     /**
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index f391200..1b95b60 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -63,10 +63,6 @@
 
     boolean requestRouteToHostAddress(int networkType, in byte[] hostAddress);
 
-    boolean getBackgroundDataSetting();
-
-    void setBackgroundDataSetting(boolean allowBackgroundData);
-
     boolean getMobileDataEnabled();
 
     void setMobileDataEnabled(boolean enabled);
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index de06f20..554afd2 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3081,6 +3081,7 @@
          * Whether background data usage is allowed by the user. See
          * ConnectivityManager for more info.
          */
+        @Deprecated
         public static final String BACKGROUND_DATA = "background_data";
 
         /**
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 128889c..6aff54e 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3099,7 +3099,7 @@
     <!-- Notification title when mobile data usage has exceeded limit threshold, and has been disabled. [CHAR LIMIT=32] -->
     <string name="data_usage_mobile_limit_title">Mobile data disabled</string>
     <!-- Notification body when data usage has exceeded limit threshold, and has been disabled. [CHAR LIMIT=32] -->
-    <string name="data_usage_limit_body">tap to enable</string>
+    <string name="data_usage_limit_body">Touch to enable</string>
 
     <!-- Notification title when 2G-3G data usage has exceeded limit threshold. [CHAR LIMIT=32] -->
     <string name="data_usage_3g_limit_snoozed_title">2G-3G data limit exceeded</string>
@@ -3110,6 +3110,11 @@
     <!-- Notification body when data usage has exceeded limit threshold. [CHAR LIMIT=32] -->
     <string name="data_usage_limit_snoozed_body"><xliff:g id="size" example="3.8GB">%s</xliff:g> over specified limit</string>
 
+    <!-- Notification title when background data usage is limited. [CHAR LIMIT=32] -->
+    <string name="data_usage_restricted_title">Background data restricted</string>
+    <!-- Notification body when background data usage is limited. [CHAR LIMIT=32] -->
+    <string name="data_usage_restricted_body">Touch to remove restriction</string>
+
     <!-- SSL Certificate dialogs -->
     <!-- Title for an SSL Certificate dialog -->
     <string name="ssl_certificate">Security certificate</string>
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 22ce484..1341dd4 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -163,8 +163,6 @@
     private boolean mTestMode;
     private static ConnectivityService sServiceInstance;
 
-    private AtomicBoolean mBackgroundDataEnabled = new AtomicBoolean(true);
-
     private INetworkManagementService mNetd;
     private INetworkPolicyManager mPolicyManager;
 
@@ -213,13 +211,6 @@
             MAX_NETWORK_STATE_TRACKER_EVENT + 5;
 
     /**
-     * used internally to set the background data preference
-     * arg1 = TRUE for enabled, FALSE for disabled
-     */
-    private static final int EVENT_SET_BACKGROUND_DATA =
-            MAX_NETWORK_STATE_TRACKER_EVENT + 6;
-
-    /**
      * used internally to set enable/disable cellular data
      * arg1 = ENBALED or DISABLED
      */
@@ -317,9 +308,6 @@
         handlerThread.start();
         mHandler = new MyHandler(handlerThread.getLooper());
 
-        mBackgroundDataEnabled.set(Settings.Secure.getInt(context.getContentResolver(),
-                Settings.Secure.BACKGROUND_DATA, 1) == 1);
-
         // setup our unique device name
         if (TextUtils.isEmpty(SystemProperties.get("net.hostname"))) {
             String id = Settings.Secure.getString(context.getContentResolver(),
@@ -1209,35 +1197,6 @@
     }
 
     /**
-     * @see ConnectivityManager#getBackgroundDataSetting()
-     */
-    public boolean getBackgroundDataSetting() {
-        return mBackgroundDataEnabled.get();
-    }
-
-    /**
-     * @see ConnectivityManager#setBackgroundDataSetting(boolean)
-     */
-    public void setBackgroundDataSetting(boolean allowBackgroundDataUsage) {
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.CHANGE_BACKGROUND_DATA_SETTING,
-                "ConnectivityService");
-
-        mBackgroundDataEnabled.set(allowBackgroundDataUsage);
-
-        mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_BACKGROUND_DATA,
-                (allowBackgroundDataUsage ? ENABLED : DISABLED), 0));
-    }
-
-    private void handleSetBackgroundData(boolean enabled) {
-        Settings.Secure.putInt(mContext.getContentResolver(),
-                Settings.Secure.BACKGROUND_DATA, enabled ? 1 : 0);
-        Intent broadcast = new Intent(
-                ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED);
-        mContext.sendBroadcast(broadcast);
-    }
-
-    /**
      * @see ConnectivityManager#getMobileDataEnabled()
      */
     public boolean getMobileDataEnabled() {
@@ -2273,12 +2232,6 @@
                     handleSetNetworkPreference(preference);
                     break;
                 }
-                case EVENT_SET_BACKGROUND_DATA:
-                {
-                    boolean enabled = (msg.arg1 == ENABLED);
-                    handleSetBackgroundData(enabled);
-                    break;
-                }
                 case EVENT_SET_MOBILE_DATA:
                 {
                     boolean enabled = (msg.arg1 == ENABLED);
diff --git a/services/java/com/android/server/net/NetworkPolicyManagerService.java b/services/java/com/android/server/net/NetworkPolicyManagerService.java
index 9c3d166..14d9665 100644
--- a/services/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -89,6 +89,7 @@
 import android.os.Message;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
+import android.provider.Settings;
 import android.telephony.TelephonyManager;
 import android.text.format.Formatter;
 import android.text.format.Time;
@@ -168,6 +169,12 @@
     private static final String ATTR_UID = "uid";
     private static final String ATTR_POLICY = "policy";
 
+    private static final String TAG_ALLOW_BACKGROUND = TAG + ":allowBackground";
+
+    // @VisibleForTesting
+    public static final String ACTION_ALLOW_BACKGROUND =
+            "com.android.server.action.ACTION_ALLOW_BACKGROUND";
+
     private static final long TIME_CACHE_MAX_AGE = DAY_IN_MILLIS;
 
     private static final int MSG_RULES_CHANGED = 0x1;
@@ -185,8 +192,8 @@
 
     private final Object mRulesLock = new Object();
 
-    private boolean mScreenOn;
-    private boolean mRestrictBackground;
+    private volatile boolean mScreenOn;
+    private volatile boolean mRestrictBackground;
 
     /** Defined network policies. */
     private HashMap<NetworkTemplate, NetworkPolicy> mNetworkPolicy = Maps.newHashMap();
@@ -265,6 +272,7 @@
 
             if (mRestrictBackground) {
                 updateRulesForRestrictBackgroundLocked();
+                updateNotificationsLocked();
             }
         }
 
@@ -309,6 +317,10 @@
         mContext.registerReceiver(
                 mStatsReceiver, statsFilter, READ_NETWORK_USAGE_HISTORY, mHandler);
 
+        // listen for restrict background changes from notifications
+        final IntentFilter allowFilter = new IntentFilter(ACTION_ALLOW_BACKGROUND);
+        mContext.registerReceiver(mAllowReceiver, allowFilter, MANAGE_NETWORK_POLICY, mHandler);
+
     }
 
     private IProcessObserver mProcessObserver = new IProcessObserver.Stub() {
@@ -402,6 +414,20 @@
     };
 
     /**
+     * Receiver that watches for {@link Notification} control of
+     * {@link #mRestrictBackground}.
+     */
+    private BroadcastReceiver mAllowReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            // on background handler thread, and verified MANAGE_NETWORK_POLICY
+            // permission above.
+
+            setRestrictBackground(false);
+        }
+    };
+
+    /**
      * Observer that watches for {@link INetworkManagementService} alerts.
      */
     private INetworkManagementEventObserver mAlertObserver = new NetworkAlertObserver() {
@@ -494,6 +520,13 @@
                 notifyUnderLimitLocked(policy.template);
             }
         }
+
+        // ongoing notification when restricting background data
+        if (mRestrictBackground) {
+            enqueueRestrictedNotification(TAG_ALLOW_BACKGROUND);
+        } else {
+            cancelNotification(TAG_ALLOW_BACKGROUND);
+        }
     }
 
     /**
@@ -614,16 +647,52 @@
     }
 
     /**
-     * Cancel any notification for combined {@link NetworkPolicy} and specific
-     * type, like {@link #TYPE_LIMIT}.
+     * Show ongoing notification to reflect that {@link #mRestrictBackground}
+     * has been enabled.
      */
-    private void cancelNotification(NetworkPolicy policy, int type) {
-        final String tag = buildNotificationTag(policy, type);
+    private void enqueueRestrictedNotification(String tag) {
+        final Resources res = mContext.getResources();
+        final Notification.Builder builder = new Notification.Builder(mContext);
+
+        final CharSequence title = res.getText(R.string.data_usage_restricted_title);
+        final CharSequence body = res.getString(R.string.data_usage_restricted_body);
+
+        builder.setOnlyAlertOnce(true);
+        builder.setOngoing(true);
+        builder.setSmallIcon(R.drawable.ic_menu_info_details);
+        builder.setTicker(title);
+        builder.setContentTitle(title);
+        builder.setContentText(body);
+
+        final Intent intent = buildAllowBackgroundDataIntent();
+        builder.setContentIntent(
+                PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT));
 
         // TODO: move to NotificationManager once we can mock it
         try {
             final String packageName = mContext.getPackageName();
-            mNotifManager.cancelNotificationWithTag(packageName, tag, 0x0);
+            final int[] idReceived = new int[1];
+            mNotifManager.enqueueNotificationWithTag(packageName, tag,
+                    0x0, builder.getNotification(), idReceived);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "problem during enqueueNotification: " + e);
+        }
+    }
+
+    /**
+     * Cancel any notification for combined {@link NetworkPolicy} and specific
+     * type, like {@link #TYPE_LIMIT}.
+     */
+    private void cancelNotification(NetworkPolicy policy, int type) {
+        cancelNotification(buildNotificationTag(policy, type));
+    }
+
+    private void cancelNotification(String tag) {
+        // TODO: move to NotificationManager once we can mock it
+        try {
+            final String packageName = mContext.getPackageName();
+            mNotifManager.cancelNotificationWithTag(
+                    packageName, tag, 0x0);
         } catch (RemoteException e) {
             Slog.w(TAG, "problem during enqueueNotification: " + e);
         }
@@ -731,15 +800,9 @@
             final boolean hasLimit = policy.limitBytes != LIMIT_DISABLED;
             final boolean hasWarning = policy.warningBytes != WARNING_DISABLED;
 
-            if (hasLimit || hasWarning) {
-                final long quotaBytes;
-                if (hasLimit) {
-                    // remaining "quota" is based on usage in current cycle
-                    quotaBytes = Math.max(0, policy.limitBytes - total);
-                } else {
-                    // to track warning alert later, use a high quota
-                    quotaBytes = Long.MAX_VALUE;
-                }
+            if (hasLimit) {
+                // remaining "quota" is based on usage in current cycle
+                final long quotaBytes = Math.max(0, policy.limitBytes - total);
 
                 if (ifaces.length > 1) {
                     // TODO: switch to shared quota once NMS supports
@@ -754,16 +817,6 @@
                     }
                 }
             }
-
-            if (hasWarning) {
-                final long alertBytes = Math.max(0, policy.warningBytes - total);
-                for (String iface : ifaces) {
-                    removeInterfaceAlert(iface);
-                    if (alertBytes > 0) {
-                        setInterfaceAlert(iface, alertBytes);
-                    }
-                }
-            }
         }
 
         // remove quota on any trailing interfaces
@@ -839,11 +892,7 @@
                             mRestrictBackground = readBooleanAttribute(
                                     in, ATTR_RESTRICT_BACKGROUND);
                         } else {
-                            try {
-                                mRestrictBackground = !mConnManager.getBackgroundDataSetting();
-                            } catch (RemoteException e) {
-                                mRestrictBackground = false;
-                            }
+                            mRestrictBackground = false;
                         }
 
                     } else if (TAG_NETWORK_POLICY.equals(tag)) {
@@ -879,6 +928,7 @@
 
         } catch (FileNotFoundException e) {
             // missing policy is okay, probably first boot
+            upgradeLegacyBackgroundData();
         } catch (IOException e) {
             Slog.e(TAG, "problem reading network stats", e);
         } catch (XmlPullParserException e) {
@@ -888,6 +938,22 @@
         }
     }
 
+    /**
+     * Upgrade legacy background data flags, notifying listeners of one last
+     * change to always-true.
+     */
+    private void upgradeLegacyBackgroundData() {
+        mRestrictBackground = Settings.Secure.getInt(
+                mContext.getContentResolver(), Settings.Secure.BACKGROUND_DATA, 1) != 1;
+
+        // kick off one last broadcast if restricted
+        if (mRestrictBackground) {
+            final Intent broadcast = new Intent(
+                    ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED);
+            mContext.sendBroadcast(broadcast);
+        }
+    }
+
     private void writePolicyLocked() {
         if (LOGV) Slog.v(TAG, "writePolicyLocked()");
 
@@ -1057,6 +1123,7 @@
         synchronized (mRulesLock) {
             mRestrictBackground = restrictBackground;
             updateRulesForRestrictBackgroundLocked();
+            updateNotificationsLocked();
             writePolicyLocked();
         }
     }
@@ -1420,6 +1487,10 @@
         return telephony.getSubscriberId();
     }
 
+    private static Intent buildAllowBackgroundDataIntent() {
+        return new Intent(ACTION_ALLOW_BACKGROUND);
+    }
+
     private static Intent buildNetworkOverLimitIntent(NetworkTemplate template) {
         final Intent intent = new Intent();
         intent.setComponent(new ComponentName(