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.model.data.ItemInfo.NO_MATCHING_ID; 19 import static com.android.launcher3.views.FloatingIconView.getLocationBoundsForView; 20 import static com.android.launcher3.views.IconLabelDotView.setIconAndDotVisible; 21 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.util.AttributeSet; 29 import android.view.MotionEvent; 30 import android.view.SurfaceHolder; 31 import android.view.SurfaceView; 32 import android.view.View; 33 import android.view.ViewTreeObserver.OnGlobalLayoutListener; 34 35 import androidx.annotation.NonNull; 36 37 import com.android.launcher3.AbstractFloatingView; 38 import com.android.launcher3.GestureNavContract; 39 import com.android.launcher3.Insettable; 40 import com.android.launcher3.Launcher; 41 import com.android.launcher3.R; 42 import com.android.launcher3.util.Executors; 43 import com.android.launcher3.util.window.RefreshRateTracker; 44 45 /** 46 * Similar to {@link FloatingIconView} but displays a surface with the targetIcon. It then passes 47 * the surfaceHandle to the {@link GestureNavContract}. 48 */ 49 public class FloatingSurfaceView extends AbstractFloatingView implements 50 OnGlobalLayoutListener, Insettable, SurfaceHolder.Callback2 { 51 52 private final RectF mTmpPosition = new RectF(); 53 54 private final Launcher mLauncher; 55 private final RectF mIconPosition = new RectF(); 56 57 private final Rect mIconBounds = new Rect(); 58 private final Picture mPicture = new Picture(); 59 private final Runnable mRemoveViewRunnable = this::removeViewFromParent; 60 61 private final SurfaceView mSurfaceView; 62 63 private View mIcon; 64 private GestureNavContract mContract; 65 FloatingSurfaceView(Context context)66 public FloatingSurfaceView(Context context) { 67 this(context, null); 68 } 69 FloatingSurfaceView(Context context, AttributeSet attrs)70 public FloatingSurfaceView(Context context, AttributeSet attrs) { 71 this(context, attrs, 0); 72 } 73 FloatingSurfaceView(Context context, AttributeSet attrs, int defStyleAttr)74 public FloatingSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) { 75 super(context, attrs, defStyleAttr); 76 mLauncher = Launcher.getLauncher(context); 77 78 mSurfaceView = new SurfaceView(context); 79 mSurfaceView.setZOrderOnTop(true); 80 81 mSurfaceView.getHolder().setFormat(PixelFormat.TRANSLUCENT); 82 mSurfaceView.getHolder().addCallback(this); 83 mIsOpen = true; 84 addView(mSurfaceView); 85 } 86 87 @Override handleClose(boolean animate)88 protected void handleClose(boolean animate) { 89 setCurrentIconVisible(true); 90 mLauncher.getViewCache().recycleView(R.layout.floating_surface_view, this); 91 mContract = null; 92 mIcon = null; 93 mIsOpen = false; 94 95 // Remove after some time, to avoid flickering 96 Executors.MAIN_EXECUTOR.getHandler().postDelayed(mRemoveViewRunnable, 97 RefreshRateTracker.getSingleFrameMs(mLauncher)); 98 } 99 removeViewFromParent()100 private void removeViewFromParent() { 101 mPicture.beginRecording(1, 1); 102 mPicture.endRecording(); 103 mLauncher.getDragLayer().removeViewInLayout(this); 104 } 105 removeViewImmediate()106 private void removeViewImmediate() { 107 // Cancel any pending remove 108 Executors.MAIN_EXECUTOR.getHandler().removeCallbacks(mRemoveViewRunnable); 109 removeViewFromParent(); 110 } 111 112 /** 113 * Shows the surfaceView for the provided contract 114 */ show(Launcher launcher, GestureNavContract contract)115 public static void show(Launcher launcher, GestureNavContract contract) { 116 FloatingSurfaceView view = launcher.getViewCache().getView(R.layout.floating_surface_view, 117 launcher, launcher.getDragLayer()); 118 view.mContract = contract; 119 view.mIsOpen = true; 120 121 view.removeViewImmediate(); 122 launcher.getDragLayer().addView(view); 123 } 124 125 @Override isOfType(int type)126 protected boolean isOfType(int type) { 127 return (type & TYPE_ICON_SURFACE) != 0; 128 } 129 130 @Override onControllerInterceptTouchEvent(MotionEvent ev)131 public boolean onControllerInterceptTouchEvent(MotionEvent ev) { 132 close(false); 133 removeViewImmediate(); 134 return false; 135 } 136 137 @Override onAttachedToWindow()138 protected void onAttachedToWindow() { 139 super.onAttachedToWindow(); 140 getViewTreeObserver().addOnGlobalLayoutListener(this); 141 updateIconLocation(); 142 } 143 144 @Override onDetachedFromWindow()145 protected void onDetachedFromWindow() { 146 super.onDetachedFromWindow(); 147 getViewTreeObserver().removeOnGlobalLayoutListener(this); 148 setCurrentIconVisible(true); 149 } 150 151 @Override onGlobalLayout()152 public void onGlobalLayout() { 153 updateIconLocation(); 154 } 155 156 @Override setInsets(Rect insets)157 public void setInsets(Rect insets) { } 158 updateIconLocation()159 private void updateIconLocation() { 160 if (mContract == null) { 161 return; 162 } 163 View icon = mLauncher.getFirstMatchForAppClose(NO_MATCHING_ID, 164 mContract.componentName.getPackageName(), mContract.user, 165 false /* supportsAllAppsState */); 166 167 boolean iconChanged = mIcon != icon; 168 if (iconChanged) { 169 setCurrentIconVisible(true); 170 mIcon = icon; 171 setCurrentIconVisible(false); 172 } 173 174 if (icon != null && icon.isAttachedToWindow()) { 175 getLocationBoundsForView(mLauncher, icon, false, mTmpPosition, mIconBounds); 176 177 if (!mTmpPosition.equals(mIconPosition)) { 178 mIconPosition.set(mTmpPosition); 179 180 LayoutParams lp = (LayoutParams) mSurfaceView.getLayoutParams(); 181 lp.width = Math.round(mIconPosition.width()); 182 lp.height = Math.round(mIconPosition.height()); 183 lp.leftMargin = Math.round(mIconPosition.left); 184 lp.topMargin = Math.round(mIconPosition.top); 185 } 186 } 187 188 sendIconInfo(); 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) { 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