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

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

Merge "Add shared transitions from clipboard UI" into udc-qpr-dev

parents 671bd95e e98c450c
Loading
Loading
Loading
Loading
+161 −16
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.systemui.clipboardoverlay;

import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS;


import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_SHOW_ACTIONS;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_SHOWN;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_TAPPED;
@@ -33,6 +34,7 @@ import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBO
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TAP_OUTSIDE;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TIMED_OUT;
import static com.android.systemui.flags.Flags.CLIPBOARD_IMAGE_TIMEOUT;
import static com.android.systemui.flags.Flags.CLIPBOARD_SHARED_TRANSITIONS;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -47,6 +49,7 @@ import android.hardware.input.InputManager;
import android.net.Uri;
import android.os.Looper;
import android.provider.DeviceConfig;
import android.util.Log;
import android.view.InputEvent;
import android.view.InputEventReceiver;
import android.view.InputMonitor;
@@ -54,6 +57,7 @@ import android.view.MotionEvent;
import android.view.WindowInsets;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.UiEventLogger;
@@ -73,7 +77,8 @@ import javax.inject.Inject;
/**
 * Controls state and UI for the overlay that appears when something is added to the clipboard
 */
public class ClipboardOverlayController implements ClipboardListener.ClipboardOverlay {
public class ClipboardOverlayController implements ClipboardListener.ClipboardOverlay,
        ClipboardOverlayView.ClipboardOverlayCallbacks {
    private static final String TAG = "ClipboardOverlayCtrlr";

    /** Constants for screenshot/copy deconflicting */
@@ -92,6 +97,7 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
    private final FeatureFlags mFeatureFlags;
    private final Executor mBgExecutor;
    private final ClipboardImageLoader mClipboardImageLoader;
    private final ClipboardTransitionExecutor mTransitionExecutor;

    private final ClipboardOverlayView mView;

@@ -179,10 +185,12 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
            ClipboardOverlayUtils clipboardUtils,
            @Background Executor bgExecutor,
            ClipboardImageLoader clipboardImageLoader,
            ClipboardTransitionExecutor transitionExecutor,
            UiEventLogger uiEventLogger) {
        mContext = context;
        mBroadcastDispatcher = broadcastDispatcher;
        mClipboardImageLoader = clipboardImageLoader;
        mTransitionExecutor = transitionExecutor;

        mClipboardLogger = new ClipboardLogger(uiEventLogger);

@@ -200,7 +208,11 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
        mClipboardUtils = clipboardUtils;
        mBgExecutor = bgExecutor;

        if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
            mView.setCallbacks(this);
        } else {
            mView.setCallbacks(mClipboardCallbacks);
        }

        mWindow.withWindowAttached(() -> {
            mWindow.setContentView(mView);
@@ -209,18 +221,26 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
        });

        mTimeoutHandler.setOnTimeoutRunnable(() -> {
            if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
                finish(CLIPBOARD_OVERLAY_TIMED_OUT);
            } else {
                mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_TIMED_OUT);
                animateOut();
            }
        });

        mCloseDialogsReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                if (ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
                    if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
                        finish(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
                    } else {
                        mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
                        animateOut();
                    }
                }
            }
        };

        mBroadcastDispatcher.registerReceiver(mCloseDialogsReceiver,
@@ -229,10 +249,14 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
            @Override
            public void onReceive(Context context, Intent intent) {
                if (SCREENSHOT_ACTION.equals(intent.getAction())) {
                    if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
                        finish(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
                    } else {
                        mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
                        animateOut();
                    }
                }
            }
        };

        mBroadcastDispatcher.registerReceiver(mScreenshotReceiver,
@@ -457,8 +481,12 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
                remoteAction.ifPresent(action -> {
                    mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_ACTION_SHOWN);
                    mView.post(() -> mView.setActionChip(action, () -> {
                        if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
                            finish(CLIPBOARD_OVERLAY_ACTION_TAPPED);
                        } else {
                            mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_ACTION_TAPPED);
                            animateOut();
                        }
                    }));
                });
            }
@@ -500,11 +528,15 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
                    if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
                        if (!mView.isInTouchRegion(
                                (int) motionEvent.getRawX(), (int) motionEvent.getRawY())) {
                            if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
                                finish(CLIPBOARD_OVERLAY_TAP_OUTSIDE);
                            } else {
                                mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_TAP_OUTSIDE);
                                animateOut();
                            }
                        }
                    }
                }
                finishInputEvent(event, true /* handled */);
            }
        };
@@ -551,12 +583,41 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
        mEnterAnimator.start();
    }

    private void finish(ClipboardOverlayEvent event) {
        finish(event, null);
    }

    private void animateOut() {
        if (mExitAnimator != null && mExitAnimator.isRunning()) {
            return;
        }
        Animator anim = mView.getExitAnimation();
        anim.addListener(new AnimatorListenerAdapter() {
        mExitAnimator = mView.getExitAnimation();
        mExitAnimator.addListener(new AnimatorListenerAdapter() {
            private boolean mCancelled;

            @Override
            public void onAnimationCancel(Animator animation) {
                super.onAnimationCancel(animation);
                mCancelled = true;
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                if (!mCancelled) {
                    hideImmediate();
                }
            }
        });
        mExitAnimator.start();
    }

    private void finish(ClipboardOverlayEvent event, @Nullable Intent intent) {
        if (mExitAnimator != null && mExitAnimator.isRunning()) {
            return;
        }
        mExitAnimator = mView.getExitAnimation();
        mExitAnimator.addListener(new AnimatorListenerAdapter() {
            private boolean mCancelled;

            @Override
@@ -569,12 +630,26 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                if (!mCancelled) {
                    mClipboardLogger.logSessionComplete(event);
                    if (intent != null) {
                        mContext.startActivity(intent);
                    }
                    hideImmediate();
                }
            }
        });
        mExitAnimator = anim;
        anim.start();
        mExitAnimator.start();
    }

    private void finishWithSharedTransition(ClipboardOverlayEvent event, Intent intent) {
        if (mExitAnimator != null && mExitAnimator.isRunning()) {
            return;
        }
        mClipboardLogger.logSessionComplete(event);
        mExitAnimator = mView.getFadeOutAnimation();
        mExitAnimator.start();
        mTransitionExecutor.startSharedTransition(
                mWindow, mView.getPreview(), intent, this::hideImmediate);
    }

    void hideImmediate() {
@@ -613,6 +688,76 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
        mClipboardLogger.reset();
    }

    @Override
    public void onDismissButtonTapped() {
        if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
            finish(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
        }
    }

    @Override
    public void onRemoteCopyButtonTapped() {
        if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
            finish(CLIPBOARD_OVERLAY_REMOTE_COPY_TAPPED,
                    IntentCreator.getRemoteCopyIntent(mClipboardModel.getClipData(), mContext));
        }
    }

    @Override
    public void onShareButtonTapped() {
        if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
            if (mClipboardModel.getType() != ClipboardModel.Type.OTHER) {
                finishWithSharedTransition(CLIPBOARD_OVERLAY_SHARE_TAPPED,
                        IntentCreator.getShareIntent(mClipboardModel.getClipData(), mContext));
            }
        }
    }

    @Override
    public void onPreviewTapped() {
        if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
            switch (mClipboardModel.getType()) {
                case TEXT:
                    finish(CLIPBOARD_OVERLAY_EDIT_TAPPED,
                            IntentCreator.getTextEditorIntent(mContext));
                    break;
                case IMAGE:
                    finishWithSharedTransition(CLIPBOARD_OVERLAY_EDIT_TAPPED,
                            IntentCreator.getImageEditIntent(mClipboardModel.getUri(), mContext));
                    break;
                default:
                    Log.w(TAG, "Got preview tapped callback for non-editable type "
                            + mClipboardModel.getType());
            }
        }
    }

    @Override
    public void onMinimizedViewTapped() {
        animateFromMinimized();
    }

    @Override
    public void onInteraction() {
        if (!mClipboardModel.isRemote()) {
            mTimeoutHandler.resetTimeout();
        }
    }

    @Override
    public void onSwipeDismissInitiated(Animator animator) {
        if (mExitAnimator != null && mExitAnimator.isRunning()) {
            mExitAnimator.cancel();
        }
        mExitAnimator = animator;
        mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_SWIPE_DISMISSED);
    }

    @Override
    public void onDismissComplete() {
        hideImmediate();
    }

    static class ClipboardLogger {
        private final UiEventLogger mUiEventLogger;
        private String mClipSource;
+17 −0
Original line number Diff line number Diff line
@@ -254,6 +254,10 @@ public class ClipboardOverlayView extends DraggableConstraintLayout {
                });
    }

    View getPreview() {
        return mClipboardPreview;
    }

    void showImagePreview(@Nullable Bitmap thumbnail) {
        if (thumbnail == null) {
            mHiddenPreview.setText(mContext.getString(R.string.clipboard_text_hidden));
@@ -368,6 +372,19 @@ public class ClipboardOverlayView extends DraggableConstraintLayout {
        return enterAnim;
    }

    Animator getFadeOutAnimation() {
        ValueAnimator alphaAnim = ValueAnimator.ofFloat(1, 0);
        alphaAnim.addUpdateListener(animation -> {
            float alpha = (float) animation.getAnimatedValue();
            mActionContainer.setAlpha(alpha);
            mActionContainerBackground.setAlpha(alpha);
            mPreviewBorder.setAlpha(alpha);
            mDismissButton.setAlpha(alpha);
        });
        alphaAnim.setDuration(300);
        return alphaAnim;
    }

    Animator getExitAnimation() {
        TimeInterpolator linearInterpolator = new LinearInterpolator();
        TimeInterpolator scaleInterpolator = new PathInterpolator(.3f, 0, 1f, 1f);
+93 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.clipboardoverlay

import android.app.ActivityOptions
import android.app.ExitTransitionCoordinator
import android.content.Context
import android.content.Intent
import android.os.RemoteException
import android.util.Log
import android.util.Pair
import android.view.IRemoteAnimationFinishedCallback
import android.view.IRemoteAnimationRunner
import android.view.RemoteAnimationAdapter
import android.view.RemoteAnimationTarget
import android.view.View
import android.view.Window
import android.view.WindowManagerGlobal
import com.android.internal.app.ChooserActivity
import com.android.systemui.settings.DisplayTracker
import javax.inject.Inject

class ClipboardTransitionExecutor
@Inject
constructor(val context: Context, val displayTracker: DisplayTracker) {
    fun startSharedTransition(window: Window, view: View, intent: Intent, onReady: Runnable) {
        val transition: Pair<ActivityOptions, ExitTransitionCoordinator> =
            ActivityOptions.startSharedElementAnimation(
                window,
                object : ExitTransitionCoordinator.ExitTransitionCallbacks {
                    override fun isReturnTransitionAllowed(): Boolean {
                        return false
                    }

                    override fun hideSharedElements() {
                        onReady.run()
                    }

                    override fun onFinish() {}
                },
                null,
                Pair.create(view, ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME)
            )
        transition.second.startExit()
        context.startActivity(intent, transition.first.toBundle())
        val runner = RemoteAnimationAdapter(NULL_ACTIVITY_TRANSITION, 0, 0)
        try {
            WindowManagerGlobal.getWindowManagerService()
                .overridePendingAppTransitionRemote(runner, displayTracker.defaultDisplayId)
        } catch (e: Exception) {
            Log.e(TAG, "Error overriding clipboard app transition", e)
        }
    }

    private val TAG: String = "ClipboardTransitionExec"

    /**
     * This is effectively a no-op, but we need something non-null to pass in, in order to
     * successfully override the pending activity entrance animation.
     */
    private val NULL_ACTIVITY_TRANSITION: IRemoteAnimationRunner.Stub =
        object : IRemoteAnimationRunner.Stub() {
            override fun onAnimationStart(
                transit: Int,
                apps: Array<RemoteAnimationTarget>,
                wallpapers: Array<RemoteAnimationTarget>,
                nonApps: Array<RemoteAnimationTarget>,
                finishedCallback: IRemoteAnimationFinishedCallback
            ) {
                try {
                    finishedCallback.onAnimationFinished()
                } catch (e: RemoteException) {
                    Log.e(TAG, "Error finishing screenshot remote animation", e)
                }
            }

            override fun onAnimationCancelled() {}
        }
}
+91 −6

File changed.

Preview size limit exceeded, changes collapsed.