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