diff options
| author | 2025-02-18 04:30:05 -0800 | |
|---|---|---|
| committer | 2025-02-18 04:30:05 -0800 | |
| commit | 4abe48eab007a6655a4e37f2a4736ebf833c1076 (patch) | |
| tree | 602a9cb3309f2bc0deec3f4a951cdc3ad349dd33 | |
| parent | f7ece4456a7262d8e326824829e5167b7226ba35 (diff) | |
| parent | 47112c6299e196df53741eba86e123905999ddcd (diff) | |
Merge "Improve window style cache" into main
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) { + } + } +} |