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
This commit is contained in:
Miranda Kephart
2020-04-29 19:57:12 -04:00
parent d8fb783389
commit 2766208311
5 changed files with 235 additions and 126 deletions

View File

@@ -0,0 +1,33 @@
<?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"/>

View File

@@ -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"

View File

@@ -0,0 +1,33 @@
<?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"/>

View File

@@ -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>

View File

@@ -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,17 +639,33 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
}
});
mScreenshotHandler.post(() -> {
// Play the shutter sound to notify that we've taken a screenshot
mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
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);
mScreenshotPreview.setLayerType(View.LAYER_TYPE_HARDWARE, null);
mScreenshotPreview.buildLayer();
mScreenshotAnimation.start();
});
mScreenshotPreview.setLayerType(View.LAYER_TYPE_HARDWARE, null);
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;
}