Merge changes Ibd113f4f,I09c2feff into tm-qpr-dev

* changes:
  Remove client side deferred config for cached state
  Defer sending config change to cached process
This commit is contained in:
Riddle Hsu
2022-05-16 03:12:44 +00:00
committed by Android (Google) Code Review
6 changed files with 112 additions and 121 deletions

View File

@@ -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 @Override
public void updateProcessState(int processState, boolean fromIpc) { public void updateProcessState(int processState, boolean fromIpc) {
final boolean wasCached;
synchronized (mAppThread) { synchronized (mAppThread) {
if (mLastProcessState == processState) { if (mLastProcessState == processState) {
return; return;
} }
wasCached = isCachedProcessState();
mLastProcessState = processState; mLastProcessState = processState;
// Defer the top state for VM to avoid aggressive JIT compilation affecting activity // Defer the top state for VM to avoid aggressive JIT compilation affecting activity
// launch time. // launch time.
@@ -3446,22 +3432,6 @@ public final class ActivityThread extends ClientTransactionHandler
+ (fromIpc ? " (from ipc" : "")); + (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. */ /** Update VM state based on ActivityManager.PROCESS_STATE_* constants. */

View File

@@ -32,8 +32,6 @@ interface ActivityThreadInternal {
boolean isInDensityCompatMode(); boolean isInDensityCompatMode();
boolean isCachedProcessState();
Application getApplication(); Application getApplication();
ArrayList<ComponentCallbacks2> collectComponentCallbacks(boolean includeUiContexts); ArrayList<ComponentCallbacks2> collectComponentCallbacks(boolean includeUiContexts);

View File

@@ -124,12 +124,6 @@ class ConfigurationController {
* @param config The new configuration. * @param config The new configuration.
*/ */
void handleConfigurationChanged(@NonNull Configuration config) { 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"); Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "configChanged");
handleConfigurationChanged(config, null /* compat */); handleConfigurationChanged(config, null /* compat */);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

View File

@@ -31,7 +31,6 @@ import static org.junit.Assert.assertTrue;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.app.Activity; import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityThread; import android.app.ActivityThread;
import android.app.ActivityThread.ActivityClientRecord; import android.app.ActivityThread.ActivityClientRecord;
import android.app.IApplicationThread; import android.app.IApplicationThread;
@@ -570,53 +569,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 @Test
public void testResumeAfterNewIntent() { public void testResumeAfterNewIntent() {
final Activity activity = mActivityTestRule.launchActivity(new Intent()); final Activity activity = mActivityTestRule.launchActivity(new Intent());

View File

@@ -16,6 +16,7 @@
package com.android.server.wm; 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.ActivityManager.PROCESS_STATE_NONEXISTENT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.content.res.Configuration.ASSETS_SEQ_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. */ /** Whether the process configuration is waiting to be dispatched to the process. */
private boolean mHasPendingConfigurationChange; 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 {@link DisplayArea} as a listener to override config changes. {@code null} if not
* registered. * registered.
@@ -316,8 +322,27 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
return mCurProcState; 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) { public void setReportedProcState(int repProcState) {
final int prevProcState = mRepProcState;
mRepProcState = repProcState; 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() { int getReportedProcState() {
@@ -1328,12 +1353,22 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
@Override @Override
public void onConfigurationChanged(Configuration newGlobalConfig) { public void onConfigurationChanged(Configuration newGlobalConfig) {
super.onConfigurationChanged(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 if (mPauseConfigurationDispatchCount > 0) {
public void onRequestedOverrideConfigurationChanged(Configuration overrideConfiguration) { mHasPendingConfigurationChange = true;
super.onRequestedOverrideConfigurationChanged(overrideConfiguration); return;
}
dispatchConfiguration(config);
} }
@Override @Override
@@ -1359,25 +1394,6 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
resolvedConfig.seq = newParentConfig.seq; 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) { void dispatchConfiguration(Configuration config) {
mHasPendingConfigurationChange = false; mHasPendingConfigurationChange = false;
if (mThread == null) { if (mThread == null) {
@@ -1388,29 +1404,47 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
} }
return; 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, ProtoLog.v(WM_DEBUG_CONFIGURATION, "Sending to proc %s new config %s", mName,
config); config);
if (Build.IS_DEBUGGABLE && mHasImeService) { if (Build.IS_DEBUGGABLE && mHasImeService) {
// TODO (b/135719017): Temporary log for debugging IME service. // TODO (b/135719017): Temporary log for debugging IME service.
Slog.v(TAG_CONFIGURATION, "Sending to IME proc " + mName + " new config " + config); Slog.v(TAG_CONFIGURATION, "Sending to IME proc " + mName + " new config " + config);
} }
mHasCachedConfiguration = false;
try { try {
config.seq = mAtm.increaseConfigurationSeqLocked(); mAtm.getLifecycleManager().scheduleTransaction(thread,
mAtm.getLifecycleManager().scheduleTransaction(mThread,
ConfigurationChangeItem.obtain(config)); ConfigurationChangeItem.obtain(config));
setLastReportedConfiguration(config);
} catch (Exception e) { } 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) { void setLastReportedConfiguration(Configuration config) {
// Synchronize for the access from setReportedProcState().
synchronized (mLastReportedConfiguration) {
mLastReportedConfiguration.setTo(config); mLastReportedConfiguration.setTo(config);
} }
Configuration getLastReportedConfiguration() {
return mLastReportedConfiguration;
} }
void pauseConfigurationDispatch() { 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. This increment ensures that the client won't ignore the configuration.
config.seq = mAtm.increaseConfigurationSeqLocked(); config.seq = mAtm.increaseConfigurationSeqLocked();
} }
// LaunchActivityItem includes the latest process configuration.
mHasCachedConfiguration = false;
return config; return config;
} }
@@ -1688,7 +1724,8 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
} }
pw.println(prefix + " Configuration=" + getConfiguration()); pw.println(prefix + " Configuration=" + getConfiguration());
pw.println(prefix + " OverrideConfiguration=" + getRequestedOverrideConfiguration()); pw.println(prefix + " OverrideConfiguration=" + getRequestedOverrideConfiguration());
pw.println(prefix + " mLastReportedConfiguration=" + mLastReportedConfiguration); pw.println(prefix + " mLastReportedConfiguration=" + (mHasCachedConfiguration
? ("(cached) " + mLastReportedConfiguration) : mLastReportedConfiguration));
final int stateFlags = mActivityStateFlags; final int stateFlags = mActivityStateFlags;
if (stateFlags != ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER) { if (stateFlags != ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER) {

View File

@@ -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_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT; 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.never;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; 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.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import android.Manifest; import android.Manifest;
import android.app.ActivityManager;
import android.app.ClientTransactionHandler;
import android.app.IApplicationThread; import android.app.IApplicationThread;
import android.app.servertransaction.ConfigurationChangeItem;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo;
import android.content.pm.ServiceInfo; import android.content.pm.ServiceInfo;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.graphics.Rect; import android.graphics.Rect;
import android.os.LocaleList; import android.os.LocaleList;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit; import android.platform.test.annotations.Presubmit;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder; import org.mockito.InOrder;
import org.mockito.Mockito; import org.mockito.Mockito;
@@ -281,6 +288,39 @@ public class WindowProcessControllerTests extends WindowTestsBase {
mWpc.getConfiguration().seq, globalSeq); 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 @Test
public void testComputeOomAdjFromActivities() { public void testComputeOomAdjFromActivities() {
final ActivityRecord activity = createActivityRecord(mWpc); final ActivityRecord activity = createActivityRecord(mWpc);