diff options
6 files changed, 112 insertions, 121 deletions
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index b4cabada0522..98683f0bd87a 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -3410,26 +3410,12 @@ public final class ActivityThread extends ClientTransactionHandler          }      } -    /** -     * Returns {@code true} if the {@link android.app.ActivityManager.ProcessState} of the current -     * process is cached. -     */ -    @Override -    @VisibleForTesting -    public boolean isCachedProcessState() { -        synchronized (mAppThread) { -            return mLastProcessState >= ActivityManager.PROCESS_STATE_CACHED_ACTIVITY; -        } -    } -      @Override      public void updateProcessState(int processState, boolean fromIpc) { -        final boolean wasCached;          synchronized (mAppThread) {              if (mLastProcessState == processState) {                  return;              } -            wasCached = isCachedProcessState();              mLastProcessState = processState;              // Defer the top state for VM to avoid aggressive JIT compilation affecting activity              // launch time. @@ -3446,22 +3432,6 @@ public final class ActivityThread extends ClientTransactionHandler                          + (fromIpc ? " (from ipc" : ""));              }          } - -        // Handle the pending configuration if the process state is changed from cached to -        // non-cached. Except the case where there is a launching activity because the -        // LaunchActivityItem will handle it. -        if (wasCached && !isCachedProcessState() && mNumLaunchingActivities.get() == 0) { -            final Configuration pendingConfig = -                    mConfigurationController.getPendingConfiguration(false /* clearPending */); -            if (pendingConfig == null) { -                return; -            } -            if (Looper.myLooper() == mH.getLooper()) { -                handleConfigurationChanged(pendingConfig); -            } else { -                sendMessage(H.CONFIGURATION_CHANGED, pendingConfig); -            } -        }      }      /** Update VM state based on ActivityManager.PROCESS_STATE_* constants. */ diff --git a/core/java/android/app/ActivityThreadInternal.java b/core/java/android/app/ActivityThreadInternal.java index b9ad5c337813..72506b9fcdbb 100644 --- a/core/java/android/app/ActivityThreadInternal.java +++ b/core/java/android/app/ActivityThreadInternal.java @@ -32,8 +32,6 @@ interface ActivityThreadInternal {      boolean isInDensityCompatMode(); -    boolean isCachedProcessState(); -      Application getApplication();      ArrayList<ComponentCallbacks2> collectComponentCallbacks(boolean includeUiContexts); diff --git a/core/java/android/app/ConfigurationController.java b/core/java/android/app/ConfigurationController.java index 1a77b65c8ef6..18dc1ce18baf 100644 --- a/core/java/android/app/ConfigurationController.java +++ b/core/java/android/app/ConfigurationController.java @@ -124,12 +124,6 @@ class ConfigurationController {       * @param config The new configuration.       */      void handleConfigurationChanged(@NonNull Configuration config) { -        if (mActivityThread.isCachedProcessState()) { -            updatePendingConfiguration(config); -            // If the process is in a cached state, delay the handling until the process is no -            // longer cached. -            return; -        }          Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "configChanged");          handleConfigurationChanged(config, null /* compat */);          Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java index bfb2fd57975f..a2d4bafef41c 100644 --- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java +++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java @@ -31,7 +31,6 @@ import static org.junit.Assert.assertTrue;  import android.annotation.Nullable;  import android.app.Activity; -import android.app.ActivityManager;  import android.app.ActivityThread;  import android.app.ActivityThread.ActivityClientRecord;  import android.app.IApplicationThread; @@ -571,53 +570,6 @@ public class ActivityThreadTest {      }      @Test -    public void testHandleProcessConfigurationChanged_DependOnProcessState() { -        final ActivityThread activityThread = ActivityThread.currentActivityThread(); -        final Configuration origConfig = activityThread.getConfiguration(); -        final int newDpi = origConfig.densityDpi + 10; -        final Configuration newConfig = new Configuration(origConfig); -        newConfig.seq++; -        newConfig.densityDpi = newDpi; - -        activityThread.updateProcessState(ActivityManager.PROCESS_STATE_CACHED_ACTIVITY, -                false /* fromIPC */); - -        applyProcessConfiguration(activityThread, newConfig); -        try { -            // In the cached state, the configuration is only set as pending and not applied. -            assertEquals(origConfig.densityDpi, activityThread.getConfiguration().densityDpi); -            assertTrue(activityThread.isCachedProcessState()); -        } finally { -            // The foreground state is the default state of instrumentation. -            activityThread.updateProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, -                    false /* fromIPC */); -        } -        InstrumentationRegistry.getInstrumentation().waitForIdleSync(); - -        try { -            // The state becomes non-cached, the pending configuration should be applied. -            assertEquals(newConfig.densityDpi, activityThread.getConfiguration().densityDpi); -            assertFalse(activityThread.isCachedProcessState()); -        } finally { -            // Restore to the original configuration. -            activityThread.getConfiguration().seq = origConfig.seq - 1; -            applyProcessConfiguration(activityThread, origConfig); -        } -    } - -    private static void applyProcessConfiguration(ActivityThread thread, Configuration config) { -        final ClientTransaction clientTransaction = newTransaction(thread, -                null /* activityToken */); -        clientTransaction.addCallback(ConfigurationChangeItem.obtain(config)); -        final IApplicationThread appThread = thread.getApplicationThread(); -        try { -            appThread.scheduleTransaction(clientTransaction); -        } catch (Exception ignored) { -        } -        InstrumentationRegistry.getInstrumentation().waitForIdleSync(); -    } - -    @Test      public void testResumeAfterNewIntent() {          final Activity activity = mActivityTestRule.launchActivity(new Intent());          final ActivityThread activityThread = activity.getActivityThread(); diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index 40417a4857d3..1c64a06036f0 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -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,25 +1394,6 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio          resolvedConfig.seq = newParentConfig.seq;      } -    private void updateConfiguration() { -        final Configuration config = getConfiguration(); -        if (mLastReportedConfiguration.diff(config) == 0) { -            // 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; -        } - -        if (mPauseConfigurationDispatchCount > 0) { -            mHasPendingConfigurationChange = true; -            return; -        } -        dispatchConfiguration(config); -    } -      void dispatchConfiguration(Configuration config) {          mHasPendingConfigurationChange = false;          if (mThread == null) { @@ -1388,29 +1404,47 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio              }              return;          } + +        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; +            } +        } + +        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) { -        mLastReportedConfiguration.setTo(config); -    } - -    Configuration getLastReportedConfiguration() { -        return mLastReportedConfiguration; +        // Synchronize for the access from setReportedProcState(). +        synchronized (mLastReportedConfiguration) { +            mLastReportedConfiguration.setTo(config); +        }      }      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) { diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java index 746f2b50ac3a..3abf7ce665ae 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java @@ -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; @@ -282,6 +289,39 @@ public class WindowProcessControllerTests extends WindowTestsBase {      }      @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);          activity.mVisibleRequested = true;  |