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
17package com.android.launcher3;
18
19import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED;
20import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;
21import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
22
23import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled;
24import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
25
26import android.annotation.SuppressLint;
27import android.content.Context;
28import android.support.annotation.IntDef;
29import android.util.AttributeSet;
30import android.util.Pair;
31import android.view.MotionEvent;
32import android.view.View;
33import android.widget.LinearLayout;
34
35import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
36import com.android.launcher3.util.TouchController;
37import com.android.launcher3.views.BaseDragLayer;
38
39import java.lang.annotation.Retention;
40import java.lang.annotation.RetentionPolicy;
41
42/**
43 * Base class for a View which shows a floating UI on top of the launcher UI.
44 */
45public abstract class AbstractFloatingView extends LinearLayout implements TouchController {
46
47    @IntDef(flag = true, value = {
48            TYPE_FOLDER,
49            TYPE_ACTION_POPUP,
50            TYPE_WIDGETS_BOTTOM_SHEET,
51            TYPE_WIDGET_RESIZE_FRAME,
52            TYPE_WIDGETS_FULL_SHEET,
53            TYPE_ON_BOARD_POPUP,
54            TYPE_DISCOVERY_BOUNCE,
55
56            TYPE_QUICKSTEP_PREVIEW,
57            TYPE_TASK_MENU,
58            TYPE_OPTIONS_POPUP
59    })
60    @Retention(RetentionPolicy.SOURCE)
61    public @interface FloatingViewType {}
62    public static final int TYPE_FOLDER = 1 << 0;
63    public static final int TYPE_ACTION_POPUP = 1 << 1;
64    public static final int TYPE_WIDGETS_BOTTOM_SHEET = 1 << 2;
65    public static final int TYPE_WIDGET_RESIZE_FRAME = 1 << 3;
66    public static final int TYPE_WIDGETS_FULL_SHEET = 1 << 4;
67    public static final int TYPE_ON_BOARD_POPUP = 1 << 5;
68    public static final int TYPE_DISCOVERY_BOUNCE = 1 << 6;
69
70    // Popups related to quickstep UI
71    public static final int TYPE_QUICKSTEP_PREVIEW = 1 << 6;
72    public static final int TYPE_TASK_MENU = 1 << 7;
73    public static final int TYPE_OPTIONS_POPUP = 1 << 8;
74
75    public static final int TYPE_ALL = TYPE_FOLDER | TYPE_ACTION_POPUP
76            | TYPE_WIDGETS_BOTTOM_SHEET | TYPE_WIDGET_RESIZE_FRAME | TYPE_WIDGETS_FULL_SHEET
77            | TYPE_QUICKSTEP_PREVIEW | TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE | TYPE_TASK_MENU
78            | TYPE_OPTIONS_POPUP;
79
80    // Type of popups which should be kept open during launcher rebind
81    public static final int TYPE_REBIND_SAFE = TYPE_WIDGETS_FULL_SHEET
82            | TYPE_QUICKSTEP_PREVIEW | TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE;
83
84    // Usually we show the back button when a floating view is open. Instead, hide for these types.
85    public static final int TYPE_HIDE_BACK_BUTTON = TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE;
86
87    public static final int TYPE_ACCESSIBLE = TYPE_ALL & ~TYPE_DISCOVERY_BOUNCE;
88
89    protected boolean mIsOpen;
90
91    public AbstractFloatingView(Context context, AttributeSet attrs) {
92        super(context, attrs);
93    }
94
95    public AbstractFloatingView(Context context, AttributeSet attrs, int defStyleAttr) {
96        super(context, attrs, defStyleAttr);
97    }
98
99    /**
100     * We need to handle touch events to prevent them from falling through to the workspace below.
101     */
102    @SuppressLint("ClickableViewAccessibility")
103    @Override
104    public boolean onTouchEvent(MotionEvent ev) {
105        return true;
106    }
107
108    public final void close(boolean animate) {
109        animate &= !Utilities.isPowerSaverPreventingAnimation(getContext());
110        if (mIsOpen) {
111            BaseActivity.fromContext(getContext()).getUserEventDispatcher()
112                    .resetElapsedContainerMillis("container closed");
113        }
114        handleClose(animate);
115        mIsOpen = false;
116    }
117
118    protected abstract void handleClose(boolean animate);
119
120    public abstract void logActionCommand(int command);
121
122    public final boolean isOpen() {
123        return mIsOpen;
124    }
125
126    protected void onWidgetsBound() {
127    }
128
129    protected abstract boolean isOfType(@FloatingViewType int type);
130
131    /** @return Whether the back is consumed. If false, Launcher will handle the back as well. */
132    public boolean onBackPressed() {
133        logActionCommand(Action.Command.BACK);
134        close(true);
135        return true;
136    }
137
138    @Override
139    public boolean onControllerTouchEvent(MotionEvent ev) {
140        return false;
141    }
142
143    protected void announceAccessibilityChanges() {
144        Pair<View, String> targetInfo = getAccessibilityTarget();
145        if (targetInfo == null || !isAccessibilityEnabled(getContext())) {
146            return;
147        }
148        sendCustomAccessibilityEvent(
149                targetInfo.first, TYPE_WINDOW_STATE_CHANGED, targetInfo.second);
150
151        if (mIsOpen) {
152            sendAccessibilityEvent(TYPE_VIEW_FOCUSED);
153        }
154        BaseDraggingActivity.fromContext(getContext()).getDragLayer()
155                .sendAccessibilityEvent(TYPE_WINDOW_CONTENT_CHANGED);
156    }
157
158    protected Pair<View, String> getAccessibilityTarget() {
159        return null;
160    }
161
162    protected static <T extends AbstractFloatingView> T getOpenView(
163            BaseDraggingActivity activity, @FloatingViewType int type) {
164        BaseDragLayer dragLayer = activity.getDragLayer();
165        // Iterate in reverse order. AbstractFloatingView is added later to the dragLayer,
166        // and will be one of the last views.
167        for (int i = dragLayer.getChildCount() - 1; i >= 0; i--) {
168            View child = dragLayer.getChildAt(i);
169            if (child instanceof AbstractFloatingView) {
170                AbstractFloatingView view = (AbstractFloatingView) child;
171                if (view.isOfType(type) && view.isOpen()) {
172                    return (T) view;
173                }
174            }
175        }
176        return null;
177    }
178
179    public static void closeOpenContainer(BaseDraggingActivity activity,
180            @FloatingViewType int type) {
181        AbstractFloatingView view = getOpenView(activity, type);
182        if (view != null) {
183            view.close(true);
184        }
185    }
186
187    public static void closeOpenViews(BaseDraggingActivity activity, boolean animate,
188            @FloatingViewType int type) {
189        BaseDragLayer dragLayer = activity.getDragLayer();
190        // Iterate in reverse order. AbstractFloatingView is added later to the dragLayer,
191        // and will be one of the last views.
192        for (int i = dragLayer.getChildCount() - 1; i >= 0; i--) {
193            View child = dragLayer.getChildAt(i);
194            if (child instanceof AbstractFloatingView) {
195                AbstractFloatingView abs = (AbstractFloatingView) child;
196                if (abs.isOfType(type)) {
197                    abs.close(animate);
198                }
199            }
200        }
201    }
202
203    public static void closeAllOpenViews(BaseDraggingActivity activity, boolean animate) {
204        closeOpenViews(activity, animate, TYPE_ALL);
205        activity.finishAutoCancelActionMode();
206    }
207
208    public static void closeAllOpenViews(BaseDraggingActivity activity) {
209        closeAllOpenViews(activity, true);
210    }
211
212    public static AbstractFloatingView getTopOpenView(BaseDraggingActivity activity) {
213        return getTopOpenViewWithType(activity, TYPE_ALL);
214    }
215
216    public static AbstractFloatingView getTopOpenViewWithType(BaseDraggingActivity activity,
217            @FloatingViewType int type) {
218        return getOpenView(activity, type);
219    }
220}
221