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

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

Update RTL/dark theme/orientation dynamically

Currently, we don't update the views once they are inflated, only
on initialization. In practice, this means that while RTL will
work correctly after rebooting the phone (or after the screenshot
process is killed and then recreated), it will not update between
taking two screenshots in quick succession.

This change adds a ConfigurationListener, which updates the dark
mode, RTL state, and phone orientation (portrait/landscape) on
a change in the configuration. Also makes the width of the preview
in landscape the correct size.

Bug: 149487205
Bug: 151351984
Fix: 149487205
Fix: 151351984
Test: manual -- tested changing RTL, orientation, and dark mode
while the screenshot process was running (including while the UI
was visible)

Change-Id: Icfe332e073546c47e92f1e7c0a4f8749bfaf833a
parent d8fb7833
Loading
Loading
Loading
Loading
+33 −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.
  -->
<ImageView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/global_screenshot_preview"
    android:layout_width="wrap_content"
    android:layout_height="@dimen/global_screenshot_x_scale"
    android:layout_gravity="center"
    android:layout_marginStart="@dimen/screenshot_offset_x"
    android:layout_marginBottom="@dimen/screenshot_offset_y"
    android:scaleType="fitStart"
    android:elevation="@dimen/screenshot_preview_elevation"
    android:visibility="gone"
    android:background="@drawable/screenshot_rounded_corners"
    android:adjustViewBounds="true"
    android:contentDescription="@string/screenshot_preview_description"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintStart_toStartOf="parent"/>
 No newline at end of file
+1 −15
Original line number Diff line number Diff line
@@ -71,21 +71,7 @@
        android:elevation="@dimen/screenshot_preview_elevation"
        android:background="@drawable/screenshot_rounded_corners"
        android:adjustViewBounds="true"/>
    <ImageView
        android:id="@+id/global_screenshot_preview"
        android:layout_width="@dimen/global_screenshot_x_scale"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginStart="@dimen/screenshot_offset_x"
        android:layout_marginBottom="@dimen/screenshot_offset_y"
        android:scaleType="fitEnd"
        android:elevation="@dimen/screenshot_preview_elevation"
        android:visibility="gone"
        android:background="@drawable/screenshot_rounded_corners"
        android:adjustViewBounds="true"
        android:contentDescription="@string/screenshot_preview_description"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"/>
    <include layout="@layout/global_screenshot_preview"/>
    <FrameLayout
        android:id="@+id/global_screenshot_dismiss_button"
        android:layout_width="@dimen/screenshot_dismiss_button_tappable_size"
+33 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright (C) 2011 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.
  -->
<ImageView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/global_screenshot_preview"
    android:layout_width="@dimen/global_screenshot_x_scale"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:layout_marginStart="@dimen/screenshot_offset_x"
    android:layout_marginBottom="@dimen/screenshot_offset_y"
    android:scaleType="fitEnd"
    android:elevation="@dimen/screenshot_preview_elevation"
    android:visibility="gone"
    android:background="@drawable/screenshot_rounded_corners"
    android:adjustViewBounds="true"
    android:contentDescription="@string/screenshot_preview_description"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintStart_toStartOf="parent"/>
 No newline at end of file
+1 −1
Original line number Diff line number Diff line
@@ -314,7 +314,7 @@
    <dimen name="screenshot_dismiss_button_margin">8dp</dimen>
    <dimen name="screenshot_action_container_offset_y">32dp</dimen>
    <dimen name="screenshot_action_container_corner_radius">10dp</dimen>
    <dimen name="screenshot_action_container_padding_vertical">10dp</dimen>
    <dimen name="screenshot_action_container_padding_vertical">16dp</dimen>
    <dimen name="screenshot_action_container_margin_horizontal">8dp</dimen>
    <dimen name="screenshot_action_container_padding_left">96dp</dimen>
    <dimen name="screenshot_action_container_padding_right">8dp</dimen>
+167 −110
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.systemui.screenshot;

import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;

@@ -61,6 +63,7 @@ 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;
@@ -71,6 +74,7 @@ import android.view.animation.AccelerateInterpolator;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
import android.widget.HorizontalScrollView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Toast;
@@ -183,24 +187,25 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
    private final Display mDisplay;
    private final DisplayMetrics mDisplayMetrics;

    private final View mScreenshotLayout;
    private final ScreenshotSelectorView mScreenshotSelectorView;
    private final ImageView mScreenshotAnimatedView;
    private final ImageView mScreenshotPreview;
    private final ImageView mScreenshotFlash;
    private final ImageView mActionsContainerBackground;
    private final FrameLayout mActionsContainer;
    private final LinearLayout mActionsView;
    private final ImageView mBackgroundProtection;
    private final FrameLayout mDismissButton;
    private final ImageView mDismissImage;
    private View mScreenshotLayout;
    private ScreenshotSelectorView mScreenshotSelectorView;
    private ImageView mScreenshotAnimatedView;
    private ImageView mScreenshotPreview;
    private ImageView mScreenshotFlash;
    private ImageView mActionsContainerBackground;
    private HorizontalScrollView mActionsContainer;
    private LinearLayout mActionsView;
    private ImageView mBackgroundProtection;
    private FrameLayout mDismissButton;

    private Bitmap mScreenBitmap;
    private SaveImageInBackgroundTask mSaveInBgTask;
    private Animator mScreenshotAnimation;
    private Runnable mOnCompleteRunnable;
    private boolean mInDarkMode = false;
    private Animator mDismissAnimation;
    private boolean mInDarkMode = false;
    private boolean mDirectionLTR = true;
    private boolean mOrientationPortrait = true;

    private float mScreenshotOffsetXPx;
    private float mScreenshotOffsetYPx;
@@ -232,57 +237,18 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
     */
    @Inject
    public GlobalScreenshot(
            Context context, @Main Resources resources, LayoutInflater layoutInflater,
            Context context, @Main Resources resources,
            ScreenshotNotificationsController screenshotNotificationsController,
            UiEventLogger uiEventLogger) {
        mContext = context;
        mNotificationsController = screenshotNotificationsController;
        mUiEventLogger = uiEventLogger;

        // Inflate the screenshot layout
        mScreenshotLayout = layoutInflater.inflate(R.layout.global_screenshot, null);
        mScreenshotAnimatedView =
                mScreenshotLayout.findViewById(R.id.global_screenshot_animated_view);
        mScreenshotAnimatedView.setClipToOutline(true);
        mScreenshotAnimatedView.setOutlineProvider(new ViewOutlineProvider() {
            @Override
            public void getOutline(View view, Outline outline) {
                outline.setRoundRect(new Rect(0, 0, view.getWidth(), view.getHeight()),
                        ROUNDED_CORNER_RADIUS * view.getWidth());
            }
        });
        mScreenshotPreview = mScreenshotLayout.findViewById(R.id.global_screenshot_preview);
        mScreenshotPreview.setClipToOutline(true);
        mScreenshotPreview.setOutlineProvider(new ViewOutlineProvider() {
            @Override
            public void getOutline(View view, Outline outline) {
                outline.setRoundRect(new Rect(0, 0, view.getWidth(), view.getHeight()),
                        ROUNDED_CORNER_RADIUS * view.getWidth());
            }
        });

        mActionsContainerBackground = mScreenshotLayout.findViewById(
                R.id.global_screenshot_actions_container_background);
        mActionsContainer = mScreenshotLayout.findViewById(
                R.id.global_screenshot_actions_container);
        mActionsView = mScreenshotLayout.findViewById(R.id.global_screenshot_actions);
        mBackgroundProtection = mScreenshotLayout.findViewById(
                R.id.global_screenshot_actions_background);
        mDismissButton = mScreenshotLayout.findViewById(R.id.global_screenshot_dismiss_button);
        mDismissButton.setOnClickListener(view -> {
            mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EXPLICIT_DISMISSAL);
            dismissScreenshot("dismiss_button", false);
            mOnCompleteRunnable.run();
        });
        mDismissImage = mDismissButton.findViewById(R.id.global_screenshot_dismiss_image);

        mScreenshotFlash = mScreenshotLayout.findViewById(R.id.global_screenshot_flash);
        mScreenshotSelectorView = mScreenshotLayout.findViewById(R.id.global_screenshot_selector);
        mScreenshotLayout.setFocusable(true);
        mScreenshotSelectorView.setFocusable(true);
        mScreenshotSelectorView.setFocusableInTouchMode(true);
        mScreenshotAnimatedView.setPivotX(0);
        mScreenshotAnimatedView.setPivotY(0);
        reloadAssets();
        Configuration config = mContext.getResources().getConfiguration();
        mInDarkMode = config.isNightModeActive();
        mDirectionLTR = config.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;
        mOrientationPortrait = config.orientation == ORIENTATION_PORTRAIT;

        // Setup the window that we are going to use
        mWindowLayoutParams = new WindowManager.LayoutParams(
@@ -333,6 +299,121 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
        inoutInfo.touchableRegion.set(touchRegion);
    }

    private void onConfigChanged(Configuration newConfig) {
        boolean needsUpdate = false;
        // dark mode
        if (newConfig.isNightModeActive()) {
            // Night mode is active, we're using dark theme
            if (!mInDarkMode) {
                mInDarkMode = true;
                needsUpdate = true;
            }
        } else {
            // Night mode is not active, we're using the light theme
            if (mInDarkMode) {
                mInDarkMode = false;
                needsUpdate = true;
            }
        }

        // RTL configuration
        switch (newConfig.getLayoutDirection()) {
            case View.LAYOUT_DIRECTION_LTR:
                if (!mDirectionLTR) {
                    mDirectionLTR = true;
                    needsUpdate = true;
                }
                break;
            case View.LAYOUT_DIRECTION_RTL:
                if (mDirectionLTR) {
                    mDirectionLTR = false;
                    needsUpdate = true;
                }
                break;
        }

        // portrait/landscape orientation
        switch (newConfig.orientation) {
            case ORIENTATION_PORTRAIT:
                if (!mOrientationPortrait) {
                    mOrientationPortrait = true;
                    needsUpdate = true;
                }
                break;
            case ORIENTATION_LANDSCAPE:
                if (mOrientationPortrait) {
                    mOrientationPortrait = false;
                    needsUpdate = true;
                }
                break;
        }

        if (needsUpdate) {
            reloadAssets();
        }
    }

    /**
     * Update assets (called when the dark theme status changes). We only need to update the dismiss
     * button and the actions container background, since the buttons are re-inflated on demand.
     */
    private void reloadAssets() {
        boolean wasAttached = mScreenshotLayout != null && mScreenshotLayout.isAttachedToWindow();
        if (wasAttached) {
            mWindowManager.removeView(mScreenshotLayout);
        }

        // Inflate the screenshot layout
        mScreenshotLayout = LayoutInflater.from(mContext).inflate(R.layout.global_screenshot, null);
        mScreenshotAnimatedView =
                mScreenshotLayout.findViewById(R.id.global_screenshot_animated_view);
        mScreenshotAnimatedView.setClipToOutline(true);
        mScreenshotAnimatedView.setOutlineProvider(new ViewOutlineProvider() {
            @Override
            public void getOutline(View view, Outline outline) {
                outline.setRoundRect(new Rect(0, 0, view.getWidth(), view.getHeight()),
                        ROUNDED_CORNER_RADIUS * view.getWidth());
            }
        });
        mScreenshotPreview = mScreenshotLayout.findViewById(R.id.global_screenshot_preview);
        mScreenshotPreview.setClipToOutline(true);
        mScreenshotPreview.setOutlineProvider(new ViewOutlineProvider() {
            @Override
            public void getOutline(View view, Outline outline) {
                outline.setRoundRect(new Rect(0, 0, view.getWidth(), view.getHeight()),
                        ROUNDED_CORNER_RADIUS * view.getWidth());
            }
        });

        mActionsContainerBackground = mScreenshotLayout.findViewById(
                R.id.global_screenshot_actions_container_background);
        mActionsContainer = mScreenshotLayout.findViewById(
                R.id.global_screenshot_actions_container);
        mActionsView = mScreenshotLayout.findViewById(R.id.global_screenshot_actions);
        mBackgroundProtection = mScreenshotLayout.findViewById(
                R.id.global_screenshot_actions_background);
        mDismissButton = mScreenshotLayout.findViewById(R.id.global_screenshot_dismiss_button);
        mDismissButton.setOnClickListener(view -> {
            mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EXPLICIT_DISMISSAL);
            dismissScreenshot("dismiss_button", false);
            mOnCompleteRunnable.run();
        });

        mScreenshotFlash = mScreenshotLayout.findViewById(R.id.global_screenshot_flash);
        mScreenshotSelectorView = mScreenshotLayout.findViewById(R.id.global_screenshot_selector);
        mScreenshotLayout.setFocusable(true);
        mScreenshotSelectorView.setFocusable(true);
        mScreenshotSelectorView.setFocusableInTouchMode(true);
        mScreenshotAnimatedView.setPivotX(0);
        mScreenshotAnimatedView.setPivotY(0);
        mActionsContainer.setScrollX(0);

        if (wasAttached) {
            mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
        }
    }


    /**
     * Creates a new worker thread and saves the screenshot to the media store.
     */
@@ -382,10 +463,8 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
        mScreenBitmap.setHasAlpha(false);
        mScreenBitmap.prepareToDraw();

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

        mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
        mScreenshotLayout.getViewTreeObserver().addOnComputeInternalInsetsListener(this);

        if (mDismissAnimation != null && mDismissAnimation.isRunning()) {
            mDismissAnimation.cancel();
@@ -395,7 +474,6 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
    }

    void takeScreenshot(Consumer<Uri> finisher, Runnable onComplete) {
        dismissScreenshot("new screenshot requested", true);
        mOnCompleteRunnable = onComplete;

        mDisplay.getRealMetrics(mDisplayMetrics);
@@ -407,7 +485,6 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
    void handleImageAsScreenshot(Bitmap screenshot, Rect screenshotScreenBounds,
            Insets visibleInsets, int taskId, Consumer<Uri> finisher, Runnable onComplete) {
        // TODO use taskId and visibleInsets
        dismissScreenshot("new screenshot requested", true);
        mOnCompleteRunnable = onComplete;
        takeScreenshot(screenshot, finisher, screenshotScreenBounds);
    }
@@ -512,41 +589,6 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
        mScreenshotPreview.setTranslationY(0);
    }

    /**
     * Update assets (called when the dark theme status changes). We only need to update the
     * dismiss
     * button and the actions container background, since the buttons are re-inflated on demand.
     */
    private void reloadAssets() {
        mDismissImage.setImageDrawable(mContext.getDrawable(R.drawable.screenshot_cancel));
        mActionsContainerBackground.setBackground(
                mContext.getDrawable(R.drawable.action_chip_container_background));
    }

    /**
     * Checks the current dark theme status and updates if it has changed.
     */
    private void updateDarkTheme() {
        int currentNightMode = mContext.getResources().getConfiguration().uiMode
                & Configuration.UI_MODE_NIGHT_MASK;
        switch (currentNightMode) {
            case Configuration.UI_MODE_NIGHT_NO:
                // Night mode is not active, we're using the light theme
                if (mInDarkMode) {
                    mInDarkMode = false;
                    reloadAssets();
                }
                break;
            case Configuration.UI_MODE_NIGHT_YES:
                // Night mode is active, we're using dark theme
                if (!mInDarkMode) {
                    mInDarkMode = true;
                    reloadAssets();
                }
                break;
        }
    }

    /**
     * Starts the animation after taking the screenshot
     */
@@ -597,6 +639,13 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
            }
        });
        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);

@@ -604,10 +653,19 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
                mScreenshotPreview.buildLayer();
                mScreenshotAnimation.start();
            });

        });

    }

    private AnimatorSet createScreenshotDropInAnimation(int width, int height, Rect bounds) {
        float cornerScale = mCornerSizeX / (float) width;
        int rotation = mContext.getDisplay().getRotation();
        float cornerScale;
        if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) {
            cornerScale = (mCornerSizeX / (float) height);
        } else {
            cornerScale = (mCornerSizeX / (float) width);
        }

        mScreenshotAnimatedView.setScaleX(1);
        mScreenshotAnimatedView.setScaleY(1);
@@ -632,8 +690,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset

        final PointF startPos = new PointF(bounds.centerX(), bounds.centerY());
        float finalX;
        if (mContext.getResources().getConfiguration().getLayoutDirection()
                == View.LAYOUT_DIRECTION_LTR) {
        if (mDirectionLTR) {
            finalX = mScreenshotOffsetXPx + width * cornerScale / 2f;
        } else {
            finalX = width - mScreenshotOffsetXPx - width * cornerScale / 2f;
@@ -713,7 +770,6 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
    private ValueAnimator createScreenshotActionsShadeAnimation(SavedImageData imageData) {
        LayoutInflater inflater = LayoutInflater.from(mContext);
        mActionsView.removeAllViews();
        mActionsContainer.setScrollX(0);
        mScreenshotLayout.invalidate();
        mScreenshotLayout.requestLayout();
        mScreenshotLayout.getViewTreeObserver().dispatchOnGlobalLayout();
@@ -803,14 +859,11 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
        animator.setDuration(SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS);
        float alphaFraction = (float) SCREENSHOT_ACTIONS_ALPHA_DURATION_MS
                / SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS;
        mActionsContainer.setVisibility(View.VISIBLE);
        mActionsContainer.setAlpha(0f);
        mActionsContainerBackground.setAlpha(0f);
        mActionsContainer.setVisibility(View.VISIBLE);
        mActionsContainerBackground.setVisibility(View.VISIBLE);

        mActionsContainer.setPivotX(0);
        mActionsContainerBackground.setPivotX(0);

        animator.addUpdateListener(animation -> {
            float t = animation.getAnimatedFraction();
            mBackgroundProtection.setAlpha(t);
@@ -825,6 +878,10 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
                chip.setAlpha(t);
                chip.setScaleX(1 / containerScale); // invert to keep size of children constant
            }
            mActionsContainer.setScrollX(mDirectionLTR ? 0 : mActionsContainer.getWidth());
            mActionsContainer.setPivotX(mDirectionLTR ? 0 : mActionsContainer.getWidth());
            mActionsContainerBackground.setPivotX(
                    mDirectionLTR ? 0 : mActionsContainerBackground.getWidth());
        });
        return animator;
    }