OSDN Git Service

[automerger] RESTRICT AUTOMERGE: Prevent reporting fake package name - framework...
[android-x86/frameworks-base.git] / core / java / com / android / internal / widget / DecorCaptionView.java
1 /*
2  * Copyright (C) 2015 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.internal.widget;
18
19 import android.content.Context;
20 import android.graphics.Color;
21 import android.graphics.Rect;
22 import android.os.RemoteException;
23 import android.util.AttributeSet;
24 import android.util.Log;
25 import android.view.GestureDetector;
26 import android.view.MotionEvent;
27 import android.view.View;
28 import android.view.ViewConfiguration;
29 import android.view.ViewGroup;
30 import android.view.ViewOutlineProvider;
31 import android.view.Window;
32
33 import com.android.internal.R;
34 import com.android.internal.policy.PhoneWindow;
35
36 import java.util.ArrayList;
37
38 /**
39  * This class represents the special screen elements to control a window on freeform
40  * environment.
41  * As such this class handles the following things:
42  * <ul>
43  * <li>The caption, containing the system buttons like maximize, close and such as well as
44  * allowing the user to drag the window around.</li>
45  * </ul>
46  * After creating the view, the function {@link #setPhoneWindow} needs to be called to make
47  * the connection to it's owning PhoneWindow.
48  * Note: At this time the application can change various attributes of the DecorView which
49  * will break things (in settle/unexpected ways):
50  * <ul>
51  * <li>setOutlineProvider</li>
52  * <li>setSurfaceFormat</li>
53  * <li>..</li>
54  * </ul>
55  *
56  * Although this ViewGroup has only two direct sub-Views, its behavior is more complex due to
57  * overlaying caption on the content and drawing.
58  *
59  * First, no matter where the content View gets added, it will always be the first child and the
60  * caption will be the second. This way the caption will always be drawn on top of the content when
61  * overlaying is enabled.
62  *
63  * Second, the touch dispatch is customized to handle overlaying. This is what happens when touch
64  * is dispatched on the caption area while overlaying it on content:
65  * <ul>
66  * <li>DecorCaptionView.onInterceptTouchEvent() will try intercepting the touch events if the
67  * down action is performed on top close or maximize buttons; the reason for that is we want these
68  * buttons to always work.</li>
69  * <li>The content View will receive the touch event. Mind that content is actually underneath the
70  * caption, so we need to introduce our own dispatch ordering. We achieve this by overriding
71  * {@link #buildTouchDispatchChildList()}.</li>
72  * <li>If the touch event is not consumed by the content View, it will go to the caption View
73  * and the dragging logic will be executed.</li>
74  * </ul>
75  */
76 public class DecorCaptionView extends ViewGroup implements View.OnTouchListener,
77         GestureDetector.OnGestureListener {
78     private final static String TAG = "DecorCaptionView";
79     private PhoneWindow mOwner = null;
80     private boolean mShow = false;
81
82     // True if the window is being dragged.
83     private boolean mDragging = false;
84
85     // True when the left mouse button got released while dragging.
86     private boolean mLeftMouseButtonReleased;
87
88     private boolean mOverlayWithAppContent = false;
89
90     private View mCaption;
91     private View mContent;
92     private View mMaximize;
93     private View mClose;
94
95     // Fields for detecting drag events.
96     private int mTouchDownX;
97     private int mTouchDownY;
98     private boolean mCheckForDragging;
99     private int mDragSlop;
100
101     // Fields for detecting and intercepting click events on close/maximize.
102     private ArrayList<View> mTouchDispatchList = new ArrayList<>(2);
103     // We use the gesture detector to detect clicks on close/maximize buttons and to be consistent
104     // with existing click detection.
105     private GestureDetector mGestureDetector;
106     private final Rect mCloseRect = new Rect();
107     private final Rect mMaximizeRect = new Rect();
108     private View mClickTarget;
109
110     public DecorCaptionView(Context context) {
111         super(context);
112         init(context);
113     }
114
115     public DecorCaptionView(Context context, AttributeSet attrs) {
116         super(context, attrs);
117         init(context);
118     }
119
120     public DecorCaptionView(Context context, AttributeSet attrs, int defStyle) {
121         super(context, attrs, defStyle);
122         init(context);
123     }
124
125     private void init(Context context) {
126         mDragSlop = ViewConfiguration.get(context).getScaledTouchSlop();
127         mGestureDetector = new GestureDetector(context, this);
128     }
129
130     @Override
131     protected void onFinishInflate() {
132         super.onFinishInflate();
133         mCaption = getChildAt(0);
134     }
135
136     public void setPhoneWindow(PhoneWindow owner, boolean show) {
137         mOwner = owner;
138         mShow = show;
139         mOverlayWithAppContent = owner.isOverlayWithDecorCaptionEnabled();
140         if (mOverlayWithAppContent) {
141             // The caption is covering the content, so we make its background transparent to make
142             // the content visible.
143             mCaption.setBackgroundColor(Color.TRANSPARENT);
144         }
145         updateCaptionVisibility();
146         // By changing the outline provider to BOUNDS, the window can remove its
147         // background without removing the shadow.
148         mOwner.getDecorView().setOutlineProvider(ViewOutlineProvider.BOUNDS);
149         mMaximize = findViewById(R.id.maximize_window);
150         mClose = findViewById(R.id.close_window);
151     }
152
153     @Override
154     public boolean onInterceptTouchEvent(MotionEvent ev) {
155         // If the user starts touch on the maximize/close buttons, we immediately intercept, so
156         // that these buttons are always clickable.
157         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
158             final int x = (int) ev.getX();
159             final int y = (int) ev.getY();
160             if (mMaximizeRect.contains(x, y)) {
161                 mClickTarget = mMaximize;
162             }
163             if (mCloseRect.contains(x, y)) {
164                 mClickTarget = mClose;
165             }
166         }
167         return mClickTarget != null;
168     }
169
170     @Override
171     public boolean onTouchEvent(MotionEvent event) {
172         if (mClickTarget != null) {
173             mGestureDetector.onTouchEvent(event);
174             final int action = event.getAction();
175             if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
176                 mClickTarget = null;
177             }
178             return true;
179         }
180         return false;
181     }
182
183     @Override
184     public boolean onTouch(View v, MotionEvent e) {
185         // Note: There are no mixed events. When a new device gets used (e.g. 1. Mouse, 2. touch)
186         // the old input device events get cancelled first. So no need to remember the kind of
187         // input device we are listening to.
188         final int x = (int) e.getX();
189         final int y = (int) e.getY();
190         switch (e.getActionMasked()) {
191             case MotionEvent.ACTION_DOWN:
192                 if (!mShow) {
193                     // When there is no caption we should not react to anything.
194                     return false;
195                 }
196                 // Checking for a drag action is started if we aren't dragging already and the
197                 // starting event is either a left mouse button or any other input device.
198                 if (((e.getToolType(e.getActionIndex()) != MotionEvent.TOOL_TYPE_MOUSE ||
199                         (e.getButtonState() & MotionEvent.BUTTON_PRIMARY) != 0))) {
200                     mCheckForDragging = true;
201                     mTouchDownX = x;
202                     mTouchDownY = y;
203                 }
204                 break;
205
206             case MotionEvent.ACTION_MOVE:
207                 if (!mDragging && mCheckForDragging && passedSlop(x, y)) {
208                     mCheckForDragging = false;
209                     mDragging = true;
210                     mLeftMouseButtonReleased = false;
211                     startMovingTask(e.getRawX(), e.getRawY());
212                 } else if (mDragging && !mLeftMouseButtonReleased) {
213                     if (e.getToolType(e.getActionIndex()) == MotionEvent.TOOL_TYPE_MOUSE &&
214                             (e.getButtonState() & MotionEvent.BUTTON_PRIMARY) == 0) {
215                         // There is no separate mouse button up call and if the user mixes mouse
216                         // button drag actions, we stop dragging once he releases the button.
217                         mLeftMouseButtonReleased = true;
218                         break;
219                     }
220                 }
221                 break;
222
223             case MotionEvent.ACTION_UP:
224             case MotionEvent.ACTION_CANCEL:
225                 if (!mDragging) {
226                     break;
227                 }
228                 // Abort the ongoing dragging.
229                 mDragging = false;
230                 return !mCheckForDragging;
231         }
232         return mDragging || mCheckForDragging;
233     }
234
235     @Override
236     public ArrayList<View> buildTouchDispatchChildList() {
237         mTouchDispatchList.ensureCapacity(3);
238         if (mCaption != null) {
239             mTouchDispatchList.add(mCaption);
240         }
241         if (mContent != null) {
242             mTouchDispatchList.add(mContent);
243         }
244         return mTouchDispatchList;
245     }
246
247     @Override
248     public boolean shouldDelayChildPressedState() {
249         return false;
250     }
251
252     private boolean passedSlop(int x, int y) {
253         return Math.abs(x - mTouchDownX) > mDragSlop || Math.abs(y - mTouchDownY) > mDragSlop;
254     }
255
256     /**
257      * The phone window configuration has changed and the caption needs to be updated.
258      * @param show True if the caption should be shown.
259      */
260     public void onConfigurationChanged(boolean show) {
261         mShow = show;
262         updateCaptionVisibility();
263     }
264
265     @Override
266     public void addView(View child, int index, ViewGroup.LayoutParams params) {
267         if (!(params instanceof MarginLayoutParams)) {
268             throw new IllegalArgumentException(
269                     "params " + params + " must subclass MarginLayoutParams");
270         }
271         // Make sure that we never get more then one client area in our view.
272         if (index >= 2 || getChildCount() >= 2) {
273             throw new IllegalStateException("DecorCaptionView can only handle 1 client view");
274         }
275         // To support the overlaying content in the caption, we need to put the content view as the
276         // first child to get the right Z-Ordering.
277         super.addView(child, 0, params);
278         mContent = child;
279     }
280
281     @Override
282     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
283         final int captionHeight;
284         if (mCaption.getVisibility() != View.GONE) {
285             measureChildWithMargins(mCaption, widthMeasureSpec, 0, heightMeasureSpec, 0);
286             captionHeight = mCaption.getMeasuredHeight();
287         } else {
288             captionHeight = 0;
289         }
290         if (mContent != null) {
291             if (mOverlayWithAppContent) {
292                 measureChildWithMargins(mContent, widthMeasureSpec, 0, heightMeasureSpec, 0);
293             } else {
294                 measureChildWithMargins(mContent, widthMeasureSpec, 0, heightMeasureSpec,
295                         captionHeight);
296             }
297         }
298
299         setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
300                 MeasureSpec.getSize(heightMeasureSpec));
301     }
302
303     @Override
304     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
305         final int captionHeight;
306         if (mCaption.getVisibility() != View.GONE) {
307             mCaption.layout(0, 0, mCaption.getMeasuredWidth(), mCaption.getMeasuredHeight());
308             captionHeight = mCaption.getBottom() - mCaption.getTop();
309             mMaximize.getHitRect(mMaximizeRect);
310             mClose.getHitRect(mCloseRect);
311         } else {
312             captionHeight = 0;
313             mMaximizeRect.setEmpty();
314             mCloseRect.setEmpty();
315         }
316
317         if (mContent != null) {
318             if (mOverlayWithAppContent) {
319                 mContent.layout(0, 0, mContent.getMeasuredWidth(), mContent.getMeasuredHeight());
320             } else {
321                 mContent.layout(0, captionHeight, mContent.getMeasuredWidth(),
322                         captionHeight + mContent.getMeasuredHeight());
323             }
324         }
325
326         // This assumes that the caption bar is at the top.
327         mOwner.notifyRestrictedCaptionAreaCallback(mMaximize.getLeft(), mMaximize.getTop(),
328                 mClose.getRight(), mClose.getBottom());
329     }
330     /**
331      * Determine if the workspace is entirely covered by the window.
332      * @return Returns true when the window is filling the entire screen/workspace.
333      **/
334     private boolean isFillingScreen() {
335         return (0 != ((getWindowSystemUiVisibility() | getSystemUiVisibility()) &
336                 (View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
337                         View.SYSTEM_UI_FLAG_IMMERSIVE | View.SYSTEM_UI_FLAG_LOW_PROFILE)));
338     }
339
340     /**
341      * Updates the visibility of the caption.
342      **/
343     private void updateCaptionVisibility() {
344         // Don't show the caption if the window has e.g. entered full screen.
345         boolean invisible = isFillingScreen() || !mShow;
346         mCaption.setVisibility(invisible ? GONE : VISIBLE);
347         mCaption.setOnTouchListener(this);
348     }
349
350     /**
351      * Maximize the window by moving it to the maximized workspace stack.
352      **/
353     private void maximizeWindow() {
354         Window.WindowControllerCallback callback = mOwner.getWindowControllerCallback();
355         if (callback != null) {
356             try {
357                 callback.exitFreeformMode();
358             } catch (RemoteException ex) {
359                 Log.e(TAG, "Cannot change task workspace.");
360             }
361         }
362     }
363
364     public boolean isCaptionShowing() {
365         return mShow;
366     }
367
368     public int getCaptionHeight() {
369         return (mCaption != null) ? mCaption.getHeight() : 0;
370     }
371
372     public void removeContentView() {
373         if (mContent != null) {
374             removeView(mContent);
375             mContent = null;
376         }
377     }
378
379     public View getCaption() {
380         return mCaption;
381     }
382
383     @Override
384     public LayoutParams generateLayoutParams(AttributeSet attrs) {
385         return new MarginLayoutParams(getContext(), attrs);
386     }
387
388     @Override
389     protected LayoutParams generateDefaultLayoutParams() {
390         return new MarginLayoutParams(MarginLayoutParams.MATCH_PARENT,
391                 MarginLayoutParams.MATCH_PARENT);
392     }
393
394     @Override
395     protected LayoutParams generateLayoutParams(LayoutParams p) {
396         return new MarginLayoutParams(p);
397     }
398
399     @Override
400     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
401         return p instanceof MarginLayoutParams;
402     }
403
404     @Override
405     public boolean onDown(MotionEvent e) {
406         return false;
407     }
408
409     @Override
410     public void onShowPress(MotionEvent e) {
411
412     }
413
414     @Override
415     public boolean onSingleTapUp(MotionEvent e) {
416         if (mClickTarget == mMaximize) {
417             maximizeWindow();
418         } else if (mClickTarget == mClose) {
419             mOwner.dispatchOnWindowDismissed(true /*finishTask*/);
420         }
421         return true;
422     }
423
424     @Override
425     public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
426         return false;
427     }
428
429     @Override
430     public void onLongPress(MotionEvent e) {
431
432     }
433
434     @Override
435     public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
436         return false;
437     }
438 }