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

Commit 462062c9 authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

Selectively delay ContentObservers based on state.

While optimizing MediaProvider operations, we noticed that many
background apps register ContentObservers which end up stampeding
after any change is notified.  This can starve the device of precious
CPU/RAM needed while a Camera app is still trying to persist heavy
operations such as a burst capture.

The initial mitigation was to simply delay all notifications while
a camera session was active, but OEMs have informed us that in a
multi-window environment it's important that foreground apps not be
subject to these delays.

Thus, to strike a balance, this change moves the delaying logic into
ContentService, where we only dispatch immediate notifications to
foreground apps.  Background apps will still hear about the changes,
but they'll be delayed several seconds to avoid the stampeding
described above.

Bug: 140249142
Test: atest --test-mapping packages/providers/MediaProvider
Change-Id: I0bffc49c116eef35c5bc4e1b24745e8e9e0974d6
parent 4827332e
Loading
Loading
Loading
Loading
+51 −44
Original line number Diff line number Diff line
@@ -55,6 +55,7 @@ import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.os.UserHandle;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
@@ -64,6 +65,8 @@ import android.util.SparseArray;
import android.util.SparseIntArray;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BackgroundThread;
import com.android.internal.os.BinderDeathDispatcher;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
@@ -89,6 +92,14 @@ public final class ContentService extends IContentService.Stub {
    /** Do a WTF if a single observer is registered more than this times. */
    private static final int TOO_MANY_OBSERVERS_THRESHOLD = 1000;

    /**
     * Delay to apply to content change notifications dispatched to apps running
     * in the background. This is used to help prevent stampeding when the user
     * is performing CPU/RAM intensive foreground tasks, such as when capturing
     * burst photos.
     */
    private static final long BACKGROUND_OBSERVER_DELAY = 10 * DateUtils.SECOND_IN_MILLIS;

    public static class Lifecycle extends SystemService {
        private ContentService mService;

@@ -427,27 +438,14 @@ public final class ContentService extends IContentService.Stub {
            }
            final int numCalls = calls.size();
            for (int i = 0; i < numCalls; i++) {
                ObserverCall oc = calls.get(i);
                try {
                    oc.mObserver.onChange(oc.mSelfChange, uri, userHandle);
                    if (DEBUG) Slog.d(TAG, "Notified " + oc.mObserver + " of " + "update at "
                            + uri);
                } catch (RemoteException ex) {
                    synchronized (mRootNode) {
                        Log.w(TAG, "Found dead observer, removing");
                        IBinder binder = oc.mObserver.asBinder();
                        final ArrayList<ObserverNode.ObserverEntry> list
                                = oc.mNode.mObservers;
                        int numList = list.size();
                        for (int j=0; j<numList; j++) {
                            ObserverNode.ObserverEntry oe = list.get(j);
                            if (oe.observer.asBinder() == binder) {
                                list.remove(j);
                                j--;
                                numList--;
                            }
                        }
                    }
                // Immediately dispatch notifications to foreground apps that
                // are important to the user; all other background observers are
                // delayed to avoid stampeding
                final ObserverCall oc = calls.get(i);
                if (oc.mProcState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
                    oc.run();
                } else {
                    BackgroundThread.getHandler().postDelayed(oc, BACKGROUND_OBSERVER_DELAY);
                }
            }
            if ((flags&ContentResolver.NOTIFY_SYNC_TO_NETWORK) != 0) {
@@ -486,23 +484,33 @@ public final class ContentService extends IContentService.Stub {
                UserHandle.getCallingUserId(), Build.VERSION_CODES.CUR_DEVELOPMENT, callingPackage);
    }

    /**
     * Hide this class since it is not part of api,
     * but current unittest framework requires it to be public
     * @hide
     *
     */
    public static final class ObserverCall {
        final ObserverNode mNode;
    /** {@hide} */
    @VisibleForTesting
    public static final class ObserverCall implements Runnable {
        final IContentObserver mObserver;
        final boolean mSelfChange;
        final int mObserverUserId;
        final Uri mUri;
        final int mUserId;
        final int mProcState;

        ObserverCall(ObserverNode node, IContentObserver observer, boolean selfChange, int observerUserId) {
            mNode = node;
        ObserverCall(IContentObserver observer, boolean selfChange, Uri uri, int userId,
                int procState) {
            mObserver = observer;
            mSelfChange = selfChange;
            mObserverUserId = observerUserId;
            mUri = uri;
            mUserId = userId;
            mProcState = procState;
        }

        @Override
        public void run() {
            try {
                mObserver.onChange(mSelfChange, mUri, mUserId);
                if (DEBUG) Slog.d(TAG, "Notified " + mObserver + " of update at " + mUri);
            } catch (RemoteException ignored) {
                // We already have a death observer that will clean up if the
                // remote process dies
            }
        }
    }

@@ -1345,11 +1353,8 @@ public final class ContentService extends IContentService.Stub {
        return ContentResolver.SYNC_EXEMPTION_NONE;
    }

    /**
     * Hide this class since it is not part of api,
     * but current unittest framework requires it to be public
     * @hide
     */
    /** {@hide} */
    @VisibleForTesting
    public static final class ObserverNode {
        private class ObserverEntry implements IBinder.DeathRecipient {
            public final IContentObserver observer;
@@ -1546,7 +1551,7 @@ public final class ContentService extends IContentService.Stub {
            return false;
        }

        private void collectMyObserversLocked(boolean leaf, IContentObserver observer,
        private void collectMyObserversLocked(Uri uri, boolean leaf, IContentObserver observer,
                                              boolean observerWantsSelfNotifications, int flags,
                                              int targetUserHandle, ArrayList<ObserverCall> calls) {
            int N = mObservers.size();
@@ -1588,8 +1593,10 @@ public final class ContentService extends IContentService.Stub {
                    if (DEBUG) Slog.d(TAG, "Reporting to " + entry.observer + ": leaf=" + leaf
                            + " flags=" + Integer.toHexString(flags)
                            + " desc=" + entry.notifyForDescendants);
                    calls.add(new ObserverCall(this, entry.observer, selfChange,
                            UserHandle.getUserId(entry.uid)));
                    final int procState = LocalServices.getService(ActivityManagerInternal.class)
                            .getUidProcessState(entry.uid);
                    calls.add(new ObserverCall(entry.observer, selfChange, uri,
                            targetUserHandle, procState));
                }
            }
        }
@@ -1605,14 +1612,14 @@ public final class ContentService extends IContentService.Stub {
            if (index >= segmentCount) {
                // This is the leaf node, notify all observers
                if (DEBUG) Slog.d(TAG, "Collecting leaf observers @ #" + index + ", node " + mName);
                collectMyObserversLocked(true, observer, observerWantsSelfNotifications,
                collectMyObserversLocked(uri, true, observer, observerWantsSelfNotifications,
                        flags, targetUserHandle, calls);
            } else if (index < segmentCount){
                segment = getUriSegment(uri, index);
                if (DEBUG) Slog.d(TAG, "Collecting non-leaf observers @ #" + index + " / "
                        + segment);
                // Notify any observers at this level who are interested in descendants
                collectMyObserversLocked(false, observer, observerWantsSelfNotifications,
                collectMyObserversLocked(uri, false, observer, observerWantsSelfNotifications,
                        flags, targetUserHandle, calls);
            }