OSDN Git Service

Remove network access for idle apps
authorAmith Yamasani <yamasani@google.com>
Sat, 25 Apr 2015 02:06:07 +0000 (19:06 -0700)
committerAmith Yamasani <yamasani@google.com>
Wed, 29 Apr 2015 21:21:53 +0000 (14:21 -0700)
Track apps going in and out of idle in the NetworkPolicyManagerService.
Apply DROP rules in firewall controller if app is to be blacklisted
for network access.

Firewall can now be in whitelist (old) or blacklist mode. When in
blacklist, it allows all by default and we can selectively DENY
some uids.

Track app idle in UsageStats and update periodically.
Track charging/discharging states.

TODO: Check for appidle temporary parole state

Bug: 20066058
Change-Id: Ia65d7544204b3bcb78a517310ef4adcc05aac6fb

core/java/android/net/NetworkPolicyManager.java
core/java/android/os/INetworkManagementService.aidl
services/core/java/com/android/server/ConnectivityService.java
services/core/java/com/android/server/NetworkManagementService.java
services/core/java/com/android/server/net/LockdownVpnTracker.java
services/core/java/com/android/server/net/NetworkPolicyManagerService.java
services/usage/java/com/android/server/usage/UsageStatsService.java

index b4c7b2b..ecc3fb4 100644 (file)
@@ -41,6 +41,7 @@ import java.util.HashSet;
  */
 public class NetworkPolicyManager {
 
+    /* POLICY_* are masks and can be ORed */
     /** No specific network policy, use system default. */
     public static final int POLICY_NONE = 0x0;
     /** Reject network usage on metered networks when application in background. */
@@ -48,10 +49,17 @@ public class NetworkPolicyManager {
     /** Allow network use (metered or not) in the background in battery save mode. */
     public static final int POLICY_ALLOW_BACKGROUND_BATTERY_SAVE = 0x2;
 
+    /* RULE_* are not masks and they must be exclusive */
     /** All network traffic should be allowed. */
     public static final int RULE_ALLOW_ALL = 0x0;
     /** Reject traffic on metered networks. */
     public static final int RULE_REJECT_METERED = 0x1;
+    /** Reject traffic on all networks. */
+    public static final int RULE_REJECT_ALL = 0x2;
+
+    public static final int FIREWALL_RULE_DEFAULT = 0;
+    public static final int FIREWALL_RULE_ALLOW = 1;
+    public static final int FIREWALL_RULE_DENY = 2;
 
     private static final boolean ALLOW_PLATFORM_APP_POLICY = true;
 
@@ -80,7 +88,7 @@ public class NetworkPolicyManager {
      * Set policy flags for specific UID.
      *
      * @param policy {@link #POLICY_NONE} or combination of flags like
-     * {@link #POLICY_REJECT_METERED_BACKGROUND}, {@link #POLICY_ALLOW_BACKGROUND_BATTERY_SAVE}.
+     * {@link #POLICY_REJECT_METERED_BACKGROUND} or {@link #POLICY_ALLOW_BACKGROUND_BATTERY_SAVE}.
      */
     public void setUidPolicy(int uid, int policy) {
         try {
@@ -322,6 +330,8 @@ public class NetworkPolicyManager {
         fout.write("[");
         if ((rules & RULE_REJECT_METERED) != 0) {
             fout.write("REJECT_METERED");
+        } else if ((rules & RULE_REJECT_ALL) != 0) {
+            fout.write("REJECT_ALL");
         }
         fout.write("]");
     }
index f93550a..b29e8d0 100644 (file)
@@ -342,7 +342,7 @@ interface INetworkManagementService
     void setFirewallInterfaceRule(String iface, boolean allow);
     void setFirewallEgressSourceRule(String addr, boolean allow);
     void setFirewallEgressDestRule(String addr, int port, boolean allow);
-    void setFirewallUidRule(int uid, boolean allow);
+    void setFirewallUidRule(int uid, int rule);
 
     /**
      * Set all packets from users in ranges to go through VPN specified by netId.
index 12a99b0..1a75b8a 100644 (file)
@@ -24,6 +24,7 @@ import static android.net.ConnectivityManager.TYPE_VPN;
 import static android.net.ConnectivityManager.getNetworkTypeName;
 import static android.net.ConnectivityManager.isNetworkTypeValid;
 import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
+import static android.net.NetworkPolicyManager.RULE_REJECT_ALL;
 import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
 
 import android.annotation.Nullable;
@@ -832,7 +833,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
             uidRules = mUidRules.get(uid, RULE_ALLOW_ALL);
         }
 
-        if (networkCostly && (uidRules & RULE_REJECT_METERED) != 0) {
+        if ((uidRules & RULE_REJECT_ALL) != 0
+                || (networkCostly && (uidRules & RULE_REJECT_METERED) != 0)) {
             return true;
         }
 
@@ -3490,7 +3492,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
             synchronized(mRulesLock) {
                 uidRules = mUidRules.get(uid, RULE_ALLOW_ALL);
             }
-            if ((uidRules & RULE_REJECT_METERED) != 0) {
+            if ((uidRules & (RULE_REJECT_METERED | RULE_REJECT_ALL)) != 0) {
                 // we could silently fail or we can filter the available nets to only give
                 // them those they have access to.  Chose the more useful
                 networkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
index b5b62b4..f6998ca 100644 (file)
@@ -43,6 +43,7 @@ import android.net.InterfaceConfiguration;
 import android.net.IpPrefix;
 import android.net.LinkAddress;
 import android.net.Network;
+import android.net.NetworkPolicyManager;
 import android.net.NetworkStats;
 import android.net.NetworkUtils;
 import android.net.RouteInfo;
@@ -107,8 +108,8 @@ import java.util.concurrent.CountDownLatch;
  */
 public class NetworkManagementService extends INetworkManagementService.Stub
         implements Watchdog.Monitor {
-    private static final String TAG = "NetworkManagementService";
-    private static final boolean DBG = false;
+    private static final String TAG = "NetworkManagement";
+    private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
     private static final String NETD_TAG = "NetdConnector";
     private static final String NETD_SOCKET_NAME = "netd";
 
@@ -188,6 +189,9 @@ public class NetworkManagementService extends INetworkManagementService.Stub
     /** Set of UIDs with cleartext penalties. */
     @GuardedBy("mQuotaLock")
     private SparseIntArray mUidCleartextPolicy = new SparseIntArray();
+    /** Set of UIDs that are to be blocked/allowed by firewall controller. */
+    @GuardedBy("mQuotaLock")
+    private SparseIntArray mUidFirewallRules = new SparseIntArray();
 
     private Object mIdleTimerLock = new Object();
     /** Set of interfaces with active idle timers. */
@@ -563,10 +567,19 @@ public class NetworkManagementService extends INetworkManagementService.Stub
                     setUidCleartextNetworkPolicy(local.keyAt(i), local.valueAt(i));
                 }
             }
-        }
 
-        // TODO: Push any existing firewall state
-        setFirewallEnabled(mFirewallEnabled || LockdownVpnTracker.isEnabled());
+            setFirewallEnabled(mFirewallEnabled || LockdownVpnTracker.isEnabled());
+
+            size = mUidFirewallRules.size();
+            if (size > 0) {
+                Slog.d(TAG, "Pushing " + size + " active firewall UID rules");
+                final SparseIntArray uidFirewallRules = mUidFirewallRules;
+                mUidFirewallRules = new SparseIntArray();
+                for (int i = 0; i < uidFirewallRules.size(); i++) {
+                    setFirewallUidRule(uidFirewallRules.keyAt(i), uidFirewallRules.valueAt(i));
+                }
+            }
+        }
     }
 
     /**
@@ -1899,7 +1912,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub
     public void setFirewallEnabled(boolean enabled) {
         enforceSystemUid();
         try {
-            mConnector.execute("firewall", enabled ? "enable" : "disable");
+            mConnector.execute("firewall", "enable", enabled ? "whitelist" : "blacklist");
             mFirewallEnabled = enabled;
         } catch (NativeDaemonConnectorException e) {
             throw e.rethrowAsParcelableException();
@@ -1949,14 +1962,48 @@ public class NetworkManagementService extends INetworkManagementService.Stub
     }
 
     @Override
-    public void setFirewallUidRule(int uid, boolean allow) {
+    public void setFirewallUidRule(int uid, int rule) {
         enforceSystemUid();
-        Preconditions.checkState(mFirewallEnabled);
-        final String rule = allow ? "allow" : "deny";
-        try {
-            mConnector.execute("firewall", "set_uid_rule", uid, rule);
-        } catch (NativeDaemonConnectorException e) {
-            throw e.rethrowAsParcelableException();
+        if (rule == NetworkPolicyManager.FIREWALL_RULE_ALLOW) {
+            Preconditions.checkState(mFirewallEnabled);
+        }
+        synchronized (mQuotaLock) {
+            final int oldUidFirewallRule = mUidFirewallRules.get(uid);
+            if (DBG) {
+                Slog.d(TAG, "oldRule = " + oldUidFirewallRule
+                        + ", newRule=" + rule + " for uid=" + uid);
+            }
+            if (oldUidFirewallRule == rule) {
+                if (DBG) Slog.d(TAG, "!!!!! Skipping change");
+                // TODO: eventually consider throwing
+                return;
+            }
+
+            try {
+                String ruleName;
+                if (isFirewallEnabled()) { // Whitelist mode
+                    if (rule == NetworkPolicyManager.FIREWALL_RULE_ALLOW) {
+                        ruleName = "allow";
+                    } else {
+                        ruleName = "deny";
+                    }
+                } else { // Blacklist mode
+                    if (rule == NetworkPolicyManager.FIREWALL_RULE_DENY) {
+                        ruleName = "deny";
+                    } else {
+                        ruleName = "allow";
+                    }
+                }
+
+                if (rule == NetworkPolicyManager.FIREWALL_RULE_DEFAULT) {
+                    mUidFirewallRules.delete(uid);
+                } else {
+                    mUidFirewallRules.put(uid, rule);
+                }
+                mConnector.execute("firewall", "set_uid_rule", uid, ruleName);
+            } catch (NativeDaemonConnectorException e) {
+                throw e.rethrowAsParcelableException();
+            }
         }
     }
 
@@ -2072,6 +2119,18 @@ public class NetworkManagementService extends INetworkManagementService.Stub
             pw.println("]");
         }
 
+        synchronized (mUidFirewallRules) {
+            pw.print("UID firewall rule: [");
+            final int size = mUidFirewallRules.size();
+            for (int i = 0; i < size; i++) {
+                pw.print(mUidFirewallRules.keyAt(i));
+                pw.print(":");
+                pw.print(mUidFirewallRules.valueAt(i));
+                if (i < size - 1) pw.print(",");
+            }
+            pw.println("]");
+        }
+
         synchronized (mIdleTimerLock) {
             pw.println("Idle timers:");
             for (HashMap.Entry<String, IdleTimerParams> ent : mActiveIdleTimers.entrySet()) {
index 6ffe6ac..0f88883 100644 (file)
@@ -17,6 +17,8 @@
 package com.android.server.net;
 
 import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
+import static android.net.NetworkPolicyManager.FIREWALL_RULE_ALLOW;
+import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT;
 
 import android.app.Notification;
 import android.app.NotificationManager;
@@ -31,6 +33,7 @@ import android.net.LinkAddress;
 import android.net.NetworkInfo;
 import android.net.NetworkInfo.DetailedState;
 import android.net.NetworkInfo.State;
+import android.net.NetworkPolicyManager;
 import android.os.INetworkManagementService;
 import android.os.RemoteException;
 import android.security.Credentials;
@@ -198,8 +201,8 @@ public class LockdownVpnTracker {
                     setFirewallEgressSourceRule(addr, true);
                 }
 
-                mNetService.setFirewallUidRule(ROOT_UID, true);
-                mNetService.setFirewallUidRule(Os.getuid(), true);
+                mNetService.setFirewallUidRule(ROOT_UID, FIREWALL_RULE_ALLOW);
+                mNetService.setFirewallUidRule(Os.getuid(), FIREWALL_RULE_ALLOW);
 
                 mErrorCount = 0;
                 mAcceptedIface = iface;
@@ -288,8 +291,8 @@ public class LockdownVpnTracker {
                     setFirewallEgressSourceRule(addr, false);
                 }
 
-                mNetService.setFirewallUidRule(ROOT_UID, false);
-                mNetService.setFirewallUidRule(Os.getuid(), false);
+                mNetService.setFirewallUidRule(ROOT_UID, FIREWALL_RULE_DEFAULT);
+                mNetService.setFirewallUidRule(Os.getuid(), FIREWALL_RULE_DEFAULT);
 
                 mAcceptedSourceAddr = null;
             }
index e45092c..57df1c3 100644 (file)
@@ -36,11 +36,14 @@ import static android.net.NetworkPolicy.LIMIT_DISABLED;
 import static android.net.NetworkPolicy.SNOOZE_NEVER;
 import static android.net.NetworkPolicy.WARNING_DISABLED;
 import static android.net.NetworkPolicyManager.EXTRA_NETWORK_TEMPLATE;
+import static android.net.NetworkPolicyManager.FIREWALL_RULE_DENY;
+import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT;
 import static android.net.NetworkPolicyManager.POLICY_ALLOW_BACKGROUND_BATTERY_SAVE;
 import static android.net.NetworkPolicyManager.POLICY_NONE;
 import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
 import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
 import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
+import static android.net.NetworkPolicyManager.RULE_REJECT_ALL;
 import static android.net.NetworkPolicyManager.computeLastCycleBoundary;
 import static android.net.NetworkPolicyManager.dumpPolicy;
 import static android.net.NetworkPolicyManager.dumpRules;
@@ -80,6 +83,8 @@ import android.app.INotificationManager;
 import android.app.IProcessObserver;
 import android.app.Notification;
 import android.app.PendingIntent;
+import android.app.usage.UsageStatsManagerInternal;
+import android.app.usage.UsageStatsManagerInternal.AppIdleStateChangeListener;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -88,6 +93,7 @@ import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.net.ConnectivityManager;
@@ -140,8 +146,6 @@ import android.util.SparseIntArray;
 import android.util.TrustedTime;
 import android.util.Xml;
 
-import com.android.server.AppOpsService;
-import com.android.server.DeviceIdleController;
 import libcore.io.IoUtils;
 
 import com.android.internal.R;
@@ -149,6 +153,8 @@ import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.AppOpsService;
+import com.android.server.DeviceIdleController;
 import com.android.server.LocalServices;
 import com.android.server.SystemConfig;
 import com.google.android.collect.Lists;
@@ -176,7 +182,8 @@ import java.util.List;
  * and delivers to listeners, such as {@link ConnectivityManager}, for
  * enforcement.
  */
-public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
+public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub
+        implements AppIdleStateChangeListener {
     private static final String TAG = "NetworkPolicy";
     private static final boolean LOGD = false;
     private static final boolean LOGV = false;
@@ -244,6 +251,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
     private final IPowerManager mPowerManager;
     private final INetworkStatsService mNetworkStats;
     private final INetworkManagementService mNetworkManager;
+    private UsageStatsManagerInternal mUsageStats;
     private final TrustedTime mTime;
 
     private IConnectivityManager mConnManager;
@@ -368,6 +376,10 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
             return;
         }
 
+        mUsageStats = LocalServices.getService(UsageStatsManagerInternal.class);
+
+        final PackageManager pm = mContext.getPackageManager();
+
         synchronized (mRulesLock) {
             updatePowerSaveWhitelistLocked();
             mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
@@ -460,6 +472,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
                 WifiManager.NETWORK_STATE_CHANGED_ACTION);
         mContext.registerReceiver(mWifiStateReceiver, wifiStateFilter, null, mHandler);
 
+        mUsageStats.addAppIdleStateChangeListener(this);
+
     }
 
     private IProcessObserver mProcessObserver = new IProcessObserver.Stub() {
@@ -568,12 +582,17 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
             final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
             if (userId == -1) return;
 
-            synchronized (mRulesLock) {
-                // Remove any policies for given user; both cleaning up after a
-                // USER_REMOVED, and one last sanity check during USER_ADDED
-                removePoliciesForUserLocked(userId);
-                // Update global restrict for new user
-                updateRulesForGlobalChangeLocked(true);
+            switch (action) {
+                case ACTION_USER_REMOVED:
+                case ACTION_USER_ADDED:
+                    synchronized (mRulesLock) {
+                        // Remove any policies for given user; both cleaning up after a
+                        // USER_REMOVED, and one last sanity check during USER_ADDED
+                        removePoliciesForUserLocked(userId);
+                        // Update global restrict for new user
+                        updateRulesForGlobalChangeLocked(true);
+                    }
+                    break;
             }
         }
     };
@@ -2040,6 +2059,22 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
         return false;
     }
 
+    private boolean isUidIdle(int uid) {
+        final String[] packages = mContext.getPackageManager().getPackagesForUid(uid);
+        final int userId = UserHandle.getUserId(uid);
+
+        for (String packageName : packages) {
+            if (!mUsageStats.isAppIdle(packageName, userId)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Applies network rules to bandwidth and firewall controllers based on uid policy.
+     * @param uid The uid for which to apply the latest policy
+     */
     void updateRulesForUidLocked(int uid) {
         if (!isUidValidForRules(uid)) return;
 
@@ -2056,10 +2091,14 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
 
         final int uidPolicy = mUidPolicy.get(uid, POLICY_NONE);
         final boolean uidForeground = isUidForegroundLocked(uid);
+        final boolean uidIdle = isUidIdle(uid);
 
         // derive active rules based on policy and active state
+
         int uidRules = RULE_ALLOW_ALL;
-        if (!uidForeground && (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0) {
+        if (uidIdle && !mPowerSaveWhitelistAppIds.get(UserHandle.getAppId(uid))) {
+            uidRules = RULE_REJECT_ALL;
+        } else if (!uidForeground && (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0) {
             // uid in background, and policy says to block metered data
             uidRules = RULE_REJECT_METERED;
         } else if (mRestrictBackground) {
@@ -2078,7 +2117,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
             }
         }
 
-        // TODO: only dispatch when rules actually change
+        final int oldRules = mUidRules.get(uid);
 
         if (uidRules == RULE_ALLOW_ALL) {
             mUidRules.delete(uid);
@@ -2086,11 +2125,24 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
             mUidRules.put(uid, uidRules);
         }
 
+        // Update bandwidth rules if necessary
+        final boolean oldRejectMetered = (oldRules & RULE_REJECT_METERED) != 0;
         final boolean rejectMetered = (uidRules & RULE_REJECT_METERED) != 0;
-        setUidNetworkRules(uid, rejectMetered);
+        if (oldRejectMetered != rejectMetered) {
+            setUidNetworkRules(uid, rejectMetered);
+        }
+
+        // Update firewall rules if necessary
+        final boolean oldFirewallReject = (oldRules & RULE_REJECT_ALL) != 0;
+        final boolean firewallReject = (uidRules & RULE_REJECT_ALL) != 0;
+        if (oldFirewallReject != firewallReject) {
+            setUidFirewallRules(uid, firewallReject);
+        }
 
         // dispatch changed rule to existing listeners
-        mHandler.obtainMessage(MSG_RULES_CHANGED, uid, uidRules).sendToTarget();
+        if (oldRules != uidRules) {
+            mHandler.obtainMessage(MSG_RULES_CHANGED, uid, uidRules).sendToTarget();
+        }
 
         try {
             // adjust stats accounting based on foreground status
@@ -2100,6 +2152,18 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
         }
     }
 
+    @Override
+    public void onAppIdleStateChanged(String packageName, int userId, boolean idle) {
+        try {
+            int uid = mContext.getPackageManager().getPackageUid(packageName, userId);
+            synchronized (mRulesLock) {
+                updateRulesForUidLocked(uid);
+            }
+        } catch (NameNotFoundException nnfe) {
+            return;
+        }
+    }
+
     private Handler.Callback mHandlerCallback = new Handler.Callback() {
         @Override
         public boolean handleMessage(Message msg) {
@@ -2223,6 +2287,22 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
         }
     }
 
+    /**
+     * Add or remove a uid to the firewall blacklist for all network ifaces.
+     * @param uid
+     * @param rejectOnAll
+     */
+    private void setUidFirewallRules(int uid, boolean rejectOnAll) {
+        try {
+            mNetworkManager.setFirewallUidRule(uid,
+                    rejectOnAll ? FIREWALL_RULE_DENY : FIREWALL_RULE_DEFAULT);
+        } catch (IllegalStateException e) {
+            Log.wtf(TAG, "problem setting firewall uid rules", e);
+        } catch (RemoteException e) {
+            // ignored; service lives in system_server
+        }
+    }
+
     private long getTotalBytes(NetworkTemplate template, long start, long end) {
         try {
             return mNetworkStats.getNetworkTotalBytes(template, start, end);
index 04984d3..c2e61c6 100644 (file)
@@ -28,7 +28,6 @@ import android.app.usage.UsageEvents.Event;
 import android.app.usage.UsageStats;
 import android.app.usage.UsageStatsManagerInternal;
 import android.app.usage.UsageStatsManagerInternal.AppIdleStateChangeListener;
-import android.appwidget.AppWidgetManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -41,6 +40,7 @@ import android.content.pm.UserInfo;
 import android.content.res.Configuration;
 import android.database.ContentObserver;
 import android.net.Uri;
+import android.os.BatteryManager;
 import android.os.Binder;
 import android.os.Environment;
 import android.os.Handler;
@@ -81,6 +81,8 @@ public class UsageStatsService extends SystemService implements
     private static final long TWENTY_MINUTES = 20 * 60 * 1000;
     private static final long FLUSH_INTERVAL = DEBUG ? TEN_SECONDS : TWENTY_MINUTES;
     private static final long TIME_CHANGE_THRESHOLD_MILLIS = 2 * 1000; // Two seconds.
+    static final long DEFAULT_APP_IDLE_THRESHOLD_MILLIS = 2L * 24 * 60 * 60 * 1000; // 1 day
+    static final long DEFAULT_CHECK_IDLE_INTERVAL = 8 * 3600 * 1000; // 8 hours
 
     // Handler message types.
     static final int MSG_REPORT_EVENT = 0;
@@ -88,6 +90,7 @@ public class UsageStatsService extends SystemService implements
     static final int MSG_REMOVE_USER = 2;
     static final int MSG_INFORM_LISTENERS = 3;
     static final int MSG_RESET_LAST_TIMESTAMP = 4;
+    static final int MSG_CHECK_IDLE_STATES = 5;
 
     private final Object mLock = new Object();
     Handler mHandler;
@@ -98,9 +101,11 @@ public class UsageStatsService extends SystemService implements
     private File mUsageStatsDir;
     long mRealTimeSnapshot;
     long mSystemTimeSnapshot;
+    boolean mAppIdleParoled;
 
-    private static final long DEFAULT_APP_IDLE_THRESHOLD_MILLIS = 1L * 24 * 60 * 60 * 1000; // 1 day
-    private long mAppIdleDurationMillis;
+    long mAppIdleDurationMillis;
+
+    long mCheckIdleIntervalMillis = DEFAULT_CHECK_IDLE_INTERVAL;
 
     private ArrayList<UsageStatsManagerInternal.AppIdleStateChangeListener>
             mPackageAccessListeners = new ArrayList<>();
@@ -113,6 +118,7 @@ public class UsageStatsService extends SystemService implements
     public void onStart() {
         mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
         mUserManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
+
         mHandler = new H(BackgroundThread.get().getLooper());
 
         File systemDataDir = new File(Environment.getDataDirectory(), "system");
@@ -123,9 +129,14 @@ public class UsageStatsService extends SystemService implements
                     + mUsageStatsDir.getAbsolutePath());
         }
 
-        getContext().registerReceiver(new UserRemovedReceiver(),
-                new IntentFilter(Intent.ACTION_USER_REMOVED));
+        IntentFilter userActions = new IntentFilter(Intent.ACTION_USER_REMOVED);
+        userActions.addAction(Intent.ACTION_USER_STARTED);
+        getContext().registerReceiverAsUser(new UserActionsReceiver(), UserHandle.ALL, userActions,
+                null, null);
 
+        IntentFilter deviceStates = new IntentFilter(BatteryManager.ACTION_CHARGING);
+        deviceStates.addAction(BatteryManager.ACTION_DISCHARGING);
+        getContext().registerReceiver(new DeviceStateReceiver(), deviceStates);
         synchronized (mLock) {
             cleanUpRemovedUsersLocked();
         }
@@ -147,18 +158,35 @@ public class UsageStatsService extends SystemService implements
         if (phase == PHASE_SYSTEM_SERVICES_READY) {
             // Observe changes to the threshold
             new SettingsObserver(mHandler).registerObserver();
+        } else if (phase == PHASE_BOOT_COMPLETED) {
+            setAppIdleParoled(getContext().getSystemService(BatteryManager.class).isCharging());
         }
     }
 
-    private class UserRemovedReceiver extends BroadcastReceiver {
+    private class UserActionsReceiver extends BroadcastReceiver {
 
         @Override
         public void onReceive(Context context, Intent intent) {
-            if (intent != null && intent.getAction().equals(Intent.ACTION_USER_REMOVED)) {
-                final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+            final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+            if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) {
                 if (userId >= 0) {
                     mHandler.obtainMessage(MSG_REMOVE_USER, userId, 0).sendToTarget();
                 }
+            } else if (Intent.ACTION_USER_STARTED.equals(intent.getAction())) {
+                if (userId >=0) {
+                    postCheckIdleStates();
+                }
+            }
+        }
+    }
+
+    private class DeviceStateReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            if (BatteryManager.ACTION_CHARGING.equals(action)
+                    || BatteryManager.ACTION_DISCHARGING.equals(action)) {
+                setAppIdleParoled(BatteryManager.ACTION_CHARGING.equals(action));
             }
         }
     }
@@ -195,6 +223,49 @@ public class UsageStatsService extends SystemService implements
         }
     }
 
+    void setAppIdleParoled(boolean paroled) {
+        synchronized (mLock) {
+            if (mAppIdleParoled != paroled) {
+                mAppIdleParoled = paroled;
+                postCheckIdleStates();
+            }
+        }
+    }
+
+    void postCheckIdleStates() {
+        mHandler.removeMessages(MSG_CHECK_IDLE_STATES);
+        mHandler.sendEmptyMessage(MSG_CHECK_IDLE_STATES);
+    }
+
+    /** Check all running users' apps to see if they enter an idle state. */
+    void checkIdleStates() {
+        final int[] runningUsers;
+        try {
+            runningUsers = ActivityManagerNative.getDefault().getRunningUserIds();
+        } catch (RemoteException re) {
+            return;
+        }
+
+        for (int i = 0; i < runningUsers.length; i++) {
+            final int userId = runningUsers[i];
+            List<PackageInfo> packages =
+                    getContext().getPackageManager().getInstalledPackages(
+                            PackageManager.GET_DISABLED_COMPONENTS
+                                | PackageManager.GET_UNINSTALLED_PACKAGES,
+                            userId);
+            synchronized (mLock) {
+                final int packageCount = packages.size();
+                for (int p = 0; p < packageCount; p++) {
+                    final String packageName = packages.get(p).packageName;
+                    final boolean isIdle = isAppIdle(packageName, userId);
+                    mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS,
+                            userId, isIdle ? 1 : 0, packageName));
+                }
+            }
+        }
+        mHandler.sendEmptyMessageDelayed(MSG_CHECK_IDLE_STATES, mCheckIdleIntervalMillis);
+    }
+
     private static void deleteRecursively(File f) {
         File[] files = f.listFiles();
         if (files != null) {
@@ -291,7 +362,7 @@ public class UsageStatsService extends SystemService implements
     void resetLastTimestamp(String packageName, int userId, boolean idle) {
         synchronized (mLock) {
             final long timeNow = checkAndGetTimeLocked();
-            final long lastTimestamp = timeNow - (idle ? mAppIdleDurationMillis : 0);
+            final long lastTimestamp = timeNow - (idle ? mAppIdleDurationMillis : 0) - 5000;
 
             final UserUsageStatsService service =
                     getUserDataAndInitializeIfNeededLocked(userId, timeNow);
@@ -409,14 +480,22 @@ public class UsageStatsService extends SystemService implements
 
     private boolean hasPassedIdleDuration(long lastUsed) {
         final long now = System.currentTimeMillis();
-        return lastUsed < now - mAppIdleDurationMillis;
+        return lastUsed <= now - mAppIdleDurationMillis;
     }
 
     boolean isAppIdle(String packageName, int userId) {
         if (packageName == null) return false;
+        synchronized (mLock) {
+            // Temporary exemption, probably due to device charging or occasional allowance to
+            // be allowed to sync, etc.
+            if (mAppIdleParoled) {
+                return false;
+            }
+        }
         if (SystemConfig.getInstance().getAllowInPowerSave().contains(packageName)) {
             return false;
         }
+        // TODO: Optimize this check
         if (isActiveDeviceAdmin(packageName, userId)) {
             return false;
         }
@@ -518,6 +597,9 @@ public class UsageStatsService extends SystemService implements
                     resetLastTimestamp((String) msg.obj, msg.arg1, msg.arg2 == 1);
                     break;
 
+                case MSG_CHECK_IDLE_STATES:
+                    checkIdleStates();
+                    break;
                 default:
                     super.handleMessage(msg);
                     break;
@@ -544,7 +626,9 @@ public class UsageStatsService extends SystemService implements
             mAppIdleDurationMillis = Settings.Secure.getLongForUser(getContext().getContentResolver(),
                     Settings.Secure.APP_IDLE_DURATION, DEFAULT_APP_IDLE_THRESHOLD_MILLIS,
                     UserHandle.USER_OWNER);
-            // TODO: Check if we need to update idle states of all the apps
+            mCheckIdleIntervalMillis = Math.min(DEFAULT_CHECK_IDLE_INTERVAL,
+                    mAppIdleDurationMillis / 4);
+            postCheckIdleStates();
         }
     }