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

Commit 196adefb authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Simplify UI and animation choreography a bit"

parents 07dc5b6b b5624dc6
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -25,7 +25,7 @@
    android:layout_marginBottom="@dimen/screenshot_offset_y"
    android:scaleType="fitEnd"
    android:elevation="@dimen/screenshot_preview_elevation"
    android:visibility="gone"
    android:visibility="invisible"
    android:background="@drawable/screenshot_rounded_corners"
    android:adjustViewBounds="true"
    android:contentDescription="@string/screenshot_edit_label"
+177 −132
Original line number Diff line number Diff line
@@ -66,6 +66,7 @@ import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.Window;
import android.view.WindowInsets;
import android.view.WindowManager;
@@ -79,13 +80,15 @@ import com.android.internal.logging.UiEventLogger;
import com.android.internal.policy.PhoneWindow;
import com.android.settingslib.applications.InterestingConfigChanges;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition;
import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback;
import com.android.systemui.util.DeviceConfigProxy;

import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
import java.util.function.Supplier;

@@ -172,7 +175,7 @@ public class ScreenshotController {
    private final UiEventLogger mUiEventLogger;
    private final ImageExporter mImageExporter;
    private final Executor mMainExecutor;
    private final Executor mBgExecutor;
    private final ExecutorService mBgExecutor;

    private final WindowManager mWindowManager;
    private final WindowManager.LayoutParams mWindowLayoutParams;
@@ -181,7 +184,6 @@ public class ScreenshotController {
    private final ScrollCaptureClient mScrollCaptureClient;
    private final DeviceConfigProxy mConfigProxy;
    private final PhoneWindow mWindow;
    private final View mDecorView;
    private final DisplayManager mDisplayManager;

    private ScreenshotView mScreenshotView;
@@ -189,8 +191,7 @@ public class ScreenshotController {
    private SaveImageInBackgroundTask mSaveInBgTask;

    private Animator mScreenshotAnimation;

    private Runnable mOnCompleteRunnable;
    private RequestCallback mCurrentRequestCallback;

    private final Handler mScreenshotHandler = new Handler(Looper.getMainLooper()) {
        @Override
@@ -227,15 +228,14 @@ public class ScreenshotController {
            UiEventLogger uiEventLogger,
            DeviceConfigProxy configProxy,
            ImageExporter imageExporter,
            @Main Executor mainExecutor,
            @Background Executor bgExecutor) {
            @Main Executor mainExecutor) {
        mScreenshotSmartActions = screenshotSmartActions;
        mNotificationsController = screenshotNotificationsController;
        mScrollCaptureClient = scrollCaptureClient;
        mUiEventLogger = uiEventLogger;
        mImageExporter = imageExporter;
        mMainExecutor = mainExecutor;
        mBgExecutor = bgExecutor;
        mBgExecutor = Executors.newSingleThreadExecutor();

        mDisplayManager = requireNonNull(context.getSystemService(DisplayManager.class));
        final Context displayContext = context.createDisplayContext(getDefaultDisplay());
@@ -266,8 +266,8 @@ public class ScreenshotController {
        mWindow.requestFeature(Window.FEATURE_NO_TITLE);
        mWindow.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS);
        mWindow.setBackgroundDrawableResource(android.R.color.transparent);
        mDecorView = mWindow.getDecorView();

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

        // Setup the Camera shutter sound
@@ -275,9 +275,8 @@ public class ScreenshotController {
        mCameraSound.load(MediaActionSound.SHUTTER_CLICK);
    }

    void takeScreenshotFullscreen(Consumer<Uri> finisher, Runnable onComplete) {
        mOnCompleteRunnable = onComplete;

    void takeScreenshotFullscreen(Consumer<Uri> finisher, RequestCallback requestCallback) {
        mCurrentRequestCallback = requestCallback;
        DisplayMetrics displayMetrics = new DisplayMetrics();
        getDefaultDisplay().getRealMetrics(displayMetrics);
        takeScreenshotInternal(
@@ -287,16 +286,14 @@ public class ScreenshotController {

    void handleImageAsScreenshot(Bitmap screenshot, Rect screenshotScreenBounds,
            Insets visibleInsets, int taskId, int userId, ComponentName topComponent,
            Consumer<Uri> finisher, Runnable onComplete) {
            Consumer<Uri> finisher, RequestCallback requestCallback) {
        // TODO: use task Id, userId, topComponent for smart handler
        mOnCompleteRunnable = onComplete;

        if (screenshot == null) {
            Log.e(TAG, "Got null bitmap from screenshot message");
            mNotificationsController.notifyScreenshotError(
                    R.string.screenshot_failed_to_capture_text);
            finisher.accept(null);
            mOnCompleteRunnable.run();
            requestCallback.reportError();
            return;
        }

@@ -306,17 +303,19 @@ public class ScreenshotController {
            visibleInsets = Insets.NONE;
            screenshotScreenBounds.set(0, 0, screenshot.getWidth(), screenshot.getHeight());
        }
        mCurrentRequestCallback = requestCallback;
        saveScreenshot(screenshot, finisher, screenshotScreenBounds, visibleInsets, showFlash);
    }

    /**
     * Displays a screenshot selector
     */
    void takeScreenshotPartial(final Consumer<Uri> finisher, Runnable onComplete) {
        dismissScreenshot(true);
        mOnCompleteRunnable = onComplete;
    void takeScreenshotPartial(final Consumer<Uri> finisher, RequestCallback requestCallback) {
        mScreenshotView.reset();
        mCurrentRequestCallback = requestCallback;

        mWindowManager.addView(mScreenshotView, mWindowLayoutParams);
        attachWindow();
        mWindow.setContentView(mScreenshotView);

        mScreenshotView.takePartialScreenshot(
                rect -> takeScreenshotInternal(finisher, rect));
@@ -337,9 +336,9 @@ public class ScreenshotController {
            }
            return;
        }
        mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT);
        cancelTimeout();
        if (immediate) {
            resetScreenshotView();
            finishDismiss();
        } else {
            mScreenshotView.animateDismissal();
        }
@@ -354,6 +353,8 @@ public class ScreenshotController {
     */
    void releaseContext() {
        mContext.release();
        mCameraSound.release();
        mBgExecutor.shutdownNow();
    }

    /**
@@ -363,12 +364,8 @@ public class ScreenshotController {
        if (DEBUG_UI) {
            Log.d(TAG, "reloadAssets()");
        }
        boolean wasAttached = mDecorView.isAttachedToWindow();
        if (wasAttached) {
            if (DEBUG_WINDOW) {
                Log.d(TAG, "Removing screenshot window");
            }
            mWindowManager.removeView(mDecorView);
        if (mScreenshotView != null && mScreenshotView.isAttachedToWindow()) {
            mWindow.clearContentView(); // Is there a simpler way to say "remove screenshotView?"
        }

        // respect the display cutout in landscape (since we'd otherwise overlap) but not portrait
@@ -376,12 +373,6 @@ public class ScreenshotController {
        mWindowLayoutParams.setFitInsetsTypes(
                orientation == ORIENTATION_PORTRAIT ? 0 : WindowInsets.Type.displayCutout());

        // ignore system bar insets for the purpose of window layout
        mDecorView.setOnApplyWindowInsetsListener((v, insets) -> v.onApplyWindowInsets(
                new WindowInsets.Builder(insets)
                        .setInsets(WindowInsets.Type.all(), Insets.NONE)
                        .build()));

        // Inflate the screenshot layout
        mScreenshotView = (ScreenshotView)
                LayoutInflater.from(mContext).inflate(R.layout.global_screenshot, null);
@@ -393,12 +384,18 @@ public class ScreenshotController {

            @Override
            public void onDismiss() {
                resetScreenshotView();
                finishDismiss();
            }
        });

        // ignore system bar insets for the purpose of window layout
        mScreenshotView.setOnApplyWindowInsetsListener((v, insets) -> v.onApplyWindowInsets(
                new WindowInsets.Builder(insets)
                        .setInsets(WindowInsets.Type.all(), Insets.NONE)
                        .build()));

        // TODO(159460485): Remove this when focus is handled properly in the system
        mDecorView.setOnTouchListener((v, event) -> {
        mScreenshotView.setOnTouchListener((v, event) -> {
            if (event.getActionMasked() == MotionEvent.ACTION_OUTSIDE) {
                if (DEBUG_INPUT) {
                    Log.d(TAG, "onTouch: ACTION_OUTSIDE");
@@ -420,8 +417,10 @@ public class ScreenshotController {
            return false;
        });

        // view is added to window manager in startAnimation
        mWindow.setContentView(mScreenshotView, mWindowLayoutParams);
        if (DEBUG_WINDOW) {
            Log.d(TAG, "adding OnComputeInternalInsetsListener");
        }
        mScreenshotView.getViewTreeObserver().addOnComputeInternalInsetsListener(mScreenshotView);
    }

    /**
@@ -458,14 +457,9 @@ public class ScreenshotController {
            Log.e(TAG, "takeScreenshotInternal: Screenshot bitmap was null");
            mNotificationsController.notifyScreenshotError(
                    R.string.screenshot_failed_to_capture_text);
            if (DEBUG_CALLBACK) {
                Log.d(TAG, "Supplying null to Consumer<Uri>");
            }
            finisher.accept(null);
            if (DEBUG_CALLBACK) {
                Log.d(TAG, "Calling mOnCompleteRunnable.run()");
            if (mCurrentRequestCallback != null) {
                mCurrentRequestCallback.reportError();
            }
            mOnCompleteRunnable.run();
            return;
        }

@@ -482,6 +476,13 @@ public class ScreenshotController {
            mAccessibilityManager.sendAccessibilityEvent(event);
        }

        if (mConfigChanges.applyNewConfig(mContext.getResources())) {
            if (DEBUG_UI) {
                Log.d(TAG, "saveScreenshot: reloading assets");
            }
            reloadAssets();
        }

        if (mScreenshotView.isAttachedToWindow()) {
            // if we didn't already dismiss for another reason
            if (!mScreenshotView.isDismissing()) {
@@ -508,18 +509,101 @@ public class ScreenshotController {
        mScreenBitmap.setHasAlpha(false);
        mScreenBitmap.prepareToDraw();

        if (mConfigChanges.applyNewConfig(mContext.getResources())) {
            if (DEBUG_UI) {
                Log.d(TAG, "saveScreenshot: reloading assets");
            }
            reloadAssets();
        }
        saveScreenshotInWorkerThread(finisher, this::showUiOnActionsReady);

        // The window is focusable by default
        setWindowFocusable(true);

        // Start the post-screenshot animation
        startAnimation(finisher, screenRect, screenInsets, showFlash);
        if (mConfigProxy.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
                SystemUiDeviceConfigFlags.SCREENSHOT_SCROLLING_ENABLED, false)) {
            View decorView = mWindow.getDecorView();

            // Wait until this window is attached to request because it is
            // the reference used to locate the target window (below).
            withWindowAttached(() -> {
                mScrollCaptureClient.setHostWindowToken(decorView.getWindowToken());
                mScrollCaptureClient.request(DEFAULT_DISPLAY,
                        /* onConnection */
                        (connection) -> mScreenshotHandler.post(() ->
                                mScreenshotView.showScrollChip(() ->
                                        /* onClick */
                                        runScrollCapture(connection))));
            });
        }


        attachWindow();
        if (DEBUG_WINDOW) {
            Log.d(TAG, "setContentView: " + mScreenshotView);
        }
        mScreenshotView.getViewTreeObserver().addOnPreDrawListener(
                new ViewTreeObserver.OnPreDrawListener() {
                    @Override
                    public boolean onPreDraw() {
                        if (DEBUG_WINDOW) {
                            Log.d(TAG, "onPreDraw: startAnimation");
                        }
                        mScreenshotView.getViewTreeObserver().removeOnPreDrawListener(this);
                        startAnimation(screenRect, showFlash);
                        return true;
                    }
                });
        mScreenshotView.setScreenshot(mScreenBitmap, screenInsets);
        setContentView(mScreenshotView);
        cancelTimeout(); // restarted after animation
    }

    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() {
                            decorView.getViewTreeObserver().removeOnWindowAttachListener(this);
                            action.run();
                        }

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

        }
    }

    private void setContentView(View contentView) {
        mWindow.setContentView(contentView);
    }

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

    void removeWindow() {
        final View decorView = mWindow.peekDecorView();
        if (decorView != null && decorView.isAttachedToWindow()) {
            if (DEBUG_WINDOW) {
                Log.d(TAG, "Removing screenshot window");
            }
            mWindowManager.removeViewImmediate(decorView);
        }
    }

    private void runScrollCapture(ScrollCaptureClient.Connection connection) {
        cancelTimeout();
        ScrollCaptureController controller = new ScrollCaptureController(mContext, connection,
                mMainExecutor, mBgExecutor, mImageExporter);
        controller.start(/* onDismiss */ () -> dismissScreenshot(false));
    }

    /**
@@ -528,11 +612,11 @@ public class ScreenshotController {
     */
    private void saveScreenshotAndToast(Consumer<Uri> finisher) {
        // Play the shutter sound to notify that we've taken a screenshot
        mScreenshotHandler.post(() -> {
        mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
        });

        saveScreenshotInWorkerThread(finisher, imageData -> {
        saveScreenshotInWorkerThread(
                /* onComplete */ finisher,
                /* actionsReadyListener */ imageData -> {
            if (DEBUG_CALLBACK) {
                Log.d(TAG, "returning URI to finisher (Consumer<URI>): " + imageData.uri);
            }
@@ -549,65 +633,17 @@ public class ScreenshotController {
        });
    }

    /**
     * If scrolling is enabled, check whether the current view is scrollable and if so, show the
     * scroll chip.
     */
    private void maybeRequestScrollCapture() {
        if (mConfigProxy.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
                SystemUiDeviceConfigFlags.SCREENSHOT_SCROLLING_ENABLED, false)) {
            mScrollCaptureClient.setHostWindowToken(mDecorView.getWindowToken());
            mScrollCaptureClient.request(DEFAULT_DISPLAY, (connection) ->
                    mScreenshotView.showScrollChip(() -> {
                        ScrollCaptureController controller = new ScrollCaptureController(
                                mContext, connection, mMainExecutor, mBgExecutor, mImageExporter);
                        controller.run(() -> mScreenshotHandler.post(
                                () -> dismissScreenshot(false)));
                    }));
        }
    }

    /**
     * Starts the animation after taking the screenshot
     */
    private void startAnimation(final Consumer<Uri> finisher, Rect screenRect, Insets screenInsets,
            boolean showFlash) {
        mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT);
        mScreenshotHandler.post(() -> {
            if (!mDecorView.isAttachedToWindow()) {
                if (DEBUG_WINDOW) {
                    Log.d(TAG, "Adding screenshot window");
                }
                mDecorView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
                    @Override
                    public void onViewAttachedToWindow(View v) {
                        mDecorView.removeOnAttachStateChangeListener(this);
                        maybeRequestScrollCapture();
                    }

                    @Override
                    public void onViewDetachedFromWindow(View v) {
                    }
                });
                mWindowManager.addView(mWindow.getDecorView(), mWindowLayoutParams);
            } else {
                maybeRequestScrollCapture();
            }

            mScreenshotView.prepareForAnimation(mScreenBitmap, screenInsets);

            mScreenshotHandler.post(() -> {
                if (DEBUG_WINDOW) {
                    Log.d(TAG, "adding OnComputeInternalInsetsListener");
    private void startAnimation(Rect screenRect, boolean showFlash) {
        if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) {
            mScreenshotAnimation.cancel();
        }
                mScreenshotView.getViewTreeObserver().addOnComputeInternalInsetsListener(
                        mScreenshotView);

        mScreenshotAnimation =
                mScreenshotView.createScreenshotDropInAnimation(screenRect, showFlash);

                saveScreenshotInWorkerThread(finisher, this::showUiOnActionsReady);

        // Play the shutter sound to notify that we've taken a screenshot
        mCameraSound.play(MediaActionSound.SHUTTER_CLICK);

@@ -615,23 +651,20 @@ public class ScreenshotController {
            Log.d(TAG, "starting post-screenshot animation");
        }
        mScreenshotAnimation.start();
            });
        });
    }

    /** Reset screenshot view and then call onCompleteRunnable */
    private void resetScreenshotView() {
    private void finishDismiss() {
        if (DEBUG_UI) {
            Log.d(TAG, "resetScreenshotView");
        }
        if (mScreenshotView.isAttachedToWindow()) {
            if (DEBUG_WINDOW) {
                Log.d(TAG, "Removing screenshot window");
            }
            mWindowManager.removeViewImmediate(mDecorView);
            Log.d(TAG, "finishDismiss");
        }
        mOnCompleteRunnable.run();
        cancelTimeout();
        removeWindow();
        mScreenshotView.reset();
        if (mCurrentRequestCallback != null) {
            mCurrentRequestCallback.onFinish();
            mCurrentRequestCallback = null;
        }
    }

    /**
@@ -655,8 +688,12 @@ public class ScreenshotController {
        mSaveInBgTask.execute();
    }

    private void resetTimeout() {
    private void cancelTimeout() {
        mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT);
    }

    private void resetTimeout() {
        cancelTimeout();

        AccessibilityManager accessibilityManager = (AccessibilityManager)
                mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
@@ -715,7 +752,7 @@ public class ScreenshotController {

                @Override
                public void hideSharedElements() {
                    resetScreenshotView();
                    finishDismiss();
                }

                @Override
@@ -763,13 +800,21 @@ public class ScreenshotController {
        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 (mDecorView.isAttachedToWindow()) {
            mWindowManager.updateViewLayout(mDecorView, mWindowLayoutParams);
        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);
        }
    }

+16 −12
Original line number Diff line number Diff line
@@ -316,21 +316,21 @@ public class ScreenshotView extends FrameLayout implements
        mScreenshotSelectorView.requestFocus();
    }

    void prepareForAnimation(Bitmap bitmap, Insets screenInsets) {
    void setScreenshot(Bitmap bitmap, Insets screenInsets) {
        mScreenshotPreview.setImageDrawable(createScreenDrawable(mResources, bitmap, screenInsets));
        // make static preview invisible (from gone) so we can query its location on screen
        mScreenshotPreview.setVisibility(View.INVISIBLE);
    }

    AnimatorSet createScreenshotDropInAnimation(Rect bounds, boolean showFlash) {
        mScreenshotPreview.setLayerType(View.LAYER_TYPE_HARDWARE, null);
        mScreenshotPreview.buildLayer();
        if (DEBUG_ANIM) {
            Log.d(TAG, "createAnim: bounds=" + bounds + " showFlash=" + showFlash);
        }

        Rect previewBounds = new Rect();
        mScreenshotPreview.getBoundsOnScreen(previewBounds);
        int[] previewLocation = new int[2];
        mScreenshotPreview.getLocationInWindow(previewLocation);
        Rect targetPosition = new Rect();
        mScreenshotPreview.getHitRect(targetPosition);

        // ratio of preview width, end vs. start size
        float cornerScale =
                mCornerSizeX / (mOrientationPortrait ? bounds.width() : bounds.height());
        final float currentScale = 1 / cornerScale;
@@ -358,8 +358,13 @@ public class ScreenshotView extends FrameLayout implements

        // animate from the current location, to the static preview location
        final PointF startPos = new PointF(bounds.centerX(), bounds.centerY());
        final PointF finalPos = new PointF(previewLocation[0] + previewBounds.width() / 2f,
                previewLocation[1] + previewBounds.height() / 2f);
        final PointF finalPos = new PointF(targetPosition.exactCenterX(),
                targetPosition.exactCenterY());

        if (DEBUG_ANIM) {
            Log.d(TAG, "toCorner: startPos=" + startPos);
            Log.d(TAG, "toCorner: finalPos=" + finalPos);
        }

        ValueAnimator toCorner = ValueAnimator.ofFloat(0, 1);
        toCorner.setDuration(SCREENSHOT_TO_CORNER_Y_DURATION_MS);
@@ -427,7 +432,7 @@ public class ScreenshotView extends FrameLayout implements
            @Override
            public void onAnimationEnd(Animator animation) {
                if (DEBUG_ANIM) {
                    Log.d(TAG, "drop-in animation completed");
                    Log.d(TAG, "drop-in animation ended");
                }
                mDismissButton.setOnClickListener(view -> {
                    if (DEBUG_INPUT) {
@@ -653,13 +658,12 @@ public class ScreenshotView extends FrameLayout implements
        getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
        // Clear any references to the bitmap
        mScreenshotPreview.setImageDrawable(null);
        mScreenshotPreview.setVisibility(View.INVISIBLE);
        mPendingSharedTransition = false;
        mActionsContainerBackground.setVisibility(View.GONE);
        mActionsContainer.setVisibility(View.GONE);
        mBackgroundProtection.setAlpha(0f);
        mDismissButton.setVisibility(View.GONE);
        mScreenshotPreview.setVisibility(View.GONE);
        mScreenshotPreview.setLayerType(View.LAYER_TYPE_NONE, null);
        mScreenshotStatic.setTranslationX(0);
        mScreenshotPreview.setTranslationY(0);
        mScreenshotPreview.setContentDescription(
+1 −1
Original line number Diff line number Diff line
@@ -72,7 +72,7 @@ public class ScrollCaptureController {
     *
     * @param after action to take after the flow is complete
     */
    public void run(final Runnable after) {
    public void start(final Runnable after) {
        mCaptureTime = ZonedDateTime.now();
        mRequestId = UUID.randomUUID();
        mConnection.start((session) -> startCapture(session, after));