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 Original line 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 Original line Diff line number Diff line
@@ -83,6 +83,7 @@ import com.android.internal.os.BackgroundThread;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.accounts.AccountManagerService;
import com.android.server.accounts.AccountManagerService;
import com.android.server.content.SyncStorageEngine.AuthorityInfo;
import com.android.server.content.SyncStorageEngine.AuthorityInfo;
import com.android.server.content.SyncStorageEngine.EndPoint;
import com.android.server.content.SyncStorageEngine.OnSyncRequestListener;
import com.android.server.content.SyncStorageEngine.OnSyncRequestListener;
import com.google.android.collect.Lists;
import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
import com.google.android.collect.Maps;
@@ -107,7 +108,7 @@ import java.util.Set;
 * @hide
 * @hide
 */
 */
public class SyncManager {
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 */
    /** Delay a sync due to local changes this long. In milliseconds */
    private static final long LOCAL_SYNC_DELAY;
    private static final long LOCAL_SYNC_DELAY;
@@ -199,6 +200,8 @@ public class SyncManager {


    protected SyncAdaptersCache mSyncAdapters;
    protected SyncAdaptersCache mSyncAdapters;


    private final AppIdleMonitor mAppIdleMonitor;

    private BroadcastReceiver mStorageIntentReceiver =
    private BroadcastReceiver mStorageIntentReceiver =
            new BroadcastReceiver() {
            new BroadcastReceiver() {
                @Override
                @Override
@@ -427,6 +430,8 @@ public class SyncManager {
        mSyncAlarmIntent = PendingIntent.getBroadcast(
        mSyncAlarmIntent = PendingIntent.getBroadcast(
                mContext, 0 /* ignored */, new Intent(ACTION_SYNC_ALARM), 0);
                mContext, 0 /* ignored */, new Intent(ACTION_SYNC_ALARM), 0);


        mAppIdleMonitor = new AppIdleMonitor(this, mContext);

        IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
        IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
        context.registerReceiver(mConnectivityIntentReceiver, intentFilter);
        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
     * @hide
     */
     */
@@ -2447,6 +2482,19 @@ public class SyncManager {
                        }
                        }
                        continue;
                        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.
                    // Add this sync to be run.
                    operations.add(op);
                    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) {
    private boolean isSyncStillActive(ActiveSyncContext activeSyncContext) {
        for (ActiveSyncContext sync : mActiveSyncContexts) {
        for (ActiveSyncContext sync : mActiveSyncContexts) {
            if (sync == activeSyncContext) {
            if (sync == activeSyncContext) {
+3 −0
Original line number Original line Diff line number Diff line
@@ -90,6 +90,9 @@ public class SyncOperation implements Comparable {
    /** Descriptive string key for this operation */
    /** Descriptive string key for this operation */
    public String wakeLockName;
    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,
    public SyncOperation(Account account, int userId, int reason, int source, String provider,
            Bundle extras, long runTimeFromNow, long flexTime, long backoff,
            Bundle extras, long runTimeFromNow, long flexTime, long backoff,
            long delayUntil, boolean allowParallelSyncs) {
            long delayUntil, boolean allowParallelSyncs) {
+4 −0
Original line number Original line Diff line number Diff line
@@ -53,6 +53,7 @@ import android.util.SparseArray;


import com.android.internal.os.BackgroundThread;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.SystemConfig;
import com.android.server.SystemService;
import com.android.server.SystemService;


import java.io.File;
import java.io.File;
@@ -383,6 +384,9 @@ public class UsageStatsService extends SystemService implements
    }
    }


    boolean isAppIdle(String packageName, int userId) {
    boolean isAppIdle(String packageName, int userId) {
        if (SystemConfig.getInstance().getAllowInPowerSave().contains(packageName)) {
            return false;
        }
        final long lastUsed = getLastPackageAccessTime(packageName, userId);
        final long lastUsed = getLastPackageAccessTime(packageName, userId);
        return hasPassedIdleDuration(lastUsed);
        return hasPassedIdleDuration(lastUsed);
    }
    }