1 /* 2 * Copyright (C) 2016 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 17 package com.android.systemui.pip.phone; 18 19 import android.content.Context; 20 import android.graphics.PixelFormat; 21 import android.graphics.Point; 22 import android.graphics.Rect; 23 import android.graphics.drawable.Drawable; 24 import android.os.VibrationEffect; 25 import android.os.Vibrator; 26 import android.view.Gravity; 27 import android.view.LayoutInflater; 28 import android.view.View; 29 import android.view.WindowManager; 30 import android.view.WindowManager.LayoutParams; 31 import android.widget.FrameLayout; 32 33 import com.android.systemui.Interpolators; 34 import com.android.systemui.R; 35 import com.android.systemui.shared.system.WindowManagerWrapper; 36 37 /** 38 * Displays the dismiss UI and target for floating objects. 39 */ 40 public class PipDismissViewController { 41 42 // This delay controls how long to wait before we show the target when the user first moves 43 // the PIP, to prevent the target from animating if the user just wants to fling the PIP 44 public static final int SHOW_TARGET_DELAY = 100; 45 private static final int SHOW_TARGET_DURATION = 350; 46 private static final int HIDE_TARGET_DURATION = 225; 47 48 private Context mContext; 49 private WindowManager mWindowManager; 50 private View mDismissView; 51 52 // Used for dismissing a bubble -- bubble should be in the target to be considered a dismiss 53 private View mTargetView; 54 private int mTargetSlop; 55 private Point mWindowSize; 56 private int[] mLoc = new int[2]; 57 private boolean mIntersecting; 58 private Vibrator mVibe; 59 PipDismissViewController(Context context)60 public PipDismissViewController(Context context) { 61 mContext = context; 62 mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 63 mVibe = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); 64 } 65 66 /** 67 * Creates the dismiss target for showing via {@link #showDismissTarget()}. 68 */ createDismissTarget()69 public void createDismissTarget() { 70 if (mDismissView == null) { 71 // Determine sizes for the view 72 final Rect stableInsets = new Rect(); 73 WindowManagerWrapper.getInstance().getStableInsets(stableInsets); 74 mWindowSize = new Point(); 75 mWindowManager.getDefaultDisplay().getRealSize(mWindowSize); 76 final int gradientHeight = mContext.getResources().getDimensionPixelSize( 77 R.dimen.pip_dismiss_gradient_height); 78 final int bottomMargin = mContext.getResources().getDimensionPixelSize( 79 R.dimen.pip_dismiss_text_bottom_margin); 80 mTargetSlop = mContext.getResources().getDimensionPixelSize( 81 R.dimen.bubble_dismiss_slop); 82 83 // Create a new view for the dismiss target 84 LayoutInflater inflater = LayoutInflater.from(mContext); 85 mDismissView = inflater.inflate(R.layout.pip_dismiss_view, null); 86 mDismissView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE); 87 mDismissView.forceHasOverlappingRendering(false); 88 89 // Set the gradient background 90 Drawable gradient = mContext.getResources().getDrawable(R.drawable.pip_dismiss_scrim); 91 gradient.setAlpha((int) (255 * 0.85f)); 92 mDismissView.setBackground(gradient); 93 94 // Adjust bottom margins of the text 95 mTargetView = mDismissView.findViewById(R.id.pip_dismiss_text); 96 FrameLayout.LayoutParams tlp = (FrameLayout.LayoutParams) mTargetView.getLayoutParams(); 97 tlp.bottomMargin = stableInsets.bottom + bottomMargin; 98 mTargetView.setLayoutParams(tlp); 99 100 // Add the target to the window 101 LayoutParams lp = new LayoutParams( 102 LayoutParams.MATCH_PARENT, gradientHeight, 103 0, mWindowSize.y - gradientHeight, 104 LayoutParams.TYPE_NAVIGATION_BAR_PANEL, 105 LayoutParams.FLAG_LAYOUT_IN_SCREEN 106 | LayoutParams.FLAG_NOT_TOUCHABLE 107 | LayoutParams.FLAG_NOT_FOCUSABLE, 108 PixelFormat.TRANSLUCENT); 109 lp.setTitle("pip-dismiss-overlay"); 110 lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS; 111 lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL; 112 mWindowManager.addView(mDismissView, lp); 113 } 114 mDismissView.animate().cancel(); 115 } 116 117 118 /** 119 * Updates the dismiss target based on location of the view, only used for bubbles not for PIP. 120 * 121 * @return whether the view is within the dismiss target. 122 */ updateTarget(View view)123 public boolean updateTarget(View view) { 124 if (mDismissView == null) { 125 return false; 126 } 127 if (mDismissView.getAlpha() > 0) { 128 view.getLocationOnScreen(mLoc); 129 Rect viewRect = new Rect(mLoc[0], mLoc[1], mLoc[0] + view.getWidth(), 130 mLoc[1] + view.getHeight()); 131 mTargetView.getLocationOnScreen(mLoc); 132 Rect targetRect = new Rect(mLoc[0], mLoc[1], mLoc[0] + mTargetView.getWidth(), 133 mLoc[1] + mTargetView.getHeight()); 134 expandRect(targetRect, mTargetSlop); 135 boolean intersecting = targetRect.intersect(viewRect); 136 if (intersecting != mIntersecting) { 137 // TODO: is this the right effect? 138 mVibe.vibrate(VibrationEffect.get(intersecting 139 ? VibrationEffect.EFFECT_CLICK 140 : VibrationEffect.EFFECT_TICK)); 141 } 142 mIntersecting = intersecting; 143 return intersecting; 144 } 145 return false; 146 } 147 148 /** 149 * Shows the dismiss target. 150 */ showDismissTarget()151 public void showDismissTarget() { 152 mDismissView.animate() 153 .alpha(1f) 154 .setInterpolator(Interpolators.LINEAR) 155 .setStartDelay(SHOW_TARGET_DELAY) 156 .setDuration(SHOW_TARGET_DURATION) 157 .start(); 158 } 159 160 /** 161 * Hides and destroys the dismiss target. 162 */ destroyDismissTarget()163 public void destroyDismissTarget() { 164 if (mDismissView != null) { 165 mDismissView.animate() 166 .alpha(0f) 167 .setInterpolator(Interpolators.LINEAR) 168 .setStartDelay(0) 169 .setDuration(HIDE_TARGET_DURATION) 170 .withEndAction(new Runnable() { 171 @Override 172 public void run() { 173 mWindowManager.removeViewImmediate(mDismissView); 174 mDismissView = null; 175 } 176 }) 177 .start(); 178 } 179 } 180 expandRect(Rect outRect, int expandAmount)181 private void expandRect(Rect outRect, int expandAmount) { 182 outRect.left = Math.max(0, outRect.left - expandAmount); 183 outRect.top = Math.max(0, outRect.top - expandAmount); 184 outRect.right = Math.min(mWindowSize.x, outRect.right + expandAmount); 185 outRect.bottom = Math.min(mWindowSize.y, outRect.bottom + expandAmount); 186 } 187 } 188