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

Commit acde48fb authored by Android Build Coastguard Worker's avatar Android Build Coastguard Worker
Browse files

Snap for 13477589 from d84b3e5a to 25Q3-release

Change-Id: I57f7cad0ed661e93c9a4d19fe130044bbfb61039
parents ff81d1a9 d84b3e5a
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -205,3 +205,14 @@ flag {
        purpose: PURPOSE_BUGFIX
    }
}

flag {
    namespace: "windowing_sdk"
    name: "fix_moving_unfocused_task"
    description: "Avoid moving the visible and unfocused multi-window mode Task to front"
    bug: "399860102"
    is_fixed_read_only: true
    metadata {
        purpose: PURPOSE_BUGFIX
    }
}
 No newline at end of file
+9 −10
Original line number Diff line number Diff line
@@ -32,21 +32,20 @@ public interface AppZoomOut {
    void setProgress(float progress);

    /**
     * Sets the top-level scaling factor applied to all content on the screen during a zoom-out.
     * Sets the squeeze effect progress.
     *
     * <p>The {@code scale} parameter determines the current zoom level, ranging from {@code 0f} to
     * {@code 1f}.
     * <p>The {@code progress} parameter determines the current zoom level and surface crop, ranging
     * from {@code 0f} to {@code 1f}.
     * <ul>
     * <li>A value of {@code 1.0f} indicates no scaling (content is displayed at its original
     * size).</li>
     * <li>A value of {@code 0.0f} represents the maximum zoom-out, effectively scaling the
     * content to zero size (though visually it might be constrained).</li>
     * <li>A value of {@code 0f} indicates no scaling and cropping (content is displayed at its
     * original size).</li>
     * <li>A value of {@code 0.0f} represents the maximum zoom-out, effectively scaling and cropping
     * the content to the max pushback level</li>
     * <li>Values between {@code 0.0f} and {@code 1.0f} represent intermediate zoom levels.</li>
     * </ul>
     *
     * @param scale The scaling factor to apply, where {@code 1.0f} is no scale and {@code 0.0f} is
     *              maximum zoom-out.
     * @param progress The progress to set the squeeze zoom effect to.
     */
    void setTopLevelScale(float scale);
    void setTopLevelProgress(float progress);

}
+7 −7
Original line number Diff line number Diff line
@@ -85,7 +85,7 @@ public class AppZoomOutController implements RemoteCallable<AppZoomOutController
        AppZoomOutDisplayAreaOrganizer appDisplayAreaOrganizer = new AppZoomOutDisplayAreaOrganizer(
                context, displayLayout, mainExecutor);
        TopLevelZoomOutDisplayAreaOrganizer topLevelDisplayAreaOrganizer =
                new TopLevelZoomOutDisplayAreaOrganizer(displayLayout, mainExecutor);
                new TopLevelZoomOutDisplayAreaOrganizer(displayLayout, context, mainExecutor);
        return new AppZoomOutController(context, shellInit, shellTaskOrganizer, displayController,
                appDisplayAreaOrganizer, topLevelDisplayAreaOrganizer, mainExecutor);
    }
@@ -135,13 +135,13 @@ public class AppZoomOutController implements RemoteCallable<AppZoomOutController

    /**
     * Scales all content on the screen belonging to
     * {@link DisplayAreaOrganizer#FEATURE_WINDOWED_MAGNIFICATION}.
     * {@link DisplayAreaOrganizer#FEATURE_WINDOWED_MAGNIFICATION} and applies a cropping.
     *
     * @param scale scale factor to be applied to the surfaces.
     * @param progress progress to be applied to the top-level zoom effect.
     */
    private void setTopLevelScale(float scale) {
    private void setTopLevelProgress(float progress) {
        if (enableLppAssistInvocationEffect()) {
            mTopLevelDisplayAreaOrganizer.setScale(scale);
            mTopLevelDisplayAreaOrganizer.setProgress(progress);
        }
    }

@@ -197,8 +197,8 @@ public class AppZoomOutController implements RemoteCallable<AppZoomOutController
        }

        @Override
        public void setTopLevelScale(float scale) {
            mMainExecutor.execute(() -> AppZoomOutController.this.setTopLevelScale(scale));
        public void setTopLevelProgress(float progress) {
            mMainExecutor.execute(() -> AppZoomOutController.this.setTopLevelProgress(progress));
        }
    }
}
+0 −120
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.wm.shell.appzoomout;

import android.content.Context;
import android.util.ArrayMap;
import android.view.Display;
import android.view.SurfaceControl;
import android.window.DisplayAreaAppearedInfo;
import android.window.DisplayAreaInfo;
import android.window.DisplayAreaOrganizer;
import android.window.WindowContainerToken;

import com.android.wm.shell.common.DisplayLayout;

import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;

/** Display area organizer that manages the top level zoom out UI and states. */
public class TopLevelZoomOutDisplayAreaOrganizer extends DisplayAreaOrganizer {
    private final DisplayLayout mDisplayLayout = new DisplayLayout();
    private final Map<WindowContainerToken, SurfaceControl> mDisplayAreaTokenMap =
            new ArrayMap<>();

    private float mScale = 1f;

    public TopLevelZoomOutDisplayAreaOrganizer(DisplayLayout displayLayout, Executor mainExecutor) {
        super(mainExecutor);
        setDisplayLayout(displayLayout);
    }

    @Override
    public void onDisplayAreaAppeared(DisplayAreaInfo displayAreaInfo, SurfaceControl leash) {
        leash.setUnreleasedWarningCallSite(
                "TopLevelZoomDisplayAreaOrganizer.onDisplayAreaAppeared");
        if (displayAreaInfo.displayId == Display.DEFAULT_DISPLAY) {
            mDisplayAreaTokenMap.put(displayAreaInfo.token, leash);
        }
    }

    @Override
    public void onDisplayAreaVanished(DisplayAreaInfo displayAreaInfo) {
        final SurfaceControl leash = mDisplayAreaTokenMap.get(displayAreaInfo.token);
        if (leash != null) {
            leash.release();
        }
        mDisplayAreaTokenMap.remove(displayAreaInfo.token);
    }

    /**
     * Registers the TopLevelZoomOutDisplayAreaOrganizer to manage the display area of
     * {@link DisplayAreaOrganizer#FEATURE_WINDOWED_MAGNIFICATION}.
     */
    void registerOrganizer() {
        final List<DisplayAreaAppearedInfo> displayAreaInfos = registerOrganizer(
                DisplayAreaOrganizer.FEATURE_WINDOWED_MAGNIFICATION);
        for (int i = 0; i < displayAreaInfos.size(); i++) {
            final DisplayAreaAppearedInfo info = displayAreaInfos.get(i);
            onDisplayAreaAppeared(info.getDisplayAreaInfo(), info.getLeash());
        }
    }

    @Override
    public void unregisterOrganizer() {
        super.unregisterOrganizer();
        reset();
    }

    void setScale(float scale) {
        if (mScale == scale) {
            return;
        }

        mScale = scale;
        apply();
    }

    private void apply() {
        SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
        mDisplayAreaTokenMap.forEach((token, leash) -> updateSurface(tx, leash, mScale));
        tx.apply();
    }

    private void reset() {
        setScale(1f);
    }

    private void updateSurface(SurfaceControl.Transaction tx, SurfaceControl leash, float scale) {
        tx
                .setScale(leash, scale, scale)
                .setPosition(leash, (1f - scale) * mDisplayLayout.width() * 0.5f,
                        (1f - scale) * mDisplayLayout.height() * 0.5f);
    }

    void setDisplayLayout(DisplayLayout displayLayout) {
        mDisplayLayout.set(displayLayout);
    }

    void onRotateDisplay(Context context, int toRotation) {
        if (mDisplayLayout.rotation() == toRotation) {
            return;
        }
        mDisplayLayout.rotateTo(context.getResources(), toRotation);
    }
}
+178 −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.wm.shell.appzoomout

import android.content.Context
import android.util.ArrayMap
import android.view.Choreographer
import android.view.Display
import android.view.SurfaceControl
import android.window.DisplayAreaInfo
import android.window.DisplayAreaOrganizer
import android.window.WindowContainerToken
import com.android.internal.policy.ScreenDecorationsUtils
import com.android.wm.shell.common.DisplayLayout
import java.util.concurrent.Executor
import kotlin.math.max

private const val SqueezeEffectMaxThicknessDp = 16
// Defines the amount the squeeze border overlaps the shrinking content on the shorter display edge.
// At full progress, the overlap is 4 dp on the shorter display edge. On the longer display edge, it
// will be more than 4 dp, depending on the display aspect ratio.
private const val SqueezeEffectOverlapShortEdgeThicknessDp = 4

/** Display area organizer that manages the top level zoom out UI and states.  */
class TopLevelZoomOutDisplayAreaOrganizer(
    displayLayout: DisplayLayout,
    private val context: Context,
    mainExecutor: Executor
) : DisplayAreaOrganizer(mainExecutor) {

    private val mDisplayAreaTokenMap: MutableMap<WindowContainerToken, SurfaceControl> = ArrayMap()
    private val mDisplayLayout = DisplayLayout()
    private var cornerRadius = 1f
    private var mProgress = 1f

    init {
        setDisplayLayout(displayLayout)
    }

    override fun onDisplayAreaAppeared(displayAreaInfo: DisplayAreaInfo, leash: SurfaceControl) {
        leash.setUnreleasedWarningCallSite("TopLevelZoomDisplayAreaOrganizer.onDisplayAreaAppeared")
        if (displayAreaInfo.displayId == Display.DEFAULT_DISPLAY) {
            mDisplayAreaTokenMap[displayAreaInfo.token] = leash
        }
    }

    override fun onDisplayAreaVanished(displayAreaInfo: DisplayAreaInfo) {
        val leash = mDisplayAreaTokenMap[displayAreaInfo.token]
        leash?.release()
        mDisplayAreaTokenMap.remove(displayAreaInfo.token)
    }

    /**
     * Registers the TopLevelZoomOutDisplayAreaOrganizer to manage the display area of
     * [DisplayAreaOrganizer.FEATURE_WINDOWED_MAGNIFICATION].
     */
    fun registerOrganizer() {
        val displayAreaInfos = registerOrganizer(FEATURE_WINDOWED_MAGNIFICATION)
        for (i in displayAreaInfos.indices) {
            val info = displayAreaInfos[i]
            onDisplayAreaAppeared(info.displayAreaInfo, info.leash)
        }
    }

    override fun unregisterOrganizer() {
        super.unregisterOrganizer()
        reset()
    }

    fun setProgress(progress: Float) {
        if (mProgress == progress) {
            return
        }

        mProgress = progress
        apply()
    }

    private fun apply() {
        val tx = SurfaceControl.Transaction()
        mDisplayAreaTokenMap.values.forEach { leash: SurfaceControl ->
            updateSurface(tx, leash, mProgress)
        }
        tx.apply()
    }

    private fun reset() {
        setProgress(1f)
    }

    private fun updateSurface(
        tx: SurfaceControl.Transaction,
        leash: SurfaceControl,
        progress: Float
    ) {
        if (progress == 0f) {
            // Reset when scale is set back to 0.
            tx
                .setCrop(leash, null)
                .setScale(leash, 1f, 1f)
                .setPosition(leash, 0f, 0f)
                .setCornerRadius(leash, 0f)
            return
        }
        // Get display dimensions once
        val displayWidth = mDisplayLayout.width()
        val displayHeight = mDisplayLayout.height()
        val displayWidthF = displayWidth.toFloat()
        val displayHeightF = displayHeight.toFloat()

        // Convert DP thickness values to pixels
        val maxThicknessPx = mDisplayLayout.dpToPx(SqueezeEffectMaxThicknessDp)
        val overlapShortEdgeThicknessPx = mDisplayLayout.dpToPx(SqueezeEffectOverlapShortEdgeThicknessDp)

        // Determine the longer edge of the display
        val longEdgePx = max(displayWidth, displayHeight) // Will be Int, but division with Float promotes

        // Calculate the potential for zooming based on thickness parameters
        // This represents how much the content "shrinks" due to the squeeze effect on both sides.
        val zoomPotentialPx = (maxThicknessPx - overlapShortEdgeThicknessPx) * 2f

        val zoomOutScale = 1f - (progress * zoomPotentialPx / longEdgePx)

        // Calculate the current thickness of the squeeze effect based on progress
        val squeezeThickness = maxThicknessPx * progress

        // Calculate the X and Y offsets needed to center the scaled content.
        // These values are also used to adjust the crop region.
        // (1f - zoomOutScale) is the percentage of size reduction.
        // Half of this reduction, applied to the width/height, gives the offset for centering.
        val positionXOffset = (1f - zoomOutScale) * displayWidthF * 0.5f
        val positionYOffset = (1f - zoomOutScale) * displayHeightF * 0.5f

        // Calculate crop values.
        // The squeezeThickness acts as an initial margin/inset.
        // This margin is then reduced by the positionOffset, because as the view scales down
        // and moves towards the center, less cropping is needed to achieve the same visual margin
        // relative to the scaled content.
        val horizontalCrop = squeezeThickness - positionXOffset
        val verticalCrop = squeezeThickness - positionYOffset

        // Calculate the right and bottom crop coordinates
        val cropRight = displayWidthF - horizontalCrop
        val cropBottom = displayHeightF - verticalCrop

        tx
            .setCrop(leash, horizontalCrop, verticalCrop, cropRight, cropBottom)
            .setCornerRadius(leash, cornerRadius * zoomOutScale)
            .setScale(leash, zoomOutScale, zoomOutScale)
            .setPosition(leash, positionXOffset, positionYOffset)
            .setFrameTimelineVsync(Choreographer.getInstance().vsyncId)
    }

    fun setDisplayLayout(displayLayout: DisplayLayout) {
        mDisplayLayout.set(displayLayout)
        cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context)
    }

    fun onRotateDisplay(context: Context, toRotation: Int) {
        if (mDisplayLayout.rotation() == toRotation) {
            return
        }
        mDisplayLayout.rotateTo(context.resources, toRotation)
    }
}
Loading