Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 47112c62 authored by Riddle Hsu's avatar Riddle Hsu
Browse files

Improve window style cache

The cache replied on AttributeCache, but because it caches entire
TypedArray (it may be big), it only preserves a few packages.
Then it may need to read from package resources frequently, which
may add latency when starting activity:
AttributeCache > createPackageContextAsUser
 > createResources > getResources
  > createApkAssetsSupplierNotLocked > loadApkAssets

By only storing the used attributes, the size of cache becomes
very small which can be preserved per boot session.

Bug: 350394503
Flag: com.android.window.flags.cache_window_style
Test: atest ActivityRecordTests#testReadWindowStyle
            WindowStyleCacheTest

Change-Id: Ib5a9fc68a9b6340920ea9c8c7849bd9032642e17
parent 3164ec8d
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -39,6 +39,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"
+15 −7
Original line number Diff line number Diff line
@@ -470,6 +470,20 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
                                : info.isChangeEnabled(ENFORCE_EDGE_TO_EDGE))));
    }

    /**
     * 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.
     *
@@ -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 */);

    }
+84 −33
Original line number Diff line number Diff line
@@ -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;
+15 −0
Original line number Diff line number Diff line
@@ -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
+81 −0
Original line number Diff line number Diff line
/*
 * 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);
        }
    }
}
Loading