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