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

Commit bdd9d83c authored by Johannes Gallmann's avatar Johannes Gallmann Committed by Android (Google) Code Review
Browse files

Merge "[SysUI][Floaty] Add top level zoom API to AppZoomOut" into main

parents f67fd4f4 767f734c
Loading
Loading
Loading
Loading
+19 −0
Original line number Diff line number Diff line
@@ -30,4 +30,23 @@ public interface AppZoomOut {
     * between [0,1], 0 when fullscreen, 1 when it's at the max pushback level.
     */
    void setProgress(float progress);

    /**
     * Sets the top-level scaling factor applied to all content on the screen during a zoom-out.
     *
     * <p>The {@code scale} parameter determines the current zoom level, 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>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.
     */
    void setTopLevelScale(float scale);

}
+47 −11
Original line number Diff line number Diff line
@@ -18,7 +18,9 @@ package com.android.wm.shell.appzoomout;

import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;

import static com.android.systemui.Flags.spatialModelAppPushback;
import static com.android.systemui.shared.Flags.enableLppAssistInvocationEffect;

import android.app.ActivityManager;
import android.app.WindowConfiguration;
@@ -26,6 +28,7 @@ import android.content.Context;
import android.content.res.Configuration;
import android.util.Slog;
import android.window.DisplayAreaInfo;
import android.window.DisplayAreaOrganizer;
import android.window.WindowContainerTransaction;

import androidx.annotation.Nullable;
@@ -50,7 +53,8 @@ public class AppZoomOutController implements RemoteCallable<AppZoomOutController
    private final Context mContext;
    private final ShellTaskOrganizer mTaskOrganizer;
    private final DisplayController mDisplayController;
    private final AppZoomOutDisplayAreaOrganizer mDisplayAreaOrganizer;
    private final AppZoomOutDisplayAreaOrganizer mAppDisplayAreaOrganizer;
    private final TopLevelZoomOutDisplayAreaOrganizer mTopLevelDisplayAreaOrganizer;
    private final ShellExecutor mMainExecutor;
    private final AppZoomOutImpl mImpl = new AppZoomOutImpl();

@@ -77,24 +81,28 @@ public class AppZoomOutController implements RemoteCallable<AppZoomOutController
    public static AppZoomOutController create(Context context, ShellInit shellInit,
            ShellTaskOrganizer shellTaskOrganizer, DisplayController displayController,
            DisplayLayout displayLayout, @ShellMainThread ShellExecutor mainExecutor) {
        AppZoomOutDisplayAreaOrganizer displayAreaOrganizer = new AppZoomOutDisplayAreaOrganizer(
        AppZoomOutDisplayAreaOrganizer appDisplayAreaOrganizer = new AppZoomOutDisplayAreaOrganizer(
                context, displayLayout, mainExecutor);
        TopLevelZoomOutDisplayAreaOrganizer topLevelDisplayAreaOrganizer =
                new TopLevelZoomOutDisplayAreaOrganizer(displayLayout, mainExecutor);
        return new AppZoomOutController(context, shellInit, shellTaskOrganizer, displayController,
                displayAreaOrganizer, mainExecutor);
                appDisplayAreaOrganizer, topLevelDisplayAreaOrganizer, mainExecutor);
    }

    @VisibleForTesting
    AppZoomOutController(Context context, ShellInit shellInit,
            ShellTaskOrganizer shellTaskOrganizer, DisplayController displayController,
            AppZoomOutDisplayAreaOrganizer displayAreaOrganizer,
            AppZoomOutDisplayAreaOrganizer appDisplayAreaOrganizer,
            TopLevelZoomOutDisplayAreaOrganizer topLevelDisplayAreaOrganizer,
            @ShellMainThread ShellExecutor mainExecutor) {
        mContext = context;
        mTaskOrganizer = shellTaskOrganizer;
        mDisplayController = displayController;
        mDisplayAreaOrganizer = displayAreaOrganizer;
        mAppDisplayAreaOrganizer = appDisplayAreaOrganizer;
        mTopLevelDisplayAreaOrganizer = topLevelDisplayAreaOrganizer;
        mMainExecutor = mainExecutor;

        if (spatialModelAppPushback()) {
        if (spatialModelAppPushback() || enableLppAssistInvocationEffect()) {
            shellInit.addInitCallback(this::onInit, this);
        }
    }
@@ -106,7 +114,12 @@ public class AppZoomOutController implements RemoteCallable<AppZoomOutController
        mDisplayController.addDisplayChangingController(this);
        updateDisplayLayout(mContext.getDisplayId());

        mDisplayAreaOrganizer.registerOrganizer();
        if (spatialModelAppPushback()) {
            mAppDisplayAreaOrganizer.registerOrganizer();
        }
        if (enableLppAssistInvocationEffect()) {
            mTopLevelDisplayAreaOrganizer.registerOrganizer();
        }
    }

    public AppZoomOut asAppZoomOut() {
@@ -114,7 +127,19 @@ public class AppZoomOutController implements RemoteCallable<AppZoomOutController
    }

    public void setProgress(float progress) {
        mDisplayAreaOrganizer.setProgress(progress);
        mAppDisplayAreaOrganizer.setProgress(progress);
    }

    /**
     * Scales all content on the screen belonging to
     * {@link DisplayAreaOrganizer#FEATURE_WINDOWED_MAGNIFICATION}.
     *
     * @param scale scale factor to be applied to the surfaces.
     */
    private void setTopLevelScale(float scale) {
        if (enableLppAssistInvocationEffect()) {
            mTopLevelDisplayAreaOrganizer.setScale(scale);
        }
    }

    void updateDisplayLayout(int displayId) {
@@ -123,7 +148,10 @@ public class AppZoomOutController implements RemoteCallable<AppZoomOutController
            Slog.w(TAG, "Failed to get new DisplayLayout.");
            return;
        }
        mDisplayAreaOrganizer.setDisplayLayout(newDisplayLayout);
        mAppDisplayAreaOrganizer.setDisplayLayout(newDisplayLayout);
        if (enableLppAssistInvocationEffect()) {
            mTopLevelDisplayAreaOrganizer.setDisplayLayout(newDisplayLayout);
        }
    }

    @Override
@@ -132,7 +160,7 @@ public class AppZoomOutController implements RemoteCallable<AppZoomOutController
            return;
        }
        if (taskInfo.getActivityType() == WindowConfiguration.ACTIVITY_TYPE_HOME) {
            mDisplayAreaOrganizer.setIsHomeTaskFocused(taskInfo.isFocused);
            mAppDisplayAreaOrganizer.setIsHomeTaskFocused(taskInfo.isFocused);
        }
    }

@@ -141,7 +169,10 @@ public class AppZoomOutController implements RemoteCallable<AppZoomOutController
            @Nullable DisplayAreaInfo newDisplayAreaInfo, WindowContainerTransaction wct) {
        // TODO: verify if there is synchronization issues.
        if (toRotation != ROTATION_UNDEFINED) {
            mDisplayAreaOrganizer.onRotateDisplay(mContext, toRotation);
            mAppDisplayAreaOrganizer.onRotateDisplay(mContext, toRotation);
            if (enableLppAssistInvocationEffect()) {
                mTopLevelDisplayAreaOrganizer.onRotateDisplay(mContext, toRotation);
            }
        }
    }

@@ -161,5 +192,10 @@ public class AppZoomOutController implements RemoteCallable<AppZoomOutController
        public void setProgress(float progress) {
            mMainExecutor.execute(() -> AppZoomOutController.this.setProgress(progress));
        }

        @Override
        public void setTopLevelScale(float scale) {
            mMainExecutor.execute(() -> AppZoomOutController.this.setTopLevelScale(scale));
        }
    }
}
+120 −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.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);
    }
}
+6 −4
Original line number Diff line number Diff line
@@ -48,7 +48,8 @@ public class AppZoomOutControllerTest extends ShellTestCase {

    @Mock private ShellTaskOrganizer mTaskOrganizer;
    @Mock private DisplayController mDisplayController;
    @Mock private AppZoomOutDisplayAreaOrganizer mDisplayAreaOrganizer;
    @Mock private AppZoomOutDisplayAreaOrganizer mAppDisplayAreaOrganizer;
    @Mock private TopLevelZoomOutDisplayAreaOrganizer mTopLevelDisplayAreaOrganizer;
    @Mock private ShellExecutor mExecutor;
    @Mock private ActivityManager.RunningTaskInfo mRunningTaskInfo;

@@ -64,7 +65,8 @@ public class AppZoomOutControllerTest extends ShellTestCase {

        ShellInit shellInit = spy(new ShellInit(mExecutor));
        mController = spy(new AppZoomOutController(mContext, shellInit, mTaskOrganizer,
                mDisplayController, mDisplayAreaOrganizer, mExecutor));
                mDisplayController, mAppDisplayAreaOrganizer, mTopLevelDisplayAreaOrganizer,
                mExecutor));
    }

    @Test
@@ -73,7 +75,7 @@ public class AppZoomOutControllerTest extends ShellTestCase {
        when(mRunningTaskInfo.getActivityType()).thenReturn(ACTIVITY_TYPE_HOME);
        mController.onFocusTaskChanged(mRunningTaskInfo);

        verify(mDisplayAreaOrganizer).setIsHomeTaskFocused(true);
        verify(mAppDisplayAreaOrganizer).setIsHomeTaskFocused(true);
    }

    @Test
@@ -82,6 +84,6 @@ public class AppZoomOutControllerTest extends ShellTestCase {
        when(mRunningTaskInfo.getActivityType()).thenReturn(ACTIVITY_TYPE_HOME);
        mController.onFocusTaskChanged(mRunningTaskInfo);

        verify(mDisplayAreaOrganizer).setIsHomeTaskFocused(false);
        verify(mAppDisplayAreaOrganizer).setIsHomeTaskFocused(false);
    }
}