diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java index 1909f4772e..f60a50bb43 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java @@ -15,14 +15,35 @@ */ package com.android.quickstep; +import static android.content.Intent.EXTRA_COMPONENT_NAME; +import static android.content.Intent.EXTRA_USER; + +import static com.android.launcher3.GestureNavContract.EXTRA_GESTURE_CONTRACT; +import static com.android.launcher3.GestureNavContract.EXTRA_ICON_POSITION; +import static com.android.launcher3.GestureNavContract.EXTRA_ICON_SURFACE; +import static com.android.launcher3.GestureNavContract.EXTRA_REMOTE_CALLBACK; import static com.android.launcher3.anim.Interpolators.ACCEL; import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME; import android.animation.ObjectAnimator; +import android.annotation.TargetApi; import android.app.ActivityOptions; import android.content.Context; import android.content.Intent; import android.graphics.Matrix; +import android.graphics.Rect; +import android.graphics.RectF; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.Messenger; +import android.os.ParcelUuid; +import android.os.UserHandle; +import android.view.Surface; +import android.view.SurfaceControl; +import android.view.SurfaceControl.Transaction; import androidx.annotation.NonNull; @@ -32,19 +53,33 @@ import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.anim.SpringAnimationBuilder; import com.android.quickstep.fallback.FallbackRecentsView; +import com.android.quickstep.util.RectFSpringAnim; import com.android.quickstep.util.TransformParams; import com.android.quickstep.util.TransformParams.BuilderProxy; +import com.android.systemui.shared.recents.model.Task.TaskKey; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.InputConsumerController; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams; +import java.lang.ref.WeakReference; +import java.util.UUID; +import java.util.function.Consumer; + /** * Handles the navigation gestures when a 3rd party launcher is the default home activity. */ +@TargetApi(Build.VERSION_CODES.R) public class FallbackSwipeHandler extends BaseSwipeUpHandlerV2 { + /** + * Message used for receiving gesture nav contract information. We use a static messenger to + * avoid leaking too make binders in case the receiving launcher does not handle the contract + * properly. + */ + private static StaticMessageReceiver sMessageReceiver = null; + private FallbackHomeAnimationFactory mActiveAnimationFactory; private final boolean mRunningOverHome; @@ -89,7 +124,9 @@ public class FallbackSwipeHandler extends protected HomeAnimationFactory createHomeAnimationFactory(long duration) { mActiveAnimationFactory = new FallbackHomeAnimationFactory(duration); ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext, 0, 0); - mContext.startActivity(new Intent(mGestureState.getHomeIntent()), options.toBundle()); + Intent intent = new Intent(mGestureState.getHomeIntent()); + mActiveAnimationFactory.addGestureContract(intent); + mContext.startActivity(intent, options.toBundle()); return mActiveAnimationFactory; } @@ -130,15 +167,19 @@ public class FallbackSwipeHandler extends } private class FallbackHomeAnimationFactory extends HomeAnimationFactory { - + private final Rect mTempRect = new Rect(); private final TransformParams mHomeAlphaParams = new TransformParams(); private final AnimatedFloat mHomeAlpha; private final AnimatedFloat mVerticalShiftForScale = new AnimatedFloat(); - private final AnimatedFloat mRecentsAlpha = new AnimatedFloat(); + private final RectF mTargetRect = new RectF(); + private SurfaceControl mSurfaceControl; + private final long mDuration; + + private RectFSpringAnim mSpringAnim; FallbackHomeAnimationFactory(long duration) { mDuration = duration; @@ -161,6 +202,15 @@ public class FallbackSwipeHandler extends this::updateRecentsActivityTransformDuringHomeAnim); } + @NonNull + @Override + public RectF getWindowTargetRect() { + if (mTargetRect.isEmpty()) { + mTargetRect.set(super.getWindowTargetRect()); + } + return mTargetRect; + } + private void updateRecentsActivityTransformDuringHomeAnim(SurfaceParams.Builder builder, RemoteAnimationTargetCompat app, TransformParams params) { builder.withAlpha(mRecentsAlpha.value); @@ -217,5 +267,87 @@ public class FallbackSwipeHandler extends .start(); } } + + @Override + public void setAnimation(RectFSpringAnim anim) { + mSpringAnim = anim; + } + + private void onMessageReceived(Message msg) { + try { + Bundle data = msg.getData(); + RectF position = data.getParcelable(EXTRA_ICON_POSITION); + if (!position.isEmpty()) { + mSurfaceControl = data.getParcelable(EXTRA_ICON_SURFACE); + mTargetRect.set(position); + if (mSpringAnim != null) { + mSpringAnim.onTargetPositionChanged(); + } + } + } catch (Exception e) { + // Ignore + } + } + + @Override + public void update(RectF currentRect, float progress, float radius) { + if (mSurfaceControl != null) { + currentRect.roundOut(mTempRect); + Transaction t = new Transaction(); + t.setGeometry(mSurfaceControl, null, mTempRect, Surface.ROTATION_0); + t.apply(); + } + } + + private void addGestureContract(Intent intent) { + if (mRunningOverHome || mGestureState.getRunningTask() == null) { + return; + } + + TaskKey key = new TaskKey(mGestureState.getRunningTask()); + if (key.getComponent() != null) { + if (sMessageReceiver == null) { + sMessageReceiver = new StaticMessageReceiver(); + } + + Bundle gestureNavContract = new Bundle(); + gestureNavContract.putParcelable(EXTRA_COMPONENT_NAME, key.getComponent()); + gestureNavContract.putParcelable(EXTRA_USER, UserHandle.of(key.userId)); + gestureNavContract.putParcelable(EXTRA_REMOTE_CALLBACK, + sMessageReceiver.newCallback(this::onMessageReceived)); + intent.putExtra(EXTRA_GESTURE_CONTRACT, gestureNavContract); + } + } + } + + private static class StaticMessageReceiver implements Handler.Callback { + + private final Messenger mMessenger = + new Messenger(new Handler(Looper.getMainLooper(), this)); + + private ParcelUuid mCurrentUID = new ParcelUuid(UUID.randomUUID()); + private WeakReference> mCurrentCallback = new WeakReference<>(null); + + public Message newCallback(Consumer callback) { + mCurrentUID = new ParcelUuid(UUID.randomUUID()); + mCurrentCallback = new WeakReference<>(callback); + + Message msg = Message.obtain(); + msg.replyTo = mMessenger; + msg.obj = mCurrentUID; + return msg; + } + + @Override + public boolean handleMessage(@NonNull Message message) { + if (mCurrentUID.equals(message.obj)) { + Consumer consumer = mCurrentCallback.get(); + if (consumer != null) { + consumer.accept(message); + return true; + } + } + return false; + } } } diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java index 6b941be471..235df425b4 100644 --- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java @@ -110,6 +110,13 @@ public abstract class BaseQuickstepLauncher extends Launcher .getHighResLoadingState().setVisible(true); } + @Override + protected void handleGestureContract(Intent intent) { + if (FeatureFlags.SEPARATE_RECENTS_ACTIVITY.get()) { + super.handleGestureContract(intent); + } + } + @Override public void onTrimMemory(int level) { super.onTrimMemory(level); diff --git a/res/layout/floating_surface_view.xml b/res/layout/floating_surface_view.xml new file mode 100644 index 0000000000..434e84f269 --- /dev/null +++ b/res/layout/floating_surface_view.xml @@ -0,0 +1,19 @@ + + + diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java index cd27a2ddca..ce37a30160 100644 --- a/src/com/android/launcher3/AbstractFloatingView.java +++ b/src/com/android/launcher3/AbstractFloatingView.java @@ -62,7 +62,8 @@ public abstract class AbstractFloatingView extends LinearLayout implements Touch TYPE_ALL_APPS_EDU, TYPE_TASK_MENU, - TYPE_OPTIONS_POPUP + TYPE_OPTIONS_POPUP, + TYPE_ICON_SURFACE }) @Retention(RetentionPolicy.SOURCE) public @interface FloatingViewType {} @@ -80,16 +81,18 @@ public abstract class AbstractFloatingView extends LinearLayout implements Touch // Popups related to quickstep UI public static final int TYPE_TASK_MENU = 1 << 10; public static final int TYPE_OPTIONS_POPUP = 1 << 11; + public static final int TYPE_ICON_SURFACE = 1 << 12; public static final int TYPE_ALL = TYPE_FOLDER | TYPE_ACTION_POPUP | TYPE_WIDGETS_BOTTOM_SHEET | TYPE_WIDGET_RESIZE_FRAME | TYPE_WIDGETS_FULL_SHEET | TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE | TYPE_TASK_MENU - | TYPE_OPTIONS_POPUP | TYPE_SNACKBAR | TYPE_LISTENER | TYPE_ALL_APPS_EDU; + | TYPE_OPTIONS_POPUP | TYPE_SNACKBAR | TYPE_LISTENER | TYPE_ALL_APPS_EDU + | TYPE_ICON_SURFACE; // Type of popups which should be kept open during launcher rebind public static final int TYPE_REBIND_SAFE = TYPE_WIDGETS_FULL_SHEET | TYPE_WIDGETS_BOTTOM_SHEET | TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE - | TYPE_ALL_APPS_EDU; + | TYPE_ALL_APPS_EDU | TYPE_ICON_SURFACE; // Usually we show the back button when a floating view is open. Instead, hide for these types. public static final int TYPE_HIDE_BACK_BUTTON = TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index 48819cbfa0..198f13d02a 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -614,6 +614,9 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, @Override public void setIconVisible(boolean visible) { mIsIconVisible = visible; + if (!mIsIconVisible) { + resetIconScale(); + } Drawable icon = visible ? mIcon : new ColorDrawable(Color.TRANSPARENT); applyCompoundDrawables(icon); } @@ -753,11 +756,14 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, @Override public SafeCloseable prepareDrawDragView() { - if (getIcon() instanceof FastBitmapDrawable) { - FastBitmapDrawable icon = (FastBitmapDrawable) getIcon(); - icon.setScale(1f); - } + resetIconScale(); setForceHideDot(true); return () -> { }; } + + private void resetIconScale() { + if (mIcon instanceof FastBitmapDrawable) { + ((FastBitmapDrawable) mIcon).setScale(1f); + } + } } diff --git a/src/com/android/launcher3/GestureNavContract.java b/src/com/android/launcher3/GestureNavContract.java new file mode 100644 index 0000000000..2a7e629247 --- /dev/null +++ b/src/com/android/launcher3/GestureNavContract.java @@ -0,0 +1,101 @@ +/* + * 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. + */ +package com.android.launcher3; + +import static android.content.Intent.EXTRA_COMPONENT_NAME; +import static android.content.Intent.EXTRA_USER; + +import android.annotation.TargetApi; +import android.content.ComponentName; +import android.content.Intent; +import android.graphics.RectF; +import android.os.Build; +import android.os.Bundle; +import android.os.Message; +import android.os.RemoteException; +import android.os.UserHandle; +import android.util.Log; +import android.view.SurfaceControl; + +import androidx.annotation.Nullable; + +/** + * Class to encapsulate the handshake protocol between Launcher and gestureNav. + */ +public class GestureNavContract { + + private static final String TAG = "GestureNavContract"; + + public static final String EXTRA_GESTURE_CONTRACT = "gesture_nav_contract_v1"; + public static final String EXTRA_ICON_POSITION = "gesture_nav_contract_icon_position"; + public static final String EXTRA_ICON_SURFACE = "gesture_nav_contract_surface_control"; + public static final String EXTRA_REMOTE_CALLBACK = "android.intent.extra.REMOTE_CALLBACK"; + + public final ComponentName componentName; + public final UserHandle user; + + private final Message mCallback; + + public GestureNavContract(ComponentName componentName, UserHandle user, Message callback) { + this.componentName = componentName; + this.user = user; + this.mCallback = callback; + } + + /** + * Sends the position information to the receiver + */ + @TargetApi(Build.VERSION_CODES.R) + public void sendEndPosition(RectF position, @Nullable SurfaceControl surfaceControl) { + Bundle result = new Bundle(); + result.putParcelable(EXTRA_ICON_POSITION, position); + result.putParcelable(EXTRA_ICON_SURFACE, surfaceControl); + + Message callback = Message.obtain(); + callback.copyFrom(mCallback); + callback.setData(result); + + try { + callback.replyTo.send(callback); + } catch (RemoteException e) { + Log.e(TAG, "Error sending icon position", e); + } + } + + /** + * Clears and returns the GestureNavContract if it was present in the intent. + */ + public static GestureNavContract fromIntent(Intent intent) { + if (!Utilities.ATLEAST_R) { + return null; + } + Bundle extras = intent.getBundleExtra(EXTRA_GESTURE_CONTRACT); + if (extras == null) { + return null; + } + intent.removeExtra(EXTRA_GESTURE_CONTRACT); + + ComponentName componentName = extras.getParcelable(EXTRA_COMPONENT_NAME); + UserHandle userHandle = extras.getParcelable(EXTRA_USER); + Message callback = extras.getParcelable(EXTRA_REMOTE_CALLBACK); + + if (componentName != null && userHandle != null && callback != null + && callback.replyTo != null) { + return new GestureNavContract(componentName, userHandle, callback); + } + return null; + } +} diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index d06ae7a9c5..4675362f64 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -21,6 +21,7 @@ import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE; import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED; import static com.android.launcher3.AbstractFloatingView.TYPE_ALL; +import static com.android.launcher3.AbstractFloatingView.TYPE_ICON_SURFACE; import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE; import static com.android.launcher3.AbstractFloatingView.TYPE_SNACKBAR; import static com.android.launcher3.InstallShortcutReceiver.FLAG_DRAG_AND_DROP; @@ -168,6 +169,7 @@ import com.android.launcher3.util.TraceHelper; import com.android.launcher3.util.UiThreadHelper; import com.android.launcher3.util.ViewOnDrawExecutor; import com.android.launcher3.views.ActivityContext; +import com.android.launcher3.views.FloatingSurfaceView; import com.android.launcher3.views.OptionsPopupView; import com.android.launcher3.views.ScrimView; import com.android.launcher3.widget.LauncherAppWidgetHostView; @@ -509,6 +511,7 @@ public class Launcher extends StatefulActivity implements Launche public void onEnterAnimationComplete() { super.onEnterAnimationComplete(); mRotationHelper.setCurrentTransitionRequest(REQUEST_NONE); + AbstractFloatingView.closeOpenViews(this, false, TYPE_ICON_SURFACE); } @Override @@ -1450,6 +1453,7 @@ public class Launcher extends StatefulActivity implements Launche mLauncherCallbacks.onHomeIntent(internalStateHandled); } mOverlayManager.hideOverlay(isStarted() && !isForceInvisible()); + handleGestureContract(intent); } else if (Intent.ACTION_ALL_APPS.equals(intent.getAction())) { getStateManager().goToState(ALL_APPS, alreadyOnHome); } @@ -1457,6 +1461,17 @@ public class Launcher extends StatefulActivity implements Launche TraceHelper.INSTANCE.endSection(traceToken); } + /** + * Handles gesture nav contract + */ + protected void handleGestureContract(Intent intent) { + GestureNavContract gnc = GestureNavContract.fromIntent(intent); + if (gnc != null) { + AbstractFloatingView.closeOpenViews(this, false, TYPE_ICON_SURFACE); + FloatingSurfaceView.show(this, gnc); + } + } + /** * Hides the keyboard if visible */ diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java index 7cdde2e84b..8186dfa3dc 100644 --- a/src/com/android/launcher3/views/FloatingIconView.java +++ b/src/com/android/launcher3/views/FloatingIconView.java @@ -196,13 +196,18 @@ public class FloatingIconView extends FrameLayout implements layout(left, lp.topMargin, left + lp.width, lp.topMargin + lp.height); } + private static void getLocationBoundsForView(Launcher launcher, View v, boolean isOpening, + RectF outRect) { + getLocationBoundsForView(launcher, v, isOpening, outRect, new Rect()); + } + /** * Gets the location bounds of a view and returns the overall rotation. * - For DeepShortcutView, we return the bounds of the icon view. * - For BubbleTextView, we return the icon bounds. */ - private static void getLocationBoundsForView(Launcher launcher, View v, boolean isOpening, - RectF outRect) { + public static void getLocationBoundsForView(Launcher launcher, View v, boolean isOpening, + RectF outRect, Rect outViewBounds) { boolean ignoreTransform = !isOpening; if (v instanceof DeepShortcutView) { v = ((DeepShortcutView) v).getBubbleText(); @@ -215,17 +220,16 @@ public class FloatingIconView extends FrameLayout implements return; } - Rect iconBounds = new Rect(); if (v instanceof BubbleTextView) { - ((BubbleTextView) v).getIconBounds(iconBounds); + ((BubbleTextView) v).getIconBounds(outViewBounds); } else if (v instanceof FolderIcon) { - ((FolderIcon) v).getPreviewBounds(iconBounds); + ((FolderIcon) v).getPreviewBounds(outViewBounds); } else { - iconBounds.set(0, 0, v.getWidth(), v.getHeight()); + outViewBounds.set(0, 0, v.getWidth(), v.getHeight()); } - float[] points = new float[] {iconBounds.left, iconBounds.top, iconBounds.right, - iconBounds.bottom}; + float[] points = new float[] {outViewBounds.left, outViewBounds.top, outViewBounds.right, + outViewBounds.bottom}; Utilities.getDescendantCoordRelativeToAncestor(v, launcher.getDragLayer(), points, false, ignoreTransform); outRect.set( diff --git a/src/com/android/launcher3/views/FloatingSurfaceView.java b/src/com/android/launcher3/views/FloatingSurfaceView.java new file mode 100644 index 0000000000..040619e9c7 --- /dev/null +++ b/src/com/android/launcher3/views/FloatingSurfaceView.java @@ -0,0 +1,241 @@ +/* + * 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. + */ +package com.android.launcher3.views; + +import static com.android.launcher3.views.FloatingIconView.getLocationBoundsForView; +import static com.android.launcher3.views.IconLabelDotView.setIconAndDotVisible; + +import android.annotation.TargetApi; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Picture; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.RectF; +import android.os.Build; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.View; +import android.view.ViewTreeObserver.OnGlobalLayoutListener; + +import androidx.annotation.NonNull; + +import com.android.launcher3.AbstractFloatingView; +import com.android.launcher3.GestureNavContract; +import com.android.launcher3.Insettable; +import com.android.launcher3.Launcher; +import com.android.launcher3.R; +import com.android.launcher3.util.DefaultDisplay; +import com.android.launcher3.util.Executors; + +/** + * Similar to {@link FloatingIconView} but displays a surface with the targetIcon. It then passes + * the surfaceHandle to the {@link GestureNavContract}. + */ +@TargetApi(Build.VERSION_CODES.R) +public class FloatingSurfaceView extends AbstractFloatingView implements + OnGlobalLayoutListener, Insettable, SurfaceHolder.Callback2 { + + private final RectF mTmpPosition = new RectF(); + + private final Launcher mLauncher; + private final RectF mIconPosition = new RectF(); + + private final Rect mIconBounds = new Rect(); + private final Picture mPicture = new Picture(); + private final Runnable mRemoveViewRunnable = this::removeViewFromParent; + + private final SurfaceView mSurfaceView; + + + private View mIcon; + private GestureNavContract mContract; + + public FloatingSurfaceView(Context context) { + this(context, null); + } + + public FloatingSurfaceView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public FloatingSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + mLauncher = Launcher.getLauncher(context); + + mSurfaceView = new SurfaceView(context); + mSurfaceView.setZOrderOnTop(true); + + mSurfaceView.getHolder().setFormat(PixelFormat.TRANSLUCENT); + mSurfaceView.getHolder().addCallback(this); + mIsOpen = true; + addView(mSurfaceView); + } + + @Override + protected void handleClose(boolean animate) { + setCurrentIconVisible(true); + mLauncher.getViewCache().recycleView(R.layout.floating_surface_view, this); + mContract = null; + mIcon = null; + mIsOpen = false; + + // Remove after some time, to avoid flickering + Executors.MAIN_EXECUTOR.getHandler().postDelayed(mRemoveViewRunnable, + DefaultDisplay.INSTANCE.get(mLauncher).getInfo().singleFrameMs); + } + + private void removeViewFromParent() { + mPicture.beginRecording(1, 1); + mPicture.endRecording(); + mLauncher.getDragLayer().removeView(this); + } + + /** + * Shows the surfaceView for the provided contract + */ + public static void show(Launcher launcher, GestureNavContract contract) { + FloatingSurfaceView view = launcher.getViewCache().getView(R.layout.floating_surface_view, + launcher, launcher.getDragLayer()); + view.mContract = contract; + view.mIsOpen = true; + + // Cancel any pending remove + Executors.MAIN_EXECUTOR.getHandler().removeCallbacks(view.mRemoveViewRunnable); + view.removeViewFromParent(); + launcher.getDragLayer().addView(view); + } + + @Override + public void logActionCommand(int command) { } + + @Override + protected boolean isOfType(int type) { + return (type & TYPE_ICON_SURFACE) != 0; + } + + @Override + public boolean onControllerInterceptTouchEvent(MotionEvent ev) { + close(false); + return false; + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + getViewTreeObserver().addOnGlobalLayoutListener(this); + updateIconLocation(); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + getViewTreeObserver().removeOnGlobalLayoutListener(this); + setCurrentIconVisible(true); + } + + @Override + public void onGlobalLayout() { + updateIconLocation(); + } + + @Override + public void setInsets(Rect insets) { } + + private void updateIconLocation() { + if (mContract == null) { + return; + } + View icon = mLauncher.getWorkspace().getFirstMatchForAppClose( + mContract.componentName.getPackageName(), mContract.user); + + boolean iconChanged = mIcon != icon; + if (iconChanged) { + setCurrentIconVisible(true); + mIcon = icon; + setCurrentIconVisible(false); + } + + if (icon != null && icon.isAttachedToWindow()) { + getLocationBoundsForView(mLauncher, icon, false, mTmpPosition, mIconBounds); + + if (!mTmpPosition.equals(mIconPosition)) { + mIconPosition.set(mTmpPosition); + sendIconInfo(); + + LayoutParams lp = (LayoutParams) mSurfaceView.getLayoutParams(); + lp.width = Math.round(mIconPosition.width()); + lp.height = Math.round(mIconPosition.height()); + lp.leftMargin = Math.round(mIconPosition.left); + lp.topMargin = Math.round(mIconPosition.top); + } + } + if (iconChanged && !mIconBounds.isEmpty()) { + // Record the icon display + setCurrentIconVisible(true); + Canvas c = mPicture.beginRecording(mIconBounds.width(), mIconBounds.height()); + c.translate(-mIconBounds.left, -mIconBounds.top); + mIcon.draw(c); + mPicture.endRecording(); + setCurrentIconVisible(false); + drawOnSurface(); + } + } + + private void sendIconInfo() { + if (mContract != null && !mIconPosition.isEmpty()) { + mContract.sendEndPosition(mIconPosition, mSurfaceView.getSurfaceControl()); + } + } + + @Override + public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) { + drawOnSurface(); + sendIconInfo(); + } + + @Override + public void surfaceChanged(@NonNull SurfaceHolder surfaceHolder, + int format, int width, int height) { + drawOnSurface(); + } + + @Override + public void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) {} + + @Override + public void surfaceRedrawNeeded(@NonNull SurfaceHolder surfaceHolder) { + drawOnSurface(); + } + + private void drawOnSurface() { + SurfaceHolder surfaceHolder = mSurfaceView.getHolder(); + + Canvas c = surfaceHolder.lockHardwareCanvas(); + if (c != null) { + mPicture.draw(c); + surfaceHolder.unlockCanvasAndPost(c); + } + } + + private void setCurrentIconVisible(boolean isVisible) { + if (mIcon != null) { + setIconAndDotVisible(mIcon, isVisible); + } + } +}