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.launcher3; 18 19 import static android.animation.ValueAnimator.areAnimatorsEnabled; 20 import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED; 21 import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED; 22 23 import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled; 24 import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent; 25 26 import android.annotation.SuppressLint; 27 import android.content.Context; 28 import android.util.AttributeSet; 29 import android.util.Pair; 30 import android.view.MotionEvent; 31 import android.view.View; 32 import android.view.accessibility.AccessibilityNodeInfo; 33 import android.view.animation.Interpolator; 34 import android.widget.LinearLayout; 35 36 import androidx.annotation.IntDef; 37 38 import com.android.launcher3.anim.PendingAnimation; 39 import com.android.launcher3.util.TouchController; 40 import com.android.launcher3.views.ActivityContext; 41 import com.android.launcher3.views.BaseDragLayer; 42 43 import java.lang.annotation.Retention; 44 import java.lang.annotation.RetentionPolicy; 45 46 /** 47 * Base class for a View which shows a floating UI on top of the launcher UI. 48 */ 49 public abstract class AbstractFloatingView extends LinearLayout implements TouchController { 50 51 @IntDef(flag = true, value = { 52 TYPE_FOLDER, 53 TYPE_ACTION_POPUP, 54 TYPE_WIDGETS_BOTTOM_SHEET, 55 TYPE_WIDGET_RESIZE_FRAME, 56 TYPE_WIDGETS_FULL_SHEET, 57 TYPE_ON_BOARD_POPUP, 58 TYPE_DISCOVERY_BOUNCE, 59 TYPE_SNACKBAR, 60 TYPE_LISTENER, 61 TYPE_ALL_APPS_EDU, 62 TYPE_DRAG_DROP_POPUP, 63 TYPE_TASK_MENU, 64 TYPE_OPTIONS_POPUP, 65 TYPE_ICON_SURFACE, 66 TYPE_PIN_WIDGET_FROM_EXTERNAL_POPUP, 67 TYPE_WIDGETS_EDUCATION_DIALOG, 68 TYPE_TASKBAR_EDUCATION_DIALOG 69 }) 70 @Retention(RetentionPolicy.SOURCE) 71 public @interface FloatingViewType {} 72 public static final int TYPE_FOLDER = 1 << 0; 73 public static final int TYPE_ACTION_POPUP = 1 << 1; 74 public static final int TYPE_WIDGETS_BOTTOM_SHEET = 1 << 2; 75 public static final int TYPE_WIDGET_RESIZE_FRAME = 1 << 3; 76 public static final int TYPE_WIDGETS_FULL_SHEET = 1 << 4; 77 public static final int TYPE_ON_BOARD_POPUP = 1 << 5; 78 public static final int TYPE_DISCOVERY_BOUNCE = 1 << 6; 79 public static final int TYPE_SNACKBAR = 1 << 7; 80 public static final int TYPE_LISTENER = 1 << 8; 81 public static final int TYPE_ALL_APPS_EDU = 1 << 9; 82 public static final int TYPE_DRAG_DROP_POPUP = 1 << 10; 83 84 // Popups related to quickstep UI 85 public static final int TYPE_TASK_MENU = 1 << 11; 86 public static final int TYPE_OPTIONS_POPUP = 1 << 12; 87 public static final int TYPE_ICON_SURFACE = 1 << 13; 88 89 public static final int TYPE_PIN_WIDGET_FROM_EXTERNAL_POPUP = 1 << 14; 90 public static final int TYPE_WIDGETS_EDUCATION_DIALOG = 1 << 15; 91 public static final int TYPE_TASKBAR_EDUCATION_DIALOG = 1 << 16; 92 93 public static final int TYPE_ALL = TYPE_FOLDER | TYPE_ACTION_POPUP 94 | TYPE_WIDGETS_BOTTOM_SHEET | TYPE_WIDGET_RESIZE_FRAME | TYPE_WIDGETS_FULL_SHEET 95 | TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE | TYPE_TASK_MENU 96 | TYPE_OPTIONS_POPUP | TYPE_SNACKBAR | TYPE_LISTENER | TYPE_ALL_APPS_EDU 97 | TYPE_ICON_SURFACE | TYPE_DRAG_DROP_POPUP | TYPE_PIN_WIDGET_FROM_EXTERNAL_POPUP 98 | TYPE_WIDGETS_EDUCATION_DIALOG | TYPE_TASKBAR_EDUCATION_DIALOG; 99 100 // Type of popups which should be kept open during launcher rebind 101 public static final int TYPE_REBIND_SAFE = TYPE_WIDGETS_FULL_SHEET 102 | TYPE_WIDGETS_BOTTOM_SHEET | TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE 103 | TYPE_ALL_APPS_EDU | TYPE_ICON_SURFACE | TYPE_WIDGETS_EDUCATION_DIALOG 104 | TYPE_TASKBAR_EDUCATION_DIALOG; 105 106 // Usually we show the back button when a floating view is open. Instead, hide for these types. 107 public static final int TYPE_HIDE_BACK_BUTTON = TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE 108 | TYPE_SNACKBAR | TYPE_WIDGET_RESIZE_FRAME | TYPE_LISTENER; 109 110 public static final int TYPE_ACCESSIBLE = TYPE_ALL & ~TYPE_DISCOVERY_BOUNCE & ~TYPE_LISTENER 111 & ~TYPE_ALL_APPS_EDU; 112 113 // These view all have particular operation associated with swipe down interaction. 114 public static final int TYPE_STATUS_BAR_SWIPE_DOWN_DISALLOW = TYPE_WIDGETS_BOTTOM_SHEET | 115 TYPE_WIDGETS_FULL_SHEET | TYPE_WIDGET_RESIZE_FRAME | TYPE_ON_BOARD_POPUP | 116 TYPE_DISCOVERY_BOUNCE | TYPE_TASK_MENU | TYPE_DRAG_DROP_POPUP; 117 118 protected boolean mIsOpen; 119 AbstractFloatingView(Context context, AttributeSet attrs)120 public AbstractFloatingView(Context context, AttributeSet attrs) { 121 super(context, attrs); 122 } 123 AbstractFloatingView(Context context, AttributeSet attrs, int defStyleAttr)124 public AbstractFloatingView(Context context, AttributeSet attrs, int defStyleAttr) { 125 super(context, attrs, defStyleAttr); 126 } 127 128 /** 129 * We need to handle touch events to prevent them from falling through to the workspace below. 130 */ 131 @SuppressLint("ClickableViewAccessibility") 132 @Override onTouchEvent(MotionEvent ev)133 public boolean onTouchEvent(MotionEvent ev) { 134 return true; 135 } 136 close(boolean animate)137 public final void close(boolean animate) { 138 animate &= areAnimatorsEnabled(); 139 if (mIsOpen) { 140 // Add to WW logging 141 } 142 handleClose(animate); 143 mIsOpen = false; 144 } 145 handleClose(boolean animate)146 protected abstract void handleClose(boolean animate); 147 148 /** 149 * Creates a user-controlled animation to hint that the view will be closed if completed. 150 * @param distanceToMove The max distance that elements should move from their starting point. 151 */ addHintCloseAnim( float distanceToMove, Interpolator interpolator, PendingAnimation target)152 public void addHintCloseAnim( 153 float distanceToMove, Interpolator interpolator, PendingAnimation target) { } 154 isOpen()155 public final boolean isOpen() { 156 return mIsOpen; 157 } 158 isOfType(@loatingViewType int type)159 protected abstract boolean isOfType(@FloatingViewType int type); 160 161 /** @return Whether the back is consumed. If false, Launcher will handle the back as well. */ onBackPressed()162 public boolean onBackPressed() { 163 close(true); 164 return true; 165 } 166 167 @Override onControllerTouchEvent(MotionEvent ev)168 public boolean onControllerTouchEvent(MotionEvent ev) { 169 return false; 170 } 171 announceAccessibilityChanges()172 protected void announceAccessibilityChanges() { 173 Pair<View, String> targetInfo = getAccessibilityTarget(); 174 if (targetInfo == null || !isAccessibilityEnabled(getContext())) { 175 return; 176 } 177 sendCustomAccessibilityEvent( 178 targetInfo.first, TYPE_WINDOW_STATE_CHANGED, targetInfo.second); 179 180 if (mIsOpen) { 181 getAccessibilityInitialFocusView().performAccessibilityAction( 182 AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null); 183 } 184 ActivityContext.lookupContext(getContext()).getDragLayer() 185 .sendAccessibilityEvent(TYPE_WINDOW_CONTENT_CHANGED); 186 } 187 getAccessibilityTarget()188 protected Pair<View, String> getAccessibilityTarget() { 189 return null; 190 } 191 192 /** Returns the View that Accessibility services should focus on first. */ getAccessibilityInitialFocusView()193 protected View getAccessibilityInitialFocusView() { 194 return this; 195 } 196 197 /** 198 * Returns a view matching FloatingViewType and {@link #isOpen()} == true. 199 */ getOpenView( ActivityContext activity, @FloatingViewType int type)200 public static <T extends AbstractFloatingView> T getOpenView( 201 ActivityContext activity, @FloatingViewType int type) { 202 return getView(activity, type, true /* mustBeOpen */); 203 } 204 205 /** 206 * Returns a view matching FloatingViewType, and {@link #isOpen()} may be false (if animating 207 * closed). 208 */ getAnyView( ActivityContext activity, @FloatingViewType int type)209 public static <T extends AbstractFloatingView> T getAnyView( 210 ActivityContext activity, @FloatingViewType int type) { 211 return getView(activity, type, false /* mustBeOpen */); 212 } 213 getView( ActivityContext activity, @FloatingViewType int type, boolean mustBeOpen)214 private static <T extends AbstractFloatingView> T getView( 215 ActivityContext activity, @FloatingViewType int type, boolean mustBeOpen) { 216 BaseDragLayer dragLayer = activity.getDragLayer(); 217 if (dragLayer == null) return null; 218 // Iterate in reverse order. AbstractFloatingView is added later to the dragLayer, 219 // and will be one of the last views. 220 for (int i = dragLayer.getChildCount() - 1; i >= 0; i--) { 221 View child = dragLayer.getChildAt(i); 222 if (child instanceof AbstractFloatingView) { 223 AbstractFloatingView view = (AbstractFloatingView) child; 224 if (view.isOfType(type) && (!mustBeOpen || view.isOpen())) { 225 return (T) view; 226 } 227 } 228 } 229 return null; 230 } 231 closeOpenContainer(ActivityContext activity, @FloatingViewType int type)232 public static void closeOpenContainer(ActivityContext activity, 233 @FloatingViewType int type) { 234 AbstractFloatingView view = getOpenView(activity, type); 235 if (view != null) { 236 view.close(true); 237 } 238 } 239 closeOpenViews(ActivityContext activity, boolean animate, @FloatingViewType int type)240 public static void closeOpenViews(ActivityContext activity, boolean animate, 241 @FloatingViewType int type) { 242 BaseDragLayer dragLayer = activity.getDragLayer(); 243 // Iterate in reverse order. AbstractFloatingView is added later to the dragLayer, 244 // and will be one of the last views. 245 for (int i = dragLayer.getChildCount() - 1; i >= 0; i--) { 246 View child = dragLayer.getChildAt(i); 247 if (child instanceof AbstractFloatingView) { 248 AbstractFloatingView abs = (AbstractFloatingView) child; 249 if (abs.isOfType(type)) { 250 abs.close(animate); 251 } 252 } 253 } 254 } 255 closeAllOpenViews(ActivityContext activity, boolean animate)256 public static void closeAllOpenViews(ActivityContext activity, boolean animate) { 257 closeOpenViews(activity, animate, TYPE_ALL); 258 activity.finishAutoCancelActionMode(); 259 } 260 closeAllOpenViews(ActivityContext activity)261 public static void closeAllOpenViews(ActivityContext activity) { 262 closeAllOpenViews(activity, true); 263 } 264 closeAllOpenViewsExcept(ActivityContext activity, boolean animate, @FloatingViewType int type)265 public static void closeAllOpenViewsExcept(ActivityContext activity, boolean animate, 266 @FloatingViewType int type) { 267 closeOpenViews(activity, animate, TYPE_ALL & ~type); 268 activity.finishAutoCancelActionMode(); 269 } 270 closeAllOpenViewsExcept(ActivityContext activity, @FloatingViewType int type)271 public static void closeAllOpenViewsExcept(ActivityContext activity, 272 @FloatingViewType int type) { 273 closeAllOpenViewsExcept(activity, true, type); 274 } 275 getTopOpenView(ActivityContext activity)276 public static AbstractFloatingView getTopOpenView(ActivityContext activity) { 277 return getTopOpenViewWithType(activity, TYPE_ALL); 278 } 279 getTopOpenViewWithType(ActivityContext activity, @FloatingViewType int type)280 public static AbstractFloatingView getTopOpenViewWithType(ActivityContext activity, 281 @FloatingViewType int type) { 282 return getOpenView(activity, type); 283 } 284 canInterceptEventsInSystemGestureRegion()285 public boolean canInterceptEventsInSystemGestureRegion() { 286 return false; 287 } 288 } 289