summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Riddle Hsu <riddlehsu@google.com> 2025-02-18 04:30:05 -0800
committer Android (Google) Code Review <android-gerrit@google.com> 2025-02-18 04:30:05 -0800
commit4abe48eab007a6655a4e37f2a4736ebf833c1076 (patch)
tree602a9cb3309f2bc0deec3f4a951cdc3ad349dd33
parentf7ece4456a7262d8e326824829e5167b7226ba35 (diff)
parent47112c6299e196df53741eba86e123905999ddcd (diff)
Merge "Improve window style cache" into main
-rw-r--r--core/java/android/window/flags/windowing_frontend.aconfig11
-rw-r--r--core/java/com/android/internal/policy/PhoneWindow.java22
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java117
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java15
-rw-r--r--services/core/java/com/android/server/wm/utils/WindowStyleCache.java81
-rw-r--r--services/tests/wmtests/res/values/styles.xml10
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java19
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/utils/WindowStyleCacheTest.java63
8 files changed, 298 insertions, 40 deletions
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 25dc6723aa78..60f2c811dd1f 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -40,6 +40,17 @@ flag {
}
flag {
+ name: "cache_window_style"
+ namespace: "windowing_frontend"
+ description: "Cache common window styles"
+ bug: "350394503"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "edge_to_edge_by_default"
namespace: "windowing_frontend"
description: "Make app go edge-to-edge by default when targeting SDK 35 or greater"
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index d73e2d47348b..4d7709632c41 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -471,6 +471,20 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
}
/**
+ * This is similar to {@link #isOptingOutEdgeToEdgeEnforcement} but the caller needs to check
+ * whether the app declares style to opt out.
+ */
+ public static boolean isOptOutEdgeToEdgeEnabled(ApplicationInfo info, boolean local) {
+ final boolean disabled = Flags.disableOptOutEdgeToEdge()
+ && (local
+ // Calling this doesn't require a permission.
+ ? CompatChanges.isChangeEnabled(DISABLE_OPT_OUT_EDGE_TO_EDGE)
+ // Calling this requires permissions.
+ : info.isChangeEnabled(DISABLE_OPT_OUT_EDGE_TO_EDGE));
+ return !disabled;
+ }
+
+ /**
* Returns whether the given application is opting out edge-to-edge enforcement.
*
* @param info The application to query.
@@ -480,13 +494,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
*/
public static boolean isOptingOutEdgeToEdgeEnforcement(ApplicationInfo info, boolean local,
TypedArray windowStyle) {
- final boolean disabled = Flags.disableOptOutEdgeToEdge()
- && (local
- // Calling this doesn't require a permission.
- ? CompatChanges.isChangeEnabled(DISABLE_OPT_OUT_EDGE_TO_EDGE)
- // Calling this requires permissions.
- : info.isChangeEnabled(DISABLE_OPT_OUT_EDGE_TO_EDGE));
- return !disabled && windowStyle.getBoolean(
+ return isOptOutEdgeToEdgeEnabled(info, local) && windowStyle.getBoolean(
R.styleable.Window_windowOptOutEdgeToEdgeEnforcement, false /* default */);
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 333d91a1b08f..ebadeac70dd1 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -289,6 +289,7 @@ import android.content.pm.PackageManagerInternal;
import android.content.pm.UserProperties;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Insets;
import android.graphics.PixelFormat;
@@ -352,7 +353,6 @@ import com.android.internal.app.ResolverActivity;
import com.android.internal.content.ReferrerIntent;
import com.android.internal.os.TimeoutRecord;
import com.android.internal.os.TransferPipe;
-import com.android.internal.policy.AttributeCache;
import com.android.internal.policy.PhoneWindow;
import com.android.internal.protolog.ProtoLog;
import com.android.internal.util.XmlUtils;
@@ -479,6 +479,8 @@ final class ActivityRecord extends WindowToken {
final String processName; // process where this component wants to run
final String taskAffinity; // as per ActivityInfo.taskAffinity
final boolean stateNotNeeded; // As per ActivityInfo.flags
+ @Nullable
+ final WindowStyle mWindowStyle;
@VisibleForTesting
int mHandoverLaunchDisplayId = INVALID_DISPLAY; // Handover launch display id to next activity.
@VisibleForTesting
@@ -1926,22 +1928,17 @@ final class ActivityRecord extends WindowToken {
? android.R.style.Theme : android.R.style.Theme_Holo;
}
- final AttributeCache.Entry ent = AttributeCache.instance().get(packageName,
- realTheme, com.android.internal.R.styleable.Window, mUserId);
-
- if (ent != null) {
- final boolean styleTranslucent = ent.array.getBoolean(
- com.android.internal.R.styleable.Window_windowIsTranslucent, false);
- final boolean styleFloating = ent.array.getBoolean(
- com.android.internal.R.styleable.Window_windowIsFloating, false);
- mOccludesParent = !(styleTranslucent || styleFloating)
+ final WindowStyle style = mAtmService.getWindowStyle(packageName, realTheme, mUserId);
+ mWindowStyle = style;
+ if (style != null) {
+ mOccludesParent = !(style.isTranslucent() || style.isFloating())
// This style is propagated to the main window attributes with
// FLAG_SHOW_WALLPAPER from PhoneWindow#generateLayout.
- || ent.array.getBoolean(R.styleable.Window_windowShowWallpaper, false);
+ || style.showWallpaper();
mStyleFillsParent = mOccludesParent;
- mNoDisplay = ent.array.getBoolean(R.styleable.Window_windowNoDisplay, false);
- mOptOutEdgeToEdge = PhoneWindow.isOptingOutEdgeToEdgeEnforcement(
- aInfo.applicationInfo, false /* local */, ent.array);
+ mNoDisplay = style.noDisplay();
+ mOptOutEdgeToEdge = style.optOutEdgeToEdge() && PhoneWindow.isOptOutEdgeToEdgeEnabled(
+ aInfo.applicationInfo, false /* local */);
} else {
mStyleFillsParent = mOccludesParent = true;
mNoDisplay = false;
@@ -2272,21 +2269,17 @@ final class ActivityRecord extends WindowToken {
return false;
}
- final AttributeCache.Entry ent = AttributeCache.instance().get(pkg, theme,
- com.android.internal.R.styleable.Window, mWmService.mCurrentUserId);
- if (ent == null) {
+ final WindowStyle style = theme == this.theme
+ ? mWindowStyle : mAtmService.getWindowStyle(pkg, theme, mUserId);
+ if (style == null) {
// Whoops! App doesn't exist. Um. Okay. We'll just pretend like we didn't
// see that.
return false;
}
- final boolean windowIsTranslucent = ent.array.getBoolean(
- com.android.internal.R.styleable.Window_windowIsTranslucent, false);
- final boolean windowIsFloating = ent.array.getBoolean(
- com.android.internal.R.styleable.Window_windowIsFloating, false);
- final boolean windowShowWallpaper = ent.array.getBoolean(
- com.android.internal.R.styleable.Window_windowShowWallpaper, false);
- final boolean windowDisableStarting = ent.array.getBoolean(
- com.android.internal.R.styleable.Window_windowDisablePreview, false);
+ final boolean windowIsTranslucent = style.isTranslucent();
+ final boolean windowIsFloating = style.isFloating();
+ final boolean windowShowWallpaper = style.showWallpaper();
+ final boolean windowDisableStarting = style.disablePreview();
ProtoLog.v(WM_DEBUG_STARTING_WINDOW,
"Translucent=%s Floating=%s ShowWallpaper=%s Disable=%s",
windowIsTranslucent, windowIsFloating, windowShowWallpaper,
@@ -7135,14 +7128,10 @@ final class ActivityRecord extends WindowToken {
if (theme == 0) {
return false;
}
- final AttributeCache.Entry ent = AttributeCache.instance().get(packageName, theme,
- R.styleable.Window, mWmService.mCurrentUserId);
- if (ent != null) {
- if (ent.array.hasValue(R.styleable.Window_windowSplashScreenBehavior)) {
- return ent.array.getInt(R.styleable.Window_windowSplashScreenBehavior,
- SPLASH_SCREEN_BEHAVIOR_DEFAULT)
- == SPLASH_SCREEN_BEHAVIOR_ICON_PREFERRED;
- }
+ final WindowStyle style = theme == this.theme
+ ? mWindowStyle : mAtmService.getWindowStyle(packageName, theme, mUserId);
+ if (style != null) {
+ return style.mSplashScreenBehavior == SPLASH_SCREEN_BEHAVIOR_ICON_PREFERRED;
}
return false;
}
@@ -9814,6 +9803,68 @@ final class ActivityRecord extends WindowToken {
int mBackgroundColor;
}
+ static class WindowStyle {
+ private static final int FLAG_IS_TRANSLUCENT = 1;
+ private static final int FLAG_IS_FLOATING = 1 << 1;
+ private static final int FLAG_SHOW_WALLPAPER = 1 << 2;
+ private static final int FLAG_NO_DISPLAY = 1 << 3;
+ private static final int FLAG_DISABLE_PREVIEW = 1 << 4;
+ private static final int FLAG_OPT_OUT_EDGE_TO_EDGE = 1 << 5;
+
+ final int mFlags;
+
+ @SplashScreenBehavior
+ final int mSplashScreenBehavior;
+
+ WindowStyle(TypedArray array) {
+ int flags = 0;
+ if (array.getBoolean(R.styleable.Window_windowIsTranslucent, false)) {
+ flags |= FLAG_IS_TRANSLUCENT;
+ }
+ if (array.getBoolean(R.styleable.Window_windowIsFloating, false)) {
+ flags |= FLAG_IS_FLOATING;
+ }
+ if (array.getBoolean(R.styleable.Window_windowShowWallpaper, false)) {
+ flags |= FLAG_SHOW_WALLPAPER;
+ }
+ if (array.getBoolean(R.styleable.Window_windowNoDisplay, false)) {
+ flags |= FLAG_NO_DISPLAY;
+ }
+ if (array.getBoolean(R.styleable.Window_windowDisablePreview, false)) {
+ flags |= FLAG_DISABLE_PREVIEW;
+ }
+ if (array.getBoolean(R.styleable.Window_windowOptOutEdgeToEdgeEnforcement, false)) {
+ flags |= FLAG_OPT_OUT_EDGE_TO_EDGE;
+ }
+ mFlags = flags;
+ mSplashScreenBehavior = array.getInt(R.styleable.Window_windowSplashScreenBehavior, 0);
+ }
+
+ boolean isTranslucent() {
+ return (mFlags & FLAG_IS_TRANSLUCENT) != 0;
+ }
+
+ boolean isFloating() {
+ return (mFlags & FLAG_IS_FLOATING) != 0;
+ }
+
+ boolean showWallpaper() {
+ return (mFlags & FLAG_SHOW_WALLPAPER) != 0;
+ }
+
+ boolean noDisplay() {
+ return (mFlags & FLAG_NO_DISPLAY) != 0;
+ }
+
+ boolean disablePreview() {
+ return (mFlags & FLAG_DISABLE_PREVIEW) != 0;
+ }
+
+ boolean optOutEdgeToEdge() {
+ return (mFlags & FLAG_OPT_OUT_EDGE_TO_EDGE) != 0;
+ }
+ }
+
static class Builder {
private final ActivityTaskManagerService mAtmService;
private WindowProcessController mCallerApp;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 819e117e6d05..4e2fade599ba 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -286,6 +286,7 @@ import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.uri.NeededUriGrants;
import com.android.server.uri.UriGrantsManagerInternal;
import com.android.server.wallpaper.WallpaperManagerInternal;
+import com.android.server.wm.utils.WindowStyleCache;
import com.android.wm.shell.Flags;
import java.io.BufferedReader;
@@ -500,6 +501,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
boolean mSuppressResizeConfigChanges;
+ private final WindowStyleCache<ActivityRecord.WindowStyle> mWindowStyleCache =
+ new WindowStyleCache<>(ActivityRecord.WindowStyle::new);
final UpdateConfigurationResult mTmpUpdateConfigurationResult =
new UpdateConfigurationResult();
@@ -5570,6 +5573,16 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
return mUserManagerInternal;
}
+ @Nullable
+ ActivityRecord.WindowStyle getWindowStyle(String packageName, int theme, int userId) {
+ if (!com.android.window.flags.Flags.cacheWindowStyle()) {
+ final AttributeCache.Entry ent = AttributeCache.instance().get(packageName,
+ theme, com.android.internal.R.styleable.Window, userId);
+ return ent != null ? new ActivityRecord.WindowStyle(ent.array) : null;
+ }
+ return mWindowStyleCache.get(packageName, theme, userId);
+ }
+
AppWarnings getAppWarningsLocked() {
return mAppWarnings;
}
@@ -6518,6 +6531,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
mCompatModePackages.handlePackageUninstalledLocked(name);
mPackageConfigPersister.onPackageUninstall(name, userId);
}
+ mWindowStyleCache.invalidatePackage(name);
}
@Override
@@ -6534,6 +6548,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
if (mRootWindowContainer == null) return;
mRootWindowContainer.updateActivityApplicationInfo(aInfo);
}
+ mWindowStyleCache.invalidatePackage(aInfo.packageName);
}
@Override
diff --git a/services/core/java/com/android/server/wm/utils/WindowStyleCache.java b/services/core/java/com/android/server/wm/utils/WindowStyleCache.java
new file mode 100644
index 000000000000..a253c2c75aa2
--- /dev/null
+++ b/services/core/java/com/android/server/wm/utils/WindowStyleCache.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.utils;
+
+import android.content.res.TypedArray;
+import android.util.ArrayMap;
+import android.util.SparseArray;
+
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.policy.AttributeCache;
+
+import java.util.function.Function;
+
+/**
+ * A wrapper of AttributeCache to preserve more dedicated style caches.
+ * @param <T> The type of style cache.
+ */
+public class WindowStyleCache<T> {
+ @GuardedBy("itself")
+ private final ArrayMap<String, SparseArray<T>> mCache = new ArrayMap<>();
+ private final Function<TypedArray, T> mEntryFactory;
+
+ public WindowStyleCache(Function<TypedArray, T> entryFactory) {
+ mEntryFactory = entryFactory;
+ }
+
+ /** Returns the cached entry. */
+ public T get(String packageName, int theme, int userId) {
+ SparseArray<T> themeMap;
+ synchronized (mCache) {
+ themeMap = mCache.get(packageName);
+ if (themeMap != null) {
+ T style = themeMap.get(theme);
+ if (style != null) {
+ return style;
+ }
+ }
+ }
+
+ final AttributeCache attributeCache = AttributeCache.instance();
+ if (attributeCache == null) {
+ return null;
+ }
+ final AttributeCache.Entry ent = attributeCache.get(packageName, theme,
+ R.styleable.Window, userId);
+ if (ent == null) {
+ return null;
+ }
+
+ final T style = mEntryFactory.apply(ent.array);
+ synchronized (mCache) {
+ if (themeMap == null) {
+ mCache.put(packageName, themeMap = new SparseArray<>());
+ }
+ themeMap.put(theme, style);
+ }
+ return style;
+ }
+
+ /** Called when the package is updated or removed. */
+ public void invalidatePackage(String packageName) {
+ synchronized (mCache) {
+ mCache.remove(packageName);
+ }
+ }
+}
diff --git a/services/tests/wmtests/res/values/styles.xml b/services/tests/wmtests/res/values/styles.xml
index 6857ff99e9b8..6ded2b557bcc 100644
--- a/services/tests/wmtests/res/values/styles.xml
+++ b/services/tests/wmtests/res/values/styles.xml
@@ -21,4 +21,14 @@
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
</style>
+
+ <style name="ActivityWindowStyleTest">
+ <item name="android:windowIsTranslucent">true</item>
+ <item name="android:windowIsFloating">true</item>
+ <item name="android:windowShowWallpaper">true</item>
+ <item name="android:windowNoDisplay">true</item>
+ <item name="android:windowDisablePreview">true</item>
+ <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
+ <item name="android:windowSplashScreenBehavior">icon_preferred</item>
+ </style>
</resources>
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 280e432f0245..6923deec1ebb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -992,6 +992,25 @@ public class ActivityRecordTests extends WindowTestsBase {
assertEquals(persistentSavedState, activity.getPersistentSavedState());
}
+ @Test
+ public void testReadWindowStyle() {
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setActivityTheme(
+ com.android.frameworks.wmtests.R.style.ActivityWindowStyleTest).build();
+ assertTrue(activity.isNoDisplay());
+ assertTrue("Fill parent because showWallpaper", activity.mStyleFillsParent);
+
+ final ActivityRecord.WindowStyle style = mAtm.getWindowStyle(
+ activity.packageName, activity.info.theme, activity.mUserId);
+ assertNotNull(style);
+ assertTrue(style.isTranslucent());
+ assertTrue(style.isFloating());
+ assertTrue(style.showWallpaper());
+ assertTrue(style.noDisplay());
+ assertTrue(style.disablePreview());
+ assertTrue(style.optOutEdgeToEdge());
+ assertEquals(1 /* icon_preferred */, style.mSplashScreenBehavior);
+ }
+
/**
* Verify that activity finish request is not performed if activity is finishing or is in
* incorrect state.
diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/WindowStyleCacheTest.java b/services/tests/wmtests/src/com/android/server/wm/utils/WindowStyleCacheTest.java
new file mode 100644
index 000000000000..57a340129456
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/utils/WindowStyleCacheTest.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.utils;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.policy.AttributeCache;
+
+import org.junit.Test;
+
+/**
+ * Build/Install/Run:
+ * atest WmTests:WindowStyleCacheTest
+ */
+@SmallTest
+@Presubmit
+public class WindowStyleCacheTest {
+
+ @Test
+ public void testCache() {
+ final Context context = getInstrumentation().getContext();
+ AttributeCache.init(context);
+ final WindowStyleCache<TestStyle> cache = new WindowStyleCache<>(TestStyle::new);
+ final String packageName = context.getPackageName();
+ final int theme = com.android.frameworks.wmtests.R.style.ActivityWindowStyleTest;
+ final int userId = context.getUserId();
+ final TestStyle style = cache.get(packageName, theme, userId);
+ assertNotNull(style);
+ assertSame(style, cache.get(packageName, theme, userId));
+
+ cache.invalidatePackage(packageName);
+ assertNotSame(style, cache.get(packageName, theme, userId));
+ }
+
+ private static class TestStyle {
+ TestStyle(TypedArray array) {
+ }
+ }
+}