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

Commit c0537b75 authored by Riddle Hsu's avatar Riddle Hsu
Browse files

Defer sending config change to cached process

Though there was commit e839c2ca to defer on client side, if
CachedAppOptimizer decides to freeze the cached process, the
async binder buffer may still be filled, and may lead to
binder transaction fail right after unfreezing because the
free buffer is not enough.

So now there is no IPC for config change of cached process,
and will send the latest change to the client when the process
state is changed from cached to non-cached.

Bug: 217934273
Test: WindowProcessControllerTests#testCachedStateConfigurationChange

Change-Id: I09c2feffba2fc124c623796af69de9f6adb60a39
parent c3565a50
Loading
Loading
Loading
Loading
+72 −35
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.server.wm;

import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY;
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.content.res.Configuration.ASSETS_SEQ_UNDEFINED;
@@ -195,6 +196,11 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
    /** Whether the process configuration is waiting to be dispatched to the process. */
    private boolean mHasPendingConfigurationChange;

    /** If the process state is in (<=) the cached state, then defer delivery of the config. */
    private static final int CACHED_CONFIG_PROC_STATE = PROCESS_STATE_CACHED_ACTIVITY;
    /** Whether {@link #mLastReportedConfiguration} is deferred by the cached state. */
    private volatile boolean mHasCachedConfiguration;

    /**
     * Registered {@link DisplayArea} as a listener to override config changes. {@code null} if not
     * registered.
@@ -316,8 +322,27 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
        return mCurProcState;
    }

    /**
     * Sets the computed process state from the oom adjustment calculation. This is frequently
     * called in activity manager's lock, so don't use window manager lock here.
     */
    @HotPath(caller = HotPath.OOM_ADJUSTMENT)
    public void setReportedProcState(int repProcState) {
        final int prevProcState = mRepProcState;
        mRepProcState = repProcState;

        // Deliver the cached config if the app changes from cached state to non-cached state.
        final IApplicationThread thread = mThread;
        if (prevProcState >= CACHED_CONFIG_PROC_STATE && repProcState < CACHED_CONFIG_PROC_STATE
                && thread != null && mHasCachedConfiguration) {
            final Configuration config;
            synchronized (mLastReportedConfiguration) {
                config = new Configuration(mLastReportedConfiguration);
            }
            // Schedule immediately to make sure the app component (e.g. receiver, service) can get
            // the latest configuration in their lifecycle callbacks (e.g. onReceive, onCreate).
            scheduleConfigurationChange(thread, config);
        }
    }

    int getReportedProcState() {
@@ -1328,12 +1353,22 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
    @Override
    public void onConfigurationChanged(Configuration newGlobalConfig) {
        super.onConfigurationChanged(newGlobalConfig);
        updateConfiguration();
        final Configuration config = getConfiguration();
        if (mLastReportedConfiguration.equals(config)) {
            // Nothing changed.
            if (Build.IS_DEBUGGABLE && mHasImeService) {
                // TODO (b/135719017): Temporary log for debugging IME service.
                Slog.w(TAG_CONFIGURATION, "Current config: " + config
                        + " unchanged for IME proc " + mName);
            }
            return;
        }

    @Override
    public void onRequestedOverrideConfigurationChanged(Configuration overrideConfiguration) {
        super.onRequestedOverrideConfigurationChanged(overrideConfiguration);
        if (mPauseConfigurationDispatchCount > 0) {
            mHasPendingConfigurationChange = true;
            return;
        }
        dispatchConfiguration(config);
    }

    @Override
@@ -1359,58 +1394,57 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
        resolvedConfig.seq = newParentConfig.seq;
    }

    private void updateConfiguration() {
        final Configuration config = getConfiguration();
        if (mLastReportedConfiguration.diff(config) == 0) {
            // Nothing changed.
    void dispatchConfiguration(Configuration config) {
        mHasPendingConfigurationChange = false;
        if (mThread == null) {
            if (Build.IS_DEBUGGABLE && mHasImeService) {
                // TODO (b/135719017): Temporary log for debugging IME service.
                Slog.w(TAG_CONFIGURATION, "Current config: " + config
                        + " unchanged for IME proc " + mName);
                Slog.w(TAG_CONFIGURATION, "Unable to send config for IME proc " + mName
                        + ": no app thread");
            }
            return;
        }

        if (mPauseConfigurationDispatchCount > 0) {
            mHasPendingConfigurationChange = true;
        config.seq = mAtm.increaseConfigurationSeqLocked();
        setLastReportedConfiguration(config);

        // A cached process doesn't have running application components, so it is unnecessary to
        // notify the configuration change. The last-reported-configuration is still set because
        // setReportedProcState() should not write any fields that require WM lock.
        if (mRepProcState >= CACHED_CONFIG_PROC_STATE) {
            mHasCachedConfiguration = true;
            // Because there are 2 volatile accesses in setReportedProcState(): mRepProcState and
            // mHasCachedConfiguration, check again in case mRepProcState is changed but hasn't
            // read the change of mHasCachedConfiguration.
            if (mRepProcState >= CACHED_CONFIG_PROC_STATE) {
                return;
            }
        dispatchConfiguration(config);
        }

    void dispatchConfiguration(Configuration config) {
        mHasPendingConfigurationChange = false;
        if (mThread == null) {
            if (Build.IS_DEBUGGABLE && mHasImeService) {
                // TODO (b/135719017): Temporary log for debugging IME service.
                Slog.w(TAG_CONFIGURATION, "Unable to send config for IME proc " + mName
                        + ": no app thread");
            }
            return;
        scheduleConfigurationChange(mThread, config);
    }

    private void scheduleConfigurationChange(IApplicationThread thread, Configuration config) {
        ProtoLog.v(WM_DEBUG_CONFIGURATION, "Sending to proc %s new config %s", mName,
                config);
        if (Build.IS_DEBUGGABLE && mHasImeService) {
            // TODO (b/135719017): Temporary log for debugging IME service.
            Slog.v(TAG_CONFIGURATION, "Sending to IME proc " + mName + " new config " + config);
        }

        mHasCachedConfiguration = false;
        try {
            config.seq = mAtm.increaseConfigurationSeqLocked();
            mAtm.getLifecycleManager().scheduleTransaction(mThread,
            mAtm.getLifecycleManager().scheduleTransaction(thread,
                    ConfigurationChangeItem.obtain(config));
            setLastReportedConfiguration(config);
        } catch (Exception e) {
            Slog.e(TAG_CONFIGURATION, "Failed to schedule configuration change", e);
            Slog.e(TAG_CONFIGURATION, "Failed to schedule configuration change: " + mOwner, e);
        }
    }

    void setLastReportedConfiguration(Configuration config) {
        // Synchronize for the access from setReportedProcState().
        synchronized (mLastReportedConfiguration) {
            mLastReportedConfiguration.setTo(config);
        }

    Configuration getLastReportedConfiguration() {
        return mLastReportedConfiguration;
    }

    void pauseConfigurationDispatch() {
@@ -1461,6 +1495,8 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
            // config seq. This increment ensures that the client won't ignore the configuration.
            config.seq = mAtm.increaseConfigurationSeqLocked();
        }
        // LaunchActivityItem includes the latest process configuration.
        mHasCachedConfiguration = false;
        return config;
    }

@@ -1688,7 +1724,8 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
        }
        pw.println(prefix + " Configuration=" + getConfiguration());
        pw.println(prefix + " OverrideConfiguration=" + getRequestedOverrideConfiguration());
        pw.println(prefix + " mLastReportedConfiguration=" + mLastReportedConfiguration);
        pw.println(prefix + " mLastReportedConfiguration=" + (mHasCachedConfiguration
                ? ("(cached) " + mLastReportedConfiguration) : mLastReportedConfiguration));

        final int stateFlags = mActivityStateFlags;
        if (stateFlags != ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER) {
+40 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;

import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -39,24 +40,30 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.when;

import android.Manifest;
import android.app.ActivityManager;
import android.app.ClientTransactionHandler;
import android.app.IApplicationThread;
import android.app.servertransaction.ConfigurationChangeItem;
import android.content.ComponentName;
import android.content.pm.ApplicationInfo;
import android.content.pm.ServiceInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.LocaleList;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mockito;

@@ -281,6 +288,39 @@ public class WindowProcessControllerTests extends WindowTestsBase {
                mWpc.getConfiguration().seq, globalSeq);
    }

    @Test
    public void testCachedStateConfigurationChange() throws RemoteException {
        final ClientLifecycleManager clientManager = mAtm.getLifecycleManager();
        doNothing().when(clientManager).scheduleTransaction(any(), any());
        final IApplicationThread thread = mWpc.getThread();
        final Configuration newConfig = new Configuration(mWpc.getConfiguration());
        newConfig.densityDpi += 100;
        // Non-cached state will send the change directly.
        mWpc.setReportedProcState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
        clearInvocations(clientManager);
        mWpc.onConfigurationChanged(newConfig);
        verify(clientManager).scheduleTransaction(eq(thread), any());

        // Cached state won't send the change.
        clearInvocations(clientManager);
        mWpc.setReportedProcState(ActivityManager.PROCESS_STATE_CACHED_ACTIVITY);
        newConfig.densityDpi += 100;
        mWpc.onConfigurationChanged(newConfig);
        verify(clientManager, never()).scheduleTransaction(eq(thread), any());

        // Cached -> non-cached will send the previous deferred config immediately.
        mWpc.setReportedProcState(ActivityManager.PROCESS_STATE_RECEIVER);
        final ArgumentCaptor<ConfigurationChangeItem> captor =
                ArgumentCaptor.forClass(ConfigurationChangeItem.class);
        verify(clientManager).scheduleTransaction(eq(thread), captor.capture());
        final ClientTransactionHandler client = mock(ClientTransactionHandler.class);
        captor.getValue().preExecute(client, null /* token */);
        final ArgumentCaptor<Configuration> configCaptor =
                ArgumentCaptor.forClass(Configuration.class);
        verify(client).updatePendingConfiguration(configCaptor.capture());
        assertEquals(newConfig, configCaptor.getValue());
    }

    @Test
    public void testComputeOomAdjFromActivities() {
        final ActivityRecord activity = createActivityRecord(mWpc);