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