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:
Sunny Goyal
2020-06-24 23:47:46 -07:00
parent 664020146f
commit 30ac97d938
9 changed files with 546 additions and 18 deletions
@@ -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);
+19
View File
@@ -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
+10 -4
View File
@@ -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;
}
}
+15
View File
@@ -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);
}
}
}