Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 96a0fd65 authored by Amith Yamasani's avatar Amith Yamasani
Browse files

Delay syncs for idle apps

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
parent fb1e9b79
Loading
Loading
Loading
Loading
+89 −0
Original line number Diff line number Diff line
/*
 * 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);
    }
}
+64 −1
Original line number Diff line number Diff line
@@ -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);

@@ -1168,6 +1173,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
     */
@@ -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) {
+3 −0
Original line number Diff line number Diff line
@@ -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) {
+4 −0
Original line number Diff line number Diff line
@@ -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);
    }