OSDN Git Service

Delay syncs for idle apps
authorAmith Yamasani <yamasani@google.com>
Fri, 10 Apr 2015 23:16:30 +0000 (16:16 -0700)
committerAmith Yamasani <yamasani@google.com>
Mon, 13 Apr 2015 22:36:32 +0000 (15:36 -0700)
Apps that haven't been in use for a while and are considered idle
are not synced until the device is charging or the app is used.

Bug: 20066058
Change-Id: I3471e3a11edae04777163b0dbd74e86495743caa

services/core/java/com/android/server/content/AppIdleMonitor.java [new file with mode: 0644]
services/core/java/com/android/server/content/SyncManager.java
services/core/java/com/android/server/content/SyncOperation.java
services/usage/java/com/android/server/usage/UsageStatsService.java

diff --git a/services/core/java/com/android/server/content/AppIdleMonitor.java b/services/core/java/com/android/server/content/AppIdleMonitor.java
new file mode 100644 (file)
index 0000000..9598de8
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2015 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.server.content;
+
+import android.app.usage.UsageStatsManagerInternal;
+import android.app.usage.UsageStatsManagerInternal.AppIdleStateChangeListener;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.BatteryManager;
+import android.os.UserHandle;
+
+import com.android.server.LocalServices;
+
+/**
+ * Helper to listen for app idle and charging status changes and restart backed off
+ * sync operations.
+ */
+class AppIdleMonitor implements AppIdleStateChangeListener {
+
+    private final SyncManager mSyncManager;
+    private final UsageStatsManagerInternal mUsageStats;
+    final BatteryManager mBatteryManager;
+    /** Is the device currently plugged into power. */
+    private boolean mPluggedIn;
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            onPluggedIn(mBatteryManager.isCharging());
+        }
+    };
+
+    AppIdleMonitor(SyncManager syncManager, Context context) {
+        mSyncManager = syncManager;
+        mUsageStats = LocalServices.getService(UsageStatsManagerInternal.class);
+        mUsageStats.addAppIdleStateChangeListener(this);
+        mBatteryManager = context.getSystemService(BatteryManager.class);
+        mPluggedIn = isPowered();
+        registerReceivers(context);
+    }
+
+    private void registerReceivers(Context context) {
+        // Monitor battery charging state
+        IntentFilter filter = new IntentFilter(BatteryManager.ACTION_CHARGING);
+        filter.addAction(BatteryManager.ACTION_DISCHARGING);
+        context.registerReceiver(mReceiver, filter);
+    }
+
+    private boolean isPowered() {
+        return mBatteryManager.isCharging();
+    }
+
+    void onPluggedIn(boolean pluggedIn) {
+        if (mPluggedIn == pluggedIn) {
+            return;
+        }
+        mPluggedIn = pluggedIn;
+        if (mPluggedIn) {
+            mSyncManager.onAppNotIdle(null, UserHandle.USER_ALL);
+        }
+    }
+
+    boolean isAppIdle(String packageName, int userId) {
+        return !mPluggedIn && mUsageStats.isAppIdle(packageName, userId);
+    }
+
+    @Override
+    public void onAppIdleStateChanged(String packageName, int userId, boolean idle) {
+        // Don't care if the app is becoming idle
+        if (idle) return;
+        mSyncManager.onAppNotIdle(packageName, userId);
+    }
+}
index 7866ddc..4173b78 100644 (file)
@@ -83,6 +83,7 @@ import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.accounts.AccountManagerService;
 import com.android.server.content.SyncStorageEngine.AuthorityInfo;
+import com.android.server.content.SyncStorageEngine.EndPoint;
 import com.android.server.content.SyncStorageEngine.OnSyncRequestListener;
 import com.google.android.collect.Lists;
 import com.google.android.collect.Maps;
@@ -107,7 +108,7 @@ import java.util.Set;
  * @hide
  */
 public class SyncManager {
-    private static final String TAG = "SyncManager";
+    static final String TAG = "SyncManager";
 
     /** Delay a sync due to local changes this long. In milliseconds */
     private static final long LOCAL_SYNC_DELAY;
@@ -199,6 +200,8 @@ public class SyncManager {
 
     protected SyncAdaptersCache mSyncAdapters;
 
+    private final AppIdleMonitor mAppIdleMonitor;
+
     private BroadcastReceiver mStorageIntentReceiver =
             new BroadcastReceiver() {
                 @Override
@@ -427,6 +430,8 @@ public class SyncManager {
         mSyncAlarmIntent = PendingIntent.getBroadcast(
                 mContext, 0 /* ignored */, new Intent(ACTION_SYNC_ALARM), 0);
 
+        mAppIdleMonitor = new AppIdleMonitor(this, mContext);
+
         IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
         context.registerReceiver(mConnectivityIntentReceiver, intentFilter);
 
@@ -1169,6 +1174,36 @@ public class SyncManager {
     }
 
     /**
+     * Clear backoff on operations in the sync queue that match the packageName and userId.
+     * @param packageName The package that just became active. Can be null to indicate that all
+     * packages are now considered active due to being plugged in.
+     * @param userId The user for which the package has become active. Can be USER_ALL if
+     * the device just plugged in.
+     */
+    void onAppNotIdle(String packageName, int userId) {
+        synchronized (mSyncQueue) {
+            // For all sync operations in sync queue, if marked as idle, compare with package name
+            // and unmark. And clear backoff for the operation.
+            final Iterator<SyncOperation> operationIterator =
+                    mSyncQueue.getOperations().iterator();
+            boolean changed = false;
+            while (operationIterator.hasNext()) {
+                final SyncOperation op = operationIterator.next();
+                if (op.appIdle
+                        && getPackageName(op.target).equals(packageName)
+                        && (userId == UserHandle.USER_ALL || op.target.userId == userId)) {
+                    op.appIdle = false;
+                    clearBackoffSetting(op);
+                    changed = true;
+                }
+            }
+            if (changed) {
+                sendCheckAlarmsMessage();
+            }
+        }
+    }
+
+    /**
      * @hide
      */
     class ActiveSyncContext extends ISyncContext.Stub
@@ -2447,6 +2482,19 @@ public class SyncManager {
                         }
                         continue;
                     }
+                    String packageName = getPackageName(op.target);
+                    // If app is considered idle, then skip for now and backoff
+                    if (packageName != null
+                            && mAppIdleMonitor.isAppIdle(packageName, op.target.userId)) {
+                        increaseBackoffSetting(op);
+                        op.appIdle = true;
+                        if (isLoggable) {
+                            Log.v(TAG, "Sync backing off idle app " + packageName);
+                        }
+                        continue;
+                    } else {
+                        op.appIdle = false;
+                    }
                     // Add this sync to be run.
                     operations.add(op);
                 }
@@ -3194,6 +3242,21 @@ public class SyncManager {
         }
     }
 
+    String getPackageName(EndPoint endpoint) {
+        if (endpoint.target_service) {
+            return endpoint.service.getPackageName();
+        } else {
+            SyncAdapterType syncAdapterType =
+                    SyncAdapterType.newKey(endpoint.provider, endpoint.account.type);
+            final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo;
+            syncAdapterInfo = mSyncAdapters.getServiceInfo(syncAdapterType, endpoint.userId);
+            if (syncAdapterInfo == null) {
+                return null;
+            }
+            return syncAdapterInfo.componentName.getPackageName();
+        }
+    }
+
     private boolean isSyncStillActive(ActiveSyncContext activeSyncContext) {
         for (ActiveSyncContext sync : mActiveSyncContexts) {
             if (sync == activeSyncContext) {
index 35827cc..10efe81 100644 (file)
@@ -90,6 +90,9 @@ public class SyncOperation implements Comparable {
     /** Descriptive string key for this operation */
     public String wakeLockName;
 
+    /** Whether this sync op was recently skipped due to the app being idle */
+    public boolean appIdle;
+
     public SyncOperation(Account account, int userId, int reason, int source, String provider,
             Bundle extras, long runTimeFromNow, long flexTime, long backoff,
             long delayUntil, boolean allowParallelSyncs) {
index cc0ab81..3d54dfb 100644 (file)
@@ -53,6 +53,7 @@ import android.util.SparseArray;
 
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.SystemConfig;
 import com.android.server.SystemService;
 
 import java.io.File;
@@ -383,6 +384,9 @@ public class UsageStatsService extends SystemService implements
     }
 
     boolean isAppIdle(String packageName, int userId) {
+        if (SystemConfig.getInstance().getAllowInPowerSave().contains(packageName)) {
+            return false;
+        }
         final long lastUsed = getLastPackageAccessTime(packageName, userId);
         return hasPassedIdleDuration(lastUsed);
     }