Merge "Implement flag to not relaunch on dock config change for apps without -desk resources" into tm-qpr-dev

This commit is contained in:
William Xiao
2023-03-24 22:58:39 +00:00
committed by Android (Google) Code Review
10 changed files with 210 additions and 4 deletions

View File

@@ -29,6 +29,8 @@ import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
import static android.app.servertransaction.ActivityLifecycleItem.PRE_ON_CREATE;
import static android.content.ContentResolver.DEPRECATE_DATA_COLUMNS;
import static android.content.ContentResolver.DEPRECATE_DATA_PREFIX;
import static android.content.res.Configuration.UI_MODE_TYPE_DESK;
import static android.content.res.Configuration.UI_MODE_TYPE_MASK;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static android.window.ConfigurationHelper.freeTextLayoutCachesIfNeeded;
@@ -194,6 +196,7 @@ import android.window.SplashScreen;
import android.window.SplashScreenView;
import android.window.WindowProviderService;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IVoiceInteractor;
@@ -5890,9 +5893,21 @@ public final class ActivityThread extends ClientTransactionHandler
final boolean shouldUpdateResources = hasPublicResConfigChange
|| shouldUpdateResources(activityToken, currentResConfig, newConfig,
amOverrideConfig, movedToDifferentDisplay, hasPublicResConfigChange);
// TODO(b/266924897): temporary workaround, remove for U.
boolean skipActivityRelaunchWhenDocking = activity.getResources().getBoolean(
R.bool.config_skipActivityRelaunchWhenDocking);
int handledConfigChanges = activity.mActivityInfo.getRealConfigChanged();
if (skipActivityRelaunchWhenDocking && onlyDeskInUiModeChanged(activity.mCurrentConfig,
newConfig)) {
// If we're not relaunching this activity when docking, we should send the configuration
// changed event. Pretend as if the activity is handling uiMode config changes in its
// manifest so that we'll report any dock changes.
handledConfigChanges |= ActivityInfo.CONFIG_UI_MODE;
}
final boolean shouldReportChange = shouldReportChange(activity.mCurrentConfig, newConfig,
r != null ? r.mSizeConfigurations : null,
activity.mActivityInfo.getRealConfigChanged());
r != null ? r.mSizeConfigurations : null, handledConfigChanges);
// Nothing significant, don't proceed with updating and reporting.
if (!shouldUpdateResources && !shouldReportChange) {
return null;
@@ -5938,6 +5953,25 @@ public final class ActivityThread extends ClientTransactionHandler
return configToReport;
}
/**
* Returns true if the uiMode configuration changed, and desk mode
* ({@link android.content.res.Configuration#UI_MODE_TYPE_DESK}) was the only change to uiMode.
*/
private boolean onlyDeskInUiModeChanged(Configuration oldConfig, Configuration newConfig) {
boolean deskModeChanged = isInDeskUiMode(oldConfig) != isInDeskUiMode(newConfig);
// UI mode contains fields other than the UI mode type, so determine if any other fields
// changed.
boolean uiModeOtherFieldsChanged =
(oldConfig.uiMode & ~UI_MODE_TYPE_MASK) != (newConfig.uiMode & ~UI_MODE_TYPE_MASK);
return deskModeChanged && !uiModeOtherFieldsChanged;
}
private static boolean isInDeskUiMode(Configuration config) {
return (config.uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_DESK;
}
/**
* Returns {@code true} if {@link Activity#onConfigurationChanged(Configuration)} should be
* dispatched.

View File

@@ -1452,6 +1452,16 @@ public final class AssetManager implements AutoCloseable {
}
}
/**
* @hide
*/
Configuration[] getSizeAndUiModeConfigurations() {
synchronized (this) {
ensureValidLocked();
return nativeGetSizeAndUiModeConfigurations(mObject);
}
}
/**
* Change the configuration used when retrieving resources. Not for use by
* applications.
@@ -1603,6 +1613,7 @@ public final class AssetManager implements AutoCloseable {
private static native @Nullable String nativeGetResourceEntryName(long ptr, @AnyRes int resid);
private static native @Nullable String[] nativeGetLocales(long ptr, boolean excludeSystem);
private static native @Nullable Configuration[] nativeGetSizeConfigurations(long ptr);
private static native @Nullable Configuration[] nativeGetSizeAndUiModeConfigurations(long ptr);
private static native void nativeSetResourceResolutionLoggingEnabled(long ptr, boolean enabled);
private static native @Nullable String nativeGetLastResourceResolution(long ptr);

View File

@@ -2207,6 +2207,11 @@ public class Resources {
return mResourcesImpl.getSizeConfigurations();
}
/** @hide */
public Configuration[] getSizeAndUiModeConfigurations() {
return mResourcesImpl.getSizeAndUiModeConfigurations();
}
/**
* Return the compatibility mode information for the application.
* The returned object should be treated as read-only.

View File

@@ -203,6 +203,10 @@ public class ResourcesImpl {
return mAssets.getSizeConfigurations();
}
Configuration[] getSizeAndUiModeConfigurations() {
return mAssets.getSizeAndUiModeConfigurations();
}
CompatibilityInfo getCompatibilityInfo() {
return mDisplayAdjustments.getCompatibilityInfo();
}

View File

@@ -93,6 +93,7 @@ static struct configuration_offsets_t {
jfieldID mScreenWidthDpOffset;
jfieldID mScreenHeightDpOffset;
jfieldID mScreenLayoutOffset;
jfieldID mUiMode;
} gConfigurationOffsets;
static struct arraymap_offsets_t {
@@ -1027,10 +1028,11 @@ static jobject ConstructConfigurationObject(JNIEnv* env, const ResTable_config&
env->SetIntField(result, gConfigurationOffsets.mScreenWidthDpOffset, config.screenWidthDp);
env->SetIntField(result, gConfigurationOffsets.mScreenHeightDpOffset, config.screenHeightDp);
env->SetIntField(result, gConfigurationOffsets.mScreenLayoutOffset, config.screenLayout);
env->SetIntField(result, gConfigurationOffsets.mUiMode, config.uiMode);
return result;
}
static jobjectArray NativeGetSizeConfigurations(JNIEnv* env, jclass /*clazz*/, jlong ptr) {
static jobjectArray GetSizeAndUiModeConfigurations(JNIEnv* env, jlong ptr) {
ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
auto configurations = assetmanager->GetResourceConfigurations(true /*exclude_system*/,
false /*exclude_mipmap*/);
@@ -1057,6 +1059,14 @@ static jobjectArray NativeGetSizeConfigurations(JNIEnv* env, jclass /*clazz*/, j
return array;
}
static jobjectArray NativeGetSizeConfigurations(JNIEnv* env, jclass /*clazz*/, jlong ptr) {
return GetSizeAndUiModeConfigurations(env, ptr);
}
static jobjectArray NativeGetSizeAndUiModeConfigurations(JNIEnv* env, jclass /*clazz*/, jlong ptr) {
return GetSizeAndUiModeConfigurations(env, ptr);
}
static jintArray NativeAttributeResolutionStack(
JNIEnv* env, jclass /*clazz*/, jlong ptr,
jlong theme_ptr, jint xml_style_res,
@@ -1487,6 +1497,8 @@ static const JNINativeMethod gAssetManagerMethods[] = {
{"nativeGetLocales", "(JZ)[Ljava/lang/String;", (void*)NativeGetLocales},
{"nativeGetSizeConfigurations", "(J)[Landroid/content/res/Configuration;",
(void*)NativeGetSizeConfigurations},
{"nativeGetSizeAndUiModeConfigurations", "(J)[Landroid/content/res/Configuration;",
(void*)NativeGetSizeAndUiModeConfigurations},
// Style attribute related methods.
{"nativeAttributeResolutionStack", "(JJIII)[I", (void*)NativeAttributeResolutionStack},
@@ -1565,6 +1577,7 @@ int register_android_content_AssetManager(JNIEnv* env) {
GetFieldIDOrDie(env, configurationClass, "screenHeightDp", "I");
gConfigurationOffsets.mScreenLayoutOffset =
GetFieldIDOrDie(env, configurationClass, "screenLayout", "I");
gConfigurationOffsets.mUiMode = GetFieldIDOrDie(env, configurationClass, "uiMode", "I");
jclass arrayMapClass = FindClassOrDie(env, "android/util/ArrayMap");
gArrayMapOffsets.classObject = MakeGlobalRefOrDie(env, arrayMapClass);

View File

@@ -5430,6 +5430,12 @@
treatment for stretched issues in camera viewfinder. -->
<bool name="config_isCameraCompatControlForStretchedIssuesEnabled">false</bool>
<!-- Docking is a uiMode configuration change and will cause activities to relaunch if it's not
handled. If true, the configuration change will be sent but activities will not be
relaunched upon docking. Apps with desk resources will behave like normal, since they may
expect the relaunch upon the desk uiMode change. -->
<bool name="config_skipActivityRelaunchWhenDocking">false</bool>
<!-- If true, hide the display cutout with display area -->
<bool name="config_hideDisplayCutoutWithDisplayArea">false</bool>

View File

@@ -4491,6 +4491,7 @@
<java-symbol type="bool" name="config_isWindowManagerCameraCompatTreatmentEnabled" />
<java-symbol type="bool" name="config_isWindowManagerCameraCompatSplitScreenAspectRatioEnabled" />
<java-symbol type="bool" name="config_isCameraCompatControlForStretchedIssuesEnabled" />
<java-symbol type="bool" name="config_skipActivityRelaunchWhenDocking" />
<java-symbol type="bool" name="config_hideDisplayCutoutWithDisplayArea" />

View File

@@ -100,6 +100,7 @@ import static android.content.res.Configuration.EMPTY;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
import static android.content.res.Configuration.UI_MODE_TYPE_DESK;
import static android.content.res.Configuration.UI_MODE_TYPE_MASK;
import static android.content.res.Configuration.UI_MODE_TYPE_VR_HEADSET;
import static android.os.Build.VERSION_CODES.HONEYCOMB;
@@ -827,6 +828,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
/** Whether the input to this activity will be dropped during the current playing animation. */
private boolean mIsInputDroppedForAnimation;
/**
* Whether the application has desk mode resources. Calculated and cached when
* {@link #hasDeskResources()} is called.
*/
@Nullable
private Boolean mHasDeskResources;
boolean mHandleExitSplashScreen;
@TransferSplashScreenState
int mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_IDLE;
@@ -9490,7 +9498,14 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
configChanged |= CONFIG_UI_MODE;
}
return (changes&(~configChanged)) != 0;
// TODO(b/274944389): remove workaround after long-term solution is implemented
// Don't restart due to desk mode change if the app does not have desk resources.
if (mWmService.mSkipActivityRelaunchWhenDocking && onlyDeskInUiModeChanged(changesConfig)
&& !hasDeskResources()) {
configChanged |= CONFIG_UI_MODE;
}
return (changes & (~configChanged)) != 0;
}
/**
@@ -9503,6 +9518,50 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
!= isInVrUiMode(lastReportedConfig));
}
/**
* Returns true if the uiMode configuration changed, and desk mode
* ({@link android.content.res.Configuration#UI_MODE_TYPE_DESK}) was the only change to uiMode.
*/
private boolean onlyDeskInUiModeChanged(Configuration lastReportedConfig) {
final Configuration currentConfig = getConfiguration();
boolean deskModeChanged = isInDeskUiMode(currentConfig) != isInDeskUiMode(
lastReportedConfig);
// UI mode contains fields other than the UI mode type, so determine if any other fields
// changed.
boolean uiModeOtherFieldsChanged =
(currentConfig.uiMode & ~UI_MODE_TYPE_MASK) != (lastReportedConfig.uiMode
& ~UI_MODE_TYPE_MASK);
return deskModeChanged && !uiModeOtherFieldsChanged;
}
/**
* Determines whether or not the application has desk mode resources.
*/
boolean hasDeskResources() {
if (mHasDeskResources != null) {
// We already determined this, return the cached value.
return mHasDeskResources;
}
mHasDeskResources = false;
try {
Resources packageResources = mAtmService.mContext.createPackageContextAsUser(
packageName, 0, UserHandle.of(mUserId)).getResources();
for (Configuration sizeConfiguration :
packageResources.getSizeAndUiModeConfigurations()) {
if (isInDeskUiMode(sizeConfiguration)) {
mHasDeskResources = true;
break;
}
}
} catch (PackageManager.NameNotFoundException e) {
Slog.w(TAG, "Exception thrown during checking for desk resources " + this, e);
}
return mHasDeskResources;
}
private int getConfigurationChanges(Configuration lastReportedConfig) {
// Determine what has changed. May be nothing, if this is a config that has come back from
// the app after going idle. In that case we just want to leave the official config object
@@ -9798,6 +9857,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
return (config.uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_VR_HEADSET;
}
private static boolean isInDeskUiMode(Configuration config) {
return (config.uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_DESK;
}
String getProcessName() {
return info.applicationInfo.processName;
}

View File

@@ -552,6 +552,16 @@ public class WindowManagerService extends IWindowManager.Stub
// everything else on screen). Otherwise, it will be put under always-on-top stacks.
final boolean mAssistantOnTopOfDream;
/**
* If true, don't relaunch the activity upon receiving a configuration change to transition to
* or from the {@link UI_MODE_TYPE_DESK} uiMode, which is sent when docking. The configuration
* change will still be sent regardless, only the relaunch is skipped. Apps with desk resources
* are exempt from this and will behave like normal, since they may expect the relaunch upon the
* desk uiMode change.
*/
@VisibleForTesting
boolean mSkipActivityRelaunchWhenDocking;
final boolean mLimitedAlphaCompositing;
final int mMaxUiWidth;
@@ -1226,6 +1236,8 @@ public class WindowManagerService extends IWindowManager.Stub
com.android.internal.R.bool.config_perDisplayFocusEnabled);
mAssistantOnTopOfDream = context.getResources().getBoolean(
com.android.internal.R.bool.config_assistantOnTopOfDream);
mSkipActivityRelaunchWhenDocking = context.getResources()
.getBoolean(R.bool.config_skipActivityRelaunchWhenDocking);
mLetterboxConfiguration = new LetterboxConfiguration(
// Using SysUI context to have access to Material colors extracted from Wallpaper.

View File

@@ -45,6 +45,7 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.content.res.Configuration.UI_MODE_TYPE_DESK;
import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
import static android.os.Process.NOBODY_UID;
import static android.view.Display.DEFAULT_DISPLAY;
@@ -489,6 +490,62 @@ public class ActivityRecordTests extends WindowTestsBase {
.scheduleTransaction(any(), any(), isA(ActivityConfigurationChangeItem.class));
}
@Test
public void testDeskModeChange_doesNotRelaunch() throws RemoteException {
mWm.mSkipActivityRelaunchWhenDocking = true;
final ActivityRecord activity = createActivityWithTask();
// The activity will already be relaunching out of the gate, finish the relaunch so we can
// test properly.
activity.finishRelaunching();
// Clear out any calls to scheduleTransaction from launching the activity.
reset(mAtm.getLifecycleManager());
final Task task = activity.getTask();
activity.setState(RESUMED, "Testing");
// Send a desk UI mode config update.
final Configuration newConfig = new Configuration(task.getConfiguration());
newConfig.uiMode |= UI_MODE_TYPE_DESK;
task.onRequestedOverrideConfigurationChanged(newConfig);
ensureActivityConfiguration(activity);
// The activity shouldn't start relaunching since it doesn't have any desk resources.
assertFalse(activity.isRelaunching());
// The configuration change is still sent to the activity, even if it doesn't relaunch.
final ActivityConfigurationChangeItem expected =
ActivityConfigurationChangeItem.obtain(newConfig);
verify(mAtm.getLifecycleManager()).scheduleTransaction(
eq(activity.app.getThread()), eq(activity.token), eq(expected));
}
@Test
public void testDeskModeChange_relaunchesWithDeskResources() {
mWm.mSkipActivityRelaunchWhenDocking = true;
final ActivityRecord activity = createActivityWithTask();
// The activity will already be relaunching out of the gate, finish the relaunch so we can
// test properly.
activity.finishRelaunching();
// Activities with desk resources should get relaunched when a UI_MODE_TYPE_DESK change
// comes in.
doReturn(true).when(activity).hasDeskResources();
final Task task = activity.getTask();
activity.setState(RESUMED, "Testing");
// Send a desk UI mode config update.
final Configuration newConfig = new Configuration(task.getConfiguration());
newConfig.uiMode |= UI_MODE_TYPE_DESK;
task.onRequestedOverrideConfigurationChanged(newConfig);
ensureActivityConfiguration(activity);
// The activity will relaunch since it has desk resources.
assertTrue(activity.isRelaunching());
}
@Test
public void testSetRequestedOrientationUpdatesConfiguration() throws Exception {
final ActivityRecord activity = new ActivityBuilder(mAtm)