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

Commit bd67f638 authored by Miranda Kephart's avatar Miranda Kephart
Browse files

Fix screenshot UI for insets

Previously, the screenshot UI was shown on top of display cutouts.
We want the animation (e.g. flash) to display over any insets, but
the screenshot preview and actions should respect insets. This
change makes the static UI respect insets, and sets the animation
so that it smoothly animates to the location of the static preview.

Bug: 153404471
Bug: 155272789
Fix: 153404471
Fix: 155272789
Test: manual -- tested full, partial, and from overview, with various
display cutouts on and off and with navbar set to gestural/2/3-button
mode.

Change-Id: I85100a674e1736dd6c198ee85fa07cd700e839b4
parent f35625fa
Loading
Loading
Loading
Loading
+7 −61
Original line number Diff line number Diff line
@@ -14,9 +14,8 @@
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License.
  -->
<androidx.constraintlayout.widget.ConstraintLayout
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/global_screenshot_frame"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
@@ -24,72 +23,18 @@
        android:id="@+id/global_screenshot_actions_background"
        android:layout_height="@dimen/screenshot_bg_protection_height"
        android:layout_width="match_parent"
        android:layout_gravity="bottom"
        android:alpha="0.0"
        android:src="@drawable/screenshot_actions_background_protection"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"/>
    <ImageView
        android:id="@+id/global_screenshot_actions_container_background"
        android:visibility="gone"
        android:layout_height="0dp"
        android:layout_width="0dp"
        android:elevation="1dp"
        android:background="@drawable/action_chip_container_background"
        android:layout_marginStart="@dimen/screenshot_action_container_margin_horizontal"
        app:layout_constraintBottom_toBottomOf="@+id/global_screenshot_actions_container"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/global_screenshot_actions_container"
        app:layout_constraintEnd_toEndOf="@+id/global_screenshot_actions_container"/>
    <HorizontalScrollView
        android:id="@+id/global_screenshot_actions_container"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginEnd="@dimen/screenshot_action_container_margin_horizontal"
        android:layout_marginBottom="@dimen/screenshot_action_container_offset_y"
        android:paddingHorizontal="@dimen/screenshot_action_container_padding_right"
        android:paddingVertical="@dimen/screenshot_action_container_padding_vertical"
        android:elevation="1dp"
        android:scrollbars="none"
        app:layout_constraintHorizontal_bias="0"
        app:layout_constraintWidth_percent="1.0"
        app:layout_constraintWidth_max="wrap"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toEndOf="@+id/global_screenshot_preview"
        app:layout_constraintEnd_toEndOf="parent">
        <LinearLayout
            android:id="@+id/global_screenshot_actions"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
    </HorizontalScrollView>
        android:src="@drawable/screenshot_actions_background_protection"/>
    <ImageView
        android:id="@+id/global_screenshot_animated_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:visibility="gone"
        android:elevation="@dimen/screenshot_preview_elevation"
        android:background="@drawable/screenshot_rounded_corners"
        android:adjustViewBounds="true"/>
    <include layout="@layout/global_screenshot_preview"/>
    <FrameLayout
        android:id="@+id/global_screenshot_dismiss_button"
        android:layout_width="@dimen/screenshot_dismiss_button_tappable_size"
        android:layout_height="@dimen/screenshot_dismiss_button_tappable_size"
        android:elevation="7dp"
        android:visibility="gone"
        android:contentDescription="@string/screenshot_dismiss_ui_description"
        app:layout_constraintStart_toEndOf="@+id/global_screenshot_preview"
        app:layout_constraintEnd_toEndOf="@+id/global_screenshot_preview"
        app:layout_constraintTop_toTopOf="@+id/global_screenshot_preview"
        app:layout_constraintBottom_toTopOf="@+id/global_screenshot_preview">
        <ImageView
            android:id="@+id/global_screenshot_dismiss_image"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_margin="@dimen/screenshot_dismiss_button_margin"
            android:src="@drawable/screenshot_cancel"/>
    </FrameLayout>
    <ImageView
        android:id="@+id/global_screenshot_flash"
        android:layout_width="match_parent"
@@ -103,4 +48,5 @@
        android:layout_height="match_parent"
        android:visibility="gone"
        android:pointerIcon="crosshair"/>
</androidx.constraintlayout.widget.ConstraintLayout>
    <include layout="@layout/global_screenshot_static"/>
</FrameLayout>
+84 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright (C) 2020 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.
  -->
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:fitsSystemWindows="true"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <ImageView
        android:id="@+id/global_screenshot_actions_container_background"
        android:visibility="gone"
        android:layout_height="0dp"
        android:layout_width="0dp"
        android:elevation="1dp"
        android:background="@drawable/action_chip_container_background"
        android:layout_marginStart="@dimen/screenshot_action_container_margin_horizontal"
        app:layout_constraintBottom_toBottomOf="@+id/global_screenshot_actions_container"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/global_screenshot_actions_container"
        app:layout_constraintEnd_toEndOf="@+id/global_screenshot_actions_container"/>
    <HorizontalScrollView
        android:id="@+id/global_screenshot_actions_container"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginEnd="@dimen/screenshot_action_container_margin_horizontal"
        android:layout_marginBottom="@dimen/screenshot_action_container_offset_y"
        android:paddingHorizontal="@dimen/screenshot_action_container_padding_right"
        android:paddingVertical="@dimen/screenshot_action_container_padding_vertical"
        android:elevation="1dp"
        android:scrollbars="none"
        app:layout_constraintHorizontal_bias="0"
        app:layout_constraintWidth_percent="1.0"
        app:layout_constraintWidth_max="wrap"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toEndOf="@+id/global_screenshot_preview"
        app:layout_constraintEnd_toEndOf="parent">
        <LinearLayout
            android:id="@+id/global_screenshot_actions"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
    </HorizontalScrollView>
    <ImageView
        android:id="@+id/global_screenshot_animated_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="center"
        android:visibility="gone"
        android:elevation="@dimen/screenshot_preview_elevation"
        android:background="@drawable/screenshot_rounded_corners"
        android:adjustViewBounds="true"/>
    <include layout="@layout/global_screenshot_preview"/>
    <FrameLayout
        android:id="@+id/global_screenshot_dismiss_button"
        android:layout_width="@dimen/screenshot_dismiss_button_tappable_size"
        android:layout_height="@dimen/screenshot_dismiss_button_tappable_size"
        android:elevation="7dp"
        android:visibility="gone"
        android:contentDescription="@string/screenshot_dismiss_ui_description"
        app:layout_constraintStart_toEndOf="@+id/global_screenshot_preview"
        app:layout_constraintEnd_toEndOf="@+id/global_screenshot_preview"
        app:layout_constraintTop_toTopOf="@+id/global_screenshot_preview"
        app:layout_constraintBottom_toTopOf="@+id/global_screenshot_preview">
        <ImageView
            android:id="@+id/global_screenshot_dismiss_image"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_margin="@dimen/screenshot_dismiss_button_margin"
            android:src="@drawable/screenshot_cancel"/>
    </FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
+2 −2
Original line number Diff line number Diff line
@@ -308,11 +308,11 @@
    <dimen name="global_screenshot_x_scale">80dp</dimen>
    <dimen name="screenshot_bg_protection_height">242dp</dimen>
    <dimen name="screenshot_preview_elevation">6dp</dimen>
    <dimen name="screenshot_offset_y">48dp</dimen>
    <dimen name="screenshot_offset_y">32dp</dimen>
    <dimen name="screenshot_offset_x">16dp</dimen>
    <dimen name="screenshot_dismiss_button_tappable_size">48dp</dimen>
    <dimen name="screenshot_dismiss_button_margin">8dp</dimen>
    <dimen name="screenshot_action_container_offset_y">32dp</dimen>
    <dimen name="screenshot_action_container_offset_y">16dp</dimen>
    <dimen name="screenshot_action_container_corner_radius">10dp</dimen>
    <dimen name="screenshot_action_container_padding_vertical">6dp</dimen>
    <dimen name="screenshot_action_container_margin_horizontal">8dp</dimen>
+36 −52
Original line number Diff line number Diff line
@@ -64,7 +64,6 @@ import android.util.Slog;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewGroup;
@@ -212,8 +211,6 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
    private boolean mDirectionLTR = true;
    private boolean mOrientationPortrait = true;

    private float mScreenshotOffsetXPx;
    private float mScreenshotOffsetYPx;
    private float mCornerSizeX;
    private float mDismissDeltaY;

@@ -273,8 +270,6 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
        mDisplayMetrics = new DisplayMetrics();
        mDisplay.getRealMetrics(mDisplayMetrics);

        mScreenshotOffsetXPx = resources.getDimensionPixelSize(R.dimen.screenshot_offset_x);
        mScreenshotOffsetYPx = resources.getDimensionPixelSize(R.dimen.screenshot_offset_y);
        mCornerSizeX = resources.getDimensionPixelSize(R.dimen.global_screenshot_x_scale);
        mDismissDeltaY = resources.getDimensionPixelSize(R.dimen.screenshot_dismissal_height_delta);

@@ -448,12 +443,11 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
     * Takes a screenshot of the current display and shows an animation.
     */
    private void takeScreenshot(Consumer<Uri> finisher, Rect crop) {
        // copy the input Rect, since SurfaceControl.screenshot can mutate it
        Rect screenRect = new Rect(crop);
        int rot = mDisplay.getRotation();
        int width = crop.width();
        int height = crop.height();

        Rect screenRect = new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels);

        takeScreenshot(SurfaceControl.screenshot(crop, width, height, rot), finisher, screenRect);
    }

@@ -483,12 +477,11 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset

        onConfigChanged(mContext.getResources().getConfiguration());


        if (mDismissAnimation != null && mDismissAnimation.isRunning()) {
            mDismissAnimation.cancel();
        }
        // Start the post-screenshot animation
        startAnimation(finisher, screenRect.width(), screenRect.height(), screenRect);
        startAnimation(finisher, mScreenBitmap.getWidth(), mScreenBitmap.getHeight(), screenRect);
    }

    void takeScreenshot(Consumer<Uri> finisher, Runnable onComplete) {
@@ -691,8 +684,8 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
    /**
     * Starts the animation after taking the screenshot
     */
    private void startAnimation(final Consumer<Uri> finisher, int w, int h,
            @Nullable Rect screenRect) {
    private void startAnimation(
            final Consumer<Uri> finisher, int bitmapWidth, int bitmapHeight, Rect screenRect) {
        // If power save is on, show a toast so there is some visual indication that a
        // screenshot has been taken.
        PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
@@ -700,7 +693,21 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
            Toast.makeText(mContext, R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show();
        }

        mScreenshotAnimation = createScreenshotDropInAnimation(w, h, screenRect);
        mScreenshotHandler.post(() -> {
            if (!mScreenshotLayout.isAttachedToWindow()) {
                mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
            }
            mScreenshotAnimatedView.setImageBitmap(mScreenBitmap);
            mScreenshotPreview.setImageBitmap(mScreenBitmap);

            // make static preview invisible (from gone) so we can query its location on screen
            mScreenshotPreview.setVisibility(View.INVISIBLE);

            mScreenshotHandler.post(() -> {
                mScreenshotLayout.getViewTreeObserver().addOnComputeInternalInsetsListener(this);

                mScreenshotAnimation =
                        createScreenshotDropInAnimation(bitmapWidth, bitmapHeight, screenRect);

                saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() {
                            @Override
@@ -708,13 +715,6 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
                                showUiOnActionsReady(imageData);
                            }
                        });
        mScreenshotHandler.post(() -> {
            if (!mScreenshotLayout.isAttachedToWindow()) {
                mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
            }
            mScreenshotLayout.getViewTreeObserver().addOnComputeInternalInsetsListener(this);

            mScreenshotHandler.post(() -> {

                // Play the shutter sound to notify that we've taken a screenshot
                mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
@@ -726,18 +726,13 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
        });
    }

    private AnimatorSet createScreenshotDropInAnimation(int width, int height, Rect bounds) {
        float screenWidth = mDisplayMetrics.widthPixels;
        float screenHeight = mDisplayMetrics.heightPixels;
    private AnimatorSet createScreenshotDropInAnimation(
            int bitmapWidth, int bitmapHeight, Rect bounds) {
        Rect previewBounds = new Rect();
        mScreenshotPreview.getBoundsOnScreen(previewBounds);

        int rotation = mContext.getDisplay().getRotation();
        float cornerScale;
        if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) {
            cornerScale = (mCornerSizeX /  screenHeight);
        } else {
            cornerScale = (mCornerSizeX / screenWidth);
        }
        float currentScale = width / screenWidth;
        float cornerScale = mCornerSizeX / (mOrientationPortrait ? bitmapWidth : bitmapHeight);
        float currentScale = bounds.height() / (float) bitmapHeight;

        mScreenshotAnimatedView.setScaleX(currentScale);
        mScreenshotAnimatedView.setScaleY(currentScale);
@@ -745,9 +740,6 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
        mScreenshotAnimatedView.setPivotX(0);
        mScreenshotAnimatedView.setPivotY(0);

        mScreenshotAnimatedView.setImageBitmap(mScreenBitmap);
        mScreenshotPreview.setImageBitmap(mScreenBitmap);

        AnimatorSet dropInAnimation = new AnimatorSet();
        ValueAnimator flashInAnimator = ValueAnimator.ofFloat(0, 1);
        flashInAnimator.setDuration(SCREENSHOT_FLASH_IN_DURATION_MS);
@@ -761,15 +753,9 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
        flashOutAnimator.addUpdateListener(animation ->
                mScreenshotFlash.setAlpha((float) animation.getAnimatedValue()));

        // animate from the current location, to the static preview location
        final PointF startPos = new PointF(bounds.centerX(), bounds.centerY());
        float finalX;
        if (mDirectionLTR) {
            finalX = mScreenshotOffsetXPx + screenWidth * cornerScale / 2f;
        } else {
            finalX = screenWidth - mScreenshotOffsetXPx - screenWidth * cornerScale / 2f;
        }
        float finalY = screenHeight - mScreenshotOffsetYPx - screenHeight * cornerScale / 2f;
        final PointF finalPos = new PointF(finalX, finalY);
        final PointF finalPos = new PointF(previewBounds.centerX(), previewBounds.centerY());

        ValueAnimator toCorner = ValueAnimator.ofFloat(0, 1);
        toCorner.setDuration(SCREENSHOT_TO_CORNER_Y_DURATION_MS);
@@ -795,13 +781,13 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
            if (t < xPositionPct) {
                float xCenter = MathUtils.lerp(startPos.x, finalPos.x,
                        mFastOutSlowIn.getInterpolation(t / xPositionPct));
                mScreenshotAnimatedView.setX(xCenter - screenWidth * currentScaleX / 2f);
                mScreenshotAnimatedView.setX(xCenter - bitmapWidth * currentScaleX / 2f);
            } else {
                mScreenshotAnimatedView.setX(finalPos.x - screenWidth * currentScaleX / 2f);
                mScreenshotAnimatedView.setX(finalPos.x - bitmapWidth * currentScaleX / 2f);
            }
            float yCenter = MathUtils.lerp(startPos.y, finalPos.y,
                    mFastOutSlowIn.getInterpolation(t));
            mScreenshotAnimatedView.setY(yCenter - screenHeight * currentScaleY / 2f);
            float yCenter = MathUtils.lerp(
                    startPos.y, finalPos.y, mFastOutSlowIn.getInterpolation(t));
            mScreenshotAnimatedView.setY(yCenter - bitmapHeight * currentScaleY / 2f);
        });

        toCorner.addListener(new AnimatorListenerAdapter() {
@@ -824,10 +810,8 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
                super.onAnimationEnd(animation);
                mScreenshotAnimatedView.setScaleX(1);
                mScreenshotAnimatedView.setScaleY(1);
                mScreenshotAnimatedView.setX(finalPos.x - width * cornerScale / 2f);
                mScreenshotAnimatedView.setY(finalPos.y - height * cornerScale / 2f);
                Rect bounds = new Rect();
                mDismissButton.getBoundsOnScreen(bounds);
                mScreenshotAnimatedView.setX(finalPos.x - bounds.width() * cornerScale / 2f);
                mScreenshotAnimatedView.setY(finalPos.y - bounds.height() * cornerScale / 2f);
                mScreenshotAnimatedView.setVisibility(View.GONE);
                mScreenshotPreview.setVisibility(View.VISIBLE);
                mDismissButton.setVisibility(View.VISIBLE);