1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.launcher3.views; 17 18 import static com.android.launcher3.views.FloatingIconView.getLocationBoundsForView; 19 import static com.android.launcher3.views.IconLabelDotView.setIconAndDotVisible; 20 21 import android.annotation.TargetApi; 22 import android.content.Context; 23 import android.graphics.Canvas; 24 import android.graphics.Picture; 25 import android.graphics.PixelFormat; 26 import android.graphics.Rect; 27 import android.graphics.RectF; 28 import android.os.Build; 29 import android.util.AttributeSet; 30 import android.view.MotionEvent; 31 import android.view.SurfaceHolder; 32 import android.view.SurfaceView; 33 import android.view.View; 34 import android.view.ViewTreeObserver.OnGlobalLayoutListener; 35 36 import androidx.annotation.NonNull; 37 38 import com.android.launcher3.AbstractFloatingView; 39 import com.android.launcher3.GestureNavContract; 40 import com.android.launcher3.Insettable; 41 import com.android.launcher3.Launcher; 42 import com.android.launcher3.R; 43 import com.android.launcher3.util.Executors; 44 import com.android.launcher3.util.window.RefreshRateTracker; 45 46 /** 47 * Similar to {@link FloatingIconView} but displays a surface with the targetIcon. It then passes 48 * the surfaceHandle to the {@link GestureNavContract}. 49 */ 50 @TargetApi(Build.VERSION_CODES.R) 51 public class FloatingSurfaceView extends AbstractFloatingView implements 52 OnGlobalLayoutListener, Insettable, SurfaceHolder.Callback2 { 53 54 private final RectF mTmpPosition = new RectF(); 55 56 private final Launcher mLauncher; 57 private final RectF mIconPosition = new RectF(); 58 59 private final Rect mIconBounds = new Rect(); 60 private final Picture mPicture = new Picture(); 61 private final Runnable mRemoveViewRunnable = this::removeViewFromParent; 62 63 private final SurfaceView mSurfaceView; 64 65 private View mIcon; 66 private GestureNavContract mContract; 67 FloatingSurfaceView(Context context)68 public FloatingSurfaceView(Context context) { 69 this(context, null); 70 } 71 FloatingSurfaceView(Context context, AttributeSet attrs)72 public FloatingSurfaceView(Context context, AttributeSet attrs) { 73 this(context, attrs, 0); 74 } 75 FloatingSurfaceView(Context context, AttributeSet attrs, int defStyleAttr)76 public FloatingSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) { 77 super(context, attrs, defStyleAttr); 78 mLauncher = Launcher.getLauncher(context); 79 80 mSurfaceView = new SurfaceView(context); 81 mSurfaceView.setZOrderOnTop(true); 82 83 mSurfaceView.getHolder().setFormat(PixelFormat.TRANSLUCENT); 84 mSurfaceView.getHolder().addCallback(this); 85 mIsOpen = true; 86 addView(mSurfaceView); 87 } 88 89 @Override handleClose(boolean animate)90 protected void handleClose(boolean animate) { 91 setCurrentIconVisible(true); 92 mLauncher.getViewCache().recycleView(R.layout.floating_surface_view, this); 93 mContract = null; 94 mIcon = null; 95 mIsOpen = false; 96 97 // Remove after some time, to avoid flickering 98 Executors.MAIN_EXECUTOR.getHandler().postDelayed(mRemoveViewRunnable, 99 RefreshRateTracker.getSingleFrameMs(mLauncher)); 100 } 101 removeViewFromParent()102 private void removeViewFromParent() { 103 mPicture.beginRecording(1, 1); 104 mPicture.endRecording(); 105 mLauncher.getDragLayer().removeViewInLayout(this); 106 } 107 removeViewImmediate()108 private void removeViewImmediate() { 109 // Cancel any pending remove 110 Executors.MAIN_EXECUTOR.getHandler().removeCallbacks(mRemoveViewRunnable); 111 removeViewFromParent(); 112 } 113 114 /** 115 * Shows the surfaceView for the provided contract 116 */ show(Launcher launcher, GestureNavContract contract)117 public static void show(Launcher launcher, GestureNavContract contract) { 118 FloatingSurfaceView view = launcher.getViewCache().getView(R.layout.floating_surface_view, 119 launcher, launcher.getDragLayer()); 120 view.mContract = contract; 121 view.mIsOpen = true; 122 123 view.removeViewImmediate(); 124 launcher.getDragLayer().addView(view); 125 } 126 127 @Override isOfType(int type)128 protected boolean isOfType(int type) { 129 return (type & TYPE_ICON_SURFACE) != 0; 130 } 131 132 @Override onControllerInterceptTouchEvent(MotionEvent ev)133 public boolean onControllerInterceptTouchEvent(MotionEvent ev) { 134 close(false); 135 removeViewImmediate(); 136 return false; 137 } 138 139 @Override onAttachedToWindow()140 protected void onAttachedToWindow() { 141 super.onAttachedToWindow(); 142 getViewTreeObserver().addOnGlobalLayoutListener(this); 143 updateIconLocation(); 144 } 145 146 @Override onDetachedFromWindow()147 protected void onDetachedFromWindow() { 148 super.onDetachedFromWindow(); 149 getViewTreeObserver().removeOnGlobalLayoutListener(this); 150 setCurrentIconVisible(true); 151 } 152 153 @Override onGlobalLayout()154 public void onGlobalLayout() { 155 updateIconLocation(); 156 } 157 158 @Override setInsets(Rect insets)159 public void setInsets(Rect insets) { } 160 updateIconLocation()161 private void updateIconLocation() { 162 if (mContract == null) { 163 return; 164 } 165 View icon = mLauncher.getFirstMatchForAppClose(-1, 166 mContract.componentName.getPackageName(), mContract.user, 167 false /* supportsAllAppsState */); 168 169 boolean iconChanged = mIcon != icon; 170 if (iconChanged) { 171 setCurrentIconVisible(true); 172 mIcon = icon; 173 setCurrentIconVisible(false); 174 } 175 176 if (icon != null && icon.isAttachedToWindow()) { 177 getLocationBoundsForView(mLauncher, icon, false, mTmpPosition, mIconBounds); 178 179 if (!mTmpPosition.equals(mIconPosition)) { 180 mIconPosition.set(mTmpPosition); 181 sendIconInfo(); 182 183 LayoutParams lp = (LayoutParams) mSurfaceView.getLayoutParams(); 184 lp.width = Math.round(mIconPosition.width()); 185 lp.height = Math.round(mIconPosition.height()); 186 lp.leftMargin = Math.round(mIconPosition.left); 187 lp.topMargin = Math.round(mIconPosition.top); 188 } 189 } 190 if (mIcon != null && iconChanged && !mIconBounds.isEmpty()) { 191 // Record the icon display 192 setCurrentIconVisible(true); 193 Canvas c = mPicture.beginRecording(mIconBounds.width(), mIconBounds.height()); 194 c.translate(-mIconBounds.left, -mIconBounds.top); 195 mIcon.draw(c); 196 mPicture.endRecording(); 197 setCurrentIconVisible(false); 198 drawOnSurface(); 199 } 200 } 201 sendIconInfo()202 private void sendIconInfo() { 203 if (mContract != null && !mIconPosition.isEmpty()) { 204 mContract.sendEndPosition(mIconPosition, mLauncher, mSurfaceView.getSurfaceControl()); 205 } 206 } 207 208 @Override surfaceCreated(@onNull SurfaceHolder surfaceHolder)209 public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) { 210 drawOnSurface(); 211 sendIconInfo(); 212 } 213 214 @Override surfaceChanged(@onNull SurfaceHolder surfaceHolder, int format, int width, int height)215 public void surfaceChanged(@NonNull SurfaceHolder surfaceHolder, 216 int format, int width, int height) { 217 drawOnSurface(); 218 } 219 220 @Override surfaceDestroyed(@onNull SurfaceHolder surfaceHolder)221 public void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) {} 222 223 @Override surfaceRedrawNeeded(@onNull SurfaceHolder surfaceHolder)224 public void surfaceRedrawNeeded(@NonNull SurfaceHolder surfaceHolder) { 225 drawOnSurface(); 226 } 227 drawOnSurface()228 private void drawOnSurface() { 229 SurfaceHolder surfaceHolder = mSurfaceView.getHolder(); 230 231 Canvas c = surfaceHolder.lockHardwareCanvas(); 232 if (c != null) { 233 mPicture.draw(c); 234 surfaceHolder.unlockCanvasAndPost(c); 235 } 236 } 237 setCurrentIconVisible(boolean isVisible)238 private void setCurrentIconVisible(boolean isVisible) { 239 if (mIcon != null) { 240 setIconAndDotVisible(mIcon, isVisible); 241 } 242 } 243 } 244