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

Commit 3eb074e5 authored by Miranda Kephart's avatar Miranda Kephart Committed by Android (Google) Code Review
Browse files

Merge "Extract screenshot window code" into main

parents 58aac31d 6df1d0ab
Loading
Loading
Loading
Loading
+15 −117
Original line number Diff line number Diff line
@@ -17,7 +17,6 @@
package com.android.systemui.screenshot;

import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;

import static com.android.systemui.Flags.screenshotSaveImageExporter;
import static com.android.systemui.screenshot.LogConfig.DEBUG_ANIM;
@@ -31,7 +30,6 @@ import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_INTERAC

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.BroadcastReceiver;
@@ -52,17 +50,12 @@ import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
import android.view.ScrollCaptureResponse;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewRootImpl;
import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.widget.Toast;
import android.window.WindowContext;

import com.android.internal.logging.UiEventLogger;
import com.android.internal.policy.PhoneWindow;
import com.android.settingslib.applications.InterestingConfigChanges;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.broadcast.BroadcastSender;
@@ -115,11 +108,9 @@ public class ScreenshotController implements InteractiveScreenshotHandler {
    private final BroadcastDispatcher mBroadcastDispatcher;
    private final ScreenshotActionsController mActionsController;

    private final WindowManager mWindowManager;
    private final WindowManager.LayoutParams mWindowLayoutParams;
    @Nullable
    private final ScreenshotSoundController mScreenshotSoundController;
    private final PhoneWindow mWindow;
    private final ScreenshotWindow mWindow;
    private final Display mDisplay;
    private final ScrollCaptureExecutor mScrollCaptureExecutor;
    private final ScreenshotNotificationSmartActionsProvider
@@ -135,8 +126,6 @@ public class ScreenshotController implements InteractiveScreenshotHandler {
    private Bitmap mScreenBitmap;
    private SaveImageInBackgroundTask mSaveInBgTask;
    private boolean mScreenshotTakenInPortrait;
    private boolean mAttachRequested;
    private boolean mDetachRequested;
    private Animator mScreenshotAnimation;
    private RequestCallback mCurrentRequestCallback;
    private String mPackageName = "";
@@ -155,7 +144,7 @@ public class ScreenshotController implements InteractiveScreenshotHandler {
    @AssistedInject
    ScreenshotController(
            Context context,
            WindowManager windowManager,
            ScreenshotWindow.Factory screenshotWindowFactory,
            FeatureFlags flags,
            ScreenshotShelfViewProxy.Factory viewProxyFactory,
            ScreenshotSmartActions screenshotSmartActions,
@@ -195,9 +184,8 @@ public class ScreenshotController implements InteractiveScreenshotHandler {
        mScreenshotHandler.setDefaultTimeoutMillis(SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS);

        mDisplay = display;
        mWindowManager = windowManager;
        final Context displayContext = context.createDisplayContext(display);
        mContext = (WindowContext) displayContext.createWindowContext(TYPE_SCREENSHOT, null);
        mWindow = screenshotWindowFactory.create(mDisplay);
        mContext = mWindow.getContext();
        mFlags = flags;
        mUserManager = userManager;
        mMessageContainerController = messageContainerController;
@@ -213,17 +201,10 @@ public class ScreenshotController implements InteractiveScreenshotHandler {
            mViewProxy.requestDismissal(SCREENSHOT_INTERACTION_TIMEOUT);
        });

        // Setup the window that we are going to use
        mWindowLayoutParams = FloatingWindowUtil.getFloatingWindowParams();
        mWindowLayoutParams.setTitle("ScreenshotAnimation");

        mWindow = FloatingWindowUtil.getFloatingWindow(mContext);
        mWindow.setWindowManager(mWindowManager, null, null);

        mConfigChanges.applyNewConfig(context.getResources());
        reloadAssets();

        mActionExecutor = actionExecutorFactory.create(mWindow, mViewProxy,
        mActionExecutor = actionExecutorFactory.create(mWindow.getWindow(), mViewProxy,
                () -> {
                    finishDismiss();
                    return Unit.INSTANCE;
@@ -318,12 +299,12 @@ public class ScreenshotController implements InteractiveScreenshotHandler {
        }

        // The window is focusable by default
        setWindowFocusable(true);
        mWindow.setFocusable(true);
        mViewProxy.requestFocus();

        enqueueScrollCaptureRequest(requestId, screenshot.getUserHandle());

        attachWindow();
        mWindow.attachWindow();

        boolean showFlash;
        if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE) {
@@ -347,13 +328,10 @@ public class ScreenshotController implements InteractiveScreenshotHandler {

        mViewProxy.setScreenshot(screenshot);

        // ignore system bar insets for the purpose of window layout
        mWindow.getDecorView().setOnApplyWindowInsetsListener(
                (v, insets) -> WindowInsets.CONSUMED);
    }

    void prepareViewForNewScreenshot(@NonNull ScreenshotData screenshot, String oldPackageName) {
        withWindowAttached(() -> {
        mWindow.whenWindowAttached(() -> {
            mAnnouncementResolver.getScreenshotAnnouncement(
                    screenshot.getUserHandle().getIdentifier(),
                    announcement -> {
@@ -444,7 +422,7 @@ public class ScreenshotController implements InteractiveScreenshotHandler {
            @Override
            public void onTouchOutside() {
                // TODO(159460485): Remove this when focus is handled properly in the system
                setWindowFocusable(false);
                mWindow.setFocusable(false);
            }
        });

@@ -457,9 +435,9 @@ public class ScreenshotController implements InteractiveScreenshotHandler {
    private void enqueueScrollCaptureRequest(UUID requestId, UserHandle owner) {
        // Wait until this window is attached to request because it is
        // the reference used to locate the target window (below).
        withWindowAttached(() -> {
        mWindow.whenWindowAttached(() -> {
            requestScrollCapture(requestId, owner);
            mWindow.peekDecorView().getViewRootImpl().setActivityConfigCallback(
            mWindow.setActivityConfigCallback(
                    new ViewRootImpl.ActivityConfigCallback() {
                        @Override
                        public void onConfigurationChanged(Configuration overrideConfig,
@@ -472,8 +450,7 @@ public class ScreenshotController implements InteractiveScreenshotHandler {
                                // to set up in the new orientation.
                                mScreenshotHandler.postDelayed(
                                        () -> requestScrollCapture(requestId, owner), 150);
                                mViewProxy.updateInsets(
                                        mWindowManager.getCurrentWindowMetrics().getWindowInsets());
                                mViewProxy.updateInsets(mWindow.getWindowInsets());
                                // Screenshot animation calculations won't be valid anymore,
                                // so just end
                                if (mScreenshotAnimation != null
@@ -489,7 +466,7 @@ public class ScreenshotController implements InteractiveScreenshotHandler {
    private void requestScrollCapture(UUID requestId, UserHandle owner) {
        mScrollCaptureExecutor.requestScrollCapture(
                mDisplay.getDisplayId(),
                mWindow.getDecorView().getWindowToken(),
                mWindow.getWindowToken(),
                (response) -> {
                    mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_IMPRESSION,
                            0, response.getPackageName());
@@ -528,61 +505,9 @@ public class ScreenshotController implements InteractiveScreenshotHandler {
                mViewProxy::startLongScreenshotTransition);
    }

    private void withWindowAttached(Runnable action) {
        View decorView = mWindow.getDecorView();
        if (decorView.isAttachedToWindow()) {
            action.run();
        } else {
            decorView.getViewTreeObserver().addOnWindowAttachListener(
                    new ViewTreeObserver.OnWindowAttachListener() {
                        @Override
                        public void onWindowAttached() {
                            mAttachRequested = false;
                            decorView.getViewTreeObserver().removeOnWindowAttachListener(this);
                            action.run();
                        }

                        @Override
                        public void onWindowDetached() {
                        }
                    });

        }
    }

    @MainThread
    private void attachWindow() {
        View decorView = mWindow.getDecorView();
        if (decorView.isAttachedToWindow() || mAttachRequested) {
            return;
        }
        if (DEBUG_WINDOW) {
            Log.d(TAG, "attachWindow");
        }
        mAttachRequested = true;
        mWindowManager.addView(decorView, mWindowLayoutParams);
        decorView.requestApplyInsets();

        ViewGroup layout = decorView.requireViewById(android.R.id.content);
        layout.setClipChildren(false);
        layout.setClipToPadding(false);
    }

    @Override
    public void removeWindow() {
        final View decorView = mWindow.peekDecorView();
        if (decorView != null && decorView.isAttachedToWindow()) {
            if (DEBUG_WINDOW) {
                Log.d(TAG, "Removing screenshot window");
            }
            mWindowManager.removeViewImmediate(decorView);
            mDetachRequested = false;
        }
        if (mAttachRequested && !mDetachRequested) {
            mDetachRequested = true;
            withWindowAttached(this::removeWindow);
        }

        mWindow.removeWindow();
        mViewProxy.stopInputListening();
    }

@@ -759,33 +684,6 @@ public class ScreenshotController implements InteractiveScreenshotHandler {
                .getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1;
    }

    /**
     * Updates the window focusability.  If the window is already showing, then it updates the
     * window immediately, otherwise the layout params will be applied when the window is next
     * shown.
     */
    private void setWindowFocusable(boolean focusable) {
        if (DEBUG_WINDOW) {
            Log.d(TAG, "setWindowFocusable: " + focusable);
        }
        int flags = mWindowLayoutParams.flags;
        if (focusable) {
            mWindowLayoutParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
        } else {
            mWindowLayoutParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
        }
        if (mWindowLayoutParams.flags == flags) {
            if (DEBUG_WINDOW) {
                Log.d(TAG, "setWindowFocusable: skipping, already " + focusable);
            }
            return;
        }
        final View decorView = mWindow.peekDecorView();
        if (decorView != null && decorView.isAttachedToWindow()) {
            mWindowManager.updateViewLayout(decorView, mWindowLayoutParams);
        }
    }

    private Rect getFullScreenRect() {
        DisplayMetrics displayMetrics = new DisplayMetrics();
        mDisplay.getRealMetrics(displayMetrics);
@@ -826,6 +724,6 @@ public class ScreenshotController implements InteractiveScreenshotHandler {
         *
         * @param display                 display to capture
         */
        LegacyScreenshotController create(Display display);
        ScreenshotController create(Display display);
    }
}
+194 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.systemui.screenshot

import android.R
import android.annotation.MainThread
import android.content.Context
import android.graphics.PixelFormat
import android.os.IBinder
import android.util.Log
import android.view.Display
import android.view.View
import android.view.ViewGroup
import android.view.ViewRootImpl
import android.view.ViewTreeObserver.OnWindowAttachListener
import android.view.Window
import android.view.WindowInsets
import android.view.WindowManager
import android.window.WindowContext
import com.android.internal.policy.PhoneWindow
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject

/** Creates and manages the window in which the screenshot UI is displayed. */
class ScreenshotWindow
@AssistedInject
constructor(
    private val windowManager: WindowManager,
    private val context: Context,
    @Assisted private val display: Display,
) {

    val window: PhoneWindow =
        PhoneWindow(
            context
                .createDisplayContext(display)
                .createWindowContext(WindowManager.LayoutParams.TYPE_SCREENSHOT, null)
        )
    private val params =
        WindowManager.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT,
                0, /* xpos */
                0, /* ypos */
                WindowManager.LayoutParams.TYPE_SCREENSHOT,
                WindowManager.LayoutParams.FLAG_FULLSCREEN or
                    WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or
                    WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
                    WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or
                    WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
                    WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
                PixelFormat.TRANSLUCENT
            )
            .apply {
                layoutInDisplayCutoutMode =
                    WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
                setFitInsetsTypes(0)
                // This is needed to let touches pass through outside the touchable areas
                privateFlags =
                    privateFlags or WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
                title = "ScreenshotUI"
            }
    private var attachRequested: Boolean = false
    private var detachRequested: Boolean = false

    init {
        window.requestFeature(Window.FEATURE_NO_TITLE)
        window.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)
        window.setBackgroundDrawableResource(R.color.transparent)
        window.setWindowManager(windowManager, null, null)
    }

    @MainThread
    fun attachWindow() {
        val decorView: View = window.getDecorView()
        if (decorView.isAttachedToWindow || attachRequested) {
            return
        }
        if (LogConfig.DEBUG_WINDOW) {
            Log.d(TAG, "attachWindow")
        }
        attachRequested = true
        windowManager.addView(decorView, params)

        decorView.requestApplyInsets()
        decorView.requireViewById<ViewGroup>(R.id.content).apply {
            clipChildren = false
            clipToPadding = false
            // ignore system bar insets for the purpose of window layout
            setOnApplyWindowInsetsListener { _, _ -> WindowInsets.CONSUMED }
        }
    }

    fun whenWindowAttached(action: Runnable) {
        val decorView: View = window.getDecorView()
        if (decorView.isAttachedToWindow) {
            action.run()
        } else {
            decorView
                .getViewTreeObserver()
                .addOnWindowAttachListener(
                    object : OnWindowAttachListener {
                        override fun onWindowAttached() {
                            attachRequested = false
                            decorView.getViewTreeObserver().removeOnWindowAttachListener(this)
                            action.run()
                        }

                        override fun onWindowDetached() {}
                    }
                )
        }
    }

    fun removeWindow() {
        val decorView: View? = window.peekDecorView()
        if (decorView != null && decorView.isAttachedToWindow) {
            if (LogConfig.DEBUG_WINDOW) {
                Log.d(TAG, "Removing screenshot window")
            }
            windowManager.removeViewImmediate(decorView)
            detachRequested = false
        }
        if (attachRequested && !detachRequested) {
            detachRequested = true
            whenWindowAttached { removeWindow() }
        }
    }

    /**
     * Updates the window focusability. If the window is already showing, then it updates the window
     * immediately, otherwise the layout params will be applied when the window is next shown.
     */
    fun setFocusable(focusable: Boolean) {
        if (LogConfig.DEBUG_WINDOW) {
            Log.d(TAG, "setWindowFocusable: $focusable")
        }
        val flags: Int = params.flags
        if (focusable) {
            params.flags = params.flags and WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE.inv()
        } else {
            params.flags = params.flags or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
        }
        if (params.flags == flags) {
            if (LogConfig.DEBUG_WINDOW) {
                Log.d(TAG, "setWindowFocusable: skipping, already $focusable")
            }
            return
        }
        window.peekDecorView()?.also {
            if (it.isAttachedToWindow) {
                windowManager.updateViewLayout(it, params)
            }
        }
    }

    fun getContext(): WindowContext = window.context as WindowContext

    fun getWindowToken(): IBinder = window.decorView.windowToken

    fun getWindowInsets(): WindowInsets = windowManager.currentWindowMetrics.windowInsets

    fun setContentView(view: View) {
        window.setContentView(view)
    }

    fun setActivityConfigCallback(callback: ViewRootImpl.ActivityConfigCallback) {
        window.peekDecorView().viewRootImpl.setActivityConfigCallback(callback)
    }

    @AssistedFactory
    interface Factory {
        fun create(display: Display): ScreenshotWindow
    }

    companion object {
        private const val TAG = "ScreenshotWindow"
    }
}