Merge "Remove network access for idle apps" into mnc-dev
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index b4c7b2b..ecc3fb4 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -41,6 +41,7 @@
*/
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 @@
/** 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 @@
* 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 @@
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("]");
}
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index f93550a..b29e8d0 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -342,7 +342,7 @@
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.
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 12a99b0..1a75b8a 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -24,6 +24,7 @@
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 @@
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 @@
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);
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index b5b62b4..f6998ca 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -43,6 +43,7 @@
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 @@
*/
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 @@
/** 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 @@
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 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 @@
}
@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 @@
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()) {
diff --git a/services/core/java/com/android/server/net/LockdownVpnTracker.java b/services/core/java/com/android/server/net/LockdownVpnTracker.java
index 6ffe6ac..0f88883 100644
--- a/services/core/java/com/android/server/net/LockdownVpnTracker.java
+++ b/services/core/java/com/android/server/net/LockdownVpnTracker.java
@@ -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.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 @@
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 @@
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;
}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index e45092c..57df1c3 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -36,11 +36,14 @@
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.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.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.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.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 @@
* 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 @@
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 @@
return;
}
+ mUsageStats = LocalServices.getService(UsageStatsManagerInternal.class);
+
+ final PackageManager pm = mContext.getPackageManager();
+
synchronized (mRulesLock) {
updatePowerSaveWhitelistLocked();
mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
@@ -460,6 +472,8 @@
WifiManager.NETWORK_STATE_CHANGED_ACTION);
mContext.registerReceiver(mWifiStateReceiver, wifiStateFilter, null, mHandler);
+ mUsageStats.addAppIdleStateChangeListener(this);
+
}
private IProcessObserver mProcessObserver = new IProcessObserver.Stub() {
@@ -568,12 +582,17 @@
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 @@
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 @@
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 @@
}
}
- // 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 @@
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 @@
}
}
+ @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 @@
}
}
+ /**
+ * 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);
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 04984d3..c2e61c6 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -28,7 +28,6 @@
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.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 @@
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 @@
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 @@
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 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 @@
+ 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 @@
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 @@
}
}
+ 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 @@
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 @@
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 @@
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 @@
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();
}
}