Adding home animation support for non-system Launcher
When user swipes up to home, Launcher will receive a onNewIntent callwith a bundle-extra gesture_nav_contract_v1. It will contain the componentName & UserHandle of the closing app & a callback. Launcher can use the callback to return the final position where the app should animate to and an optional surface to be used for crossFade animation. The surface cleanup can be handled in onEnterAnimationComplete. Change-Id: I76fdd810fdcb80b71f7d7588ccac8976d9dfe278
This commit is contained in:
+135
-3
@@ -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<RecentsActivity, FallbackRecentsView> {
|
||||
|
||||
/**
|
||||
* 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<Consumer<Message>> mCurrentCallback = new WeakReference<>(null);
|
||||
|
||||
public Message newCallback(Consumer<Message> 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<Message> consumer = mCurrentCallback.get();
|
||||
if (consumer != null) {
|
||||
consumer.accept(message);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<?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.
|
||||
-->
|
||||
<com.android.launcher3.views.FloatingSurfaceView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<LauncherState> 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<LauncherState> 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<LauncherState> 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
|
||||
*/
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user