OSDN Git Service

[automerger] DO NOT MERGE Fix build with SDK 24-like implementation of constructor...
[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 subtle/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     private boolean mOverlayWithAppContent = false;
86
87     private View mCaption;
88     private View mContent;
89     private View mMaximize;
90     private View mClose;
91
92     // Fields for detecting drag events.
93     private int mTouchDownX;
94     private int mTouchDownY;
95     private boolean mCheckForDragging;
96     private int mDragSlop;
97
98     // Fields for detecting and intercepting click events on close/maximize.
99     private ArrayList<View> mTouchDispatchList = new ArrayList<>(2);
100     // We use the gesture detector to detect clicks on close/maximize buttons and to be consistent
101     // with existing click detection.
102     private GestureDetector mGestureDetector;
103     private final Rect mCloseRect = new Rect();
104     private final Rect mMaximizeRect = new Rect();
105     private View mClickTarget;
106
107     public DecorCaptionView(Context context) {
108         super(context);
109         init(context);
110     }
111
112     public DecorCaptionView(Context context, AttributeSet attrs) {
113         super(context, attrs);
114         init(context);
115     }
116
117     public DecorCaptionView(Context context, AttributeSet attrs, int defStyle) {
118         super(context, attrs, defStyle);
119         init(context);
120     }
121
122     private void init(Context context) {
123         mDragSlop = ViewConfiguration.get(context).getScaledTouchSlop();
124         mGestureDetector = new GestureDetector(context, this);
125     }
126
127     @Override
128     protected void onFinishInflate() {
129         super.onFinishInflate();
130         mCaption = getChildAt(0);
131     }
132
133     public void setPhoneWindow(PhoneWindow owner, boolean show) {
134         mOwner = owner;
135         mShow = show;
136         mOverlayWithAppContent = owner.isOverlayWithDecorCaptionEnabled();
137         if (mOverlayWithAppContent) {
138             // The caption is covering the content, so we make its background transparent to make
139             // the content visible.
140             mCaption.setBackgroundColor(Color.TRANSPARENT);
141         }
142         updateCaptionVisibility();
143         // By changing the outline provider to BOUNDS, the window can remove its
144         // background without removing the shadow.
145         mOwner.getDecorView().setOutlineProvider(ViewOutlineProvider.BOUNDS);
146         mMaximize = findViewById(R.id.maximize_window);
147         mClose = findViewById(R.id.close_window);
148     }
149
150     @Override
151     public boolean onInterceptTouchEvent(MotionEvent ev) {
152         // If the user starts touch on the maximize/close buttons, we immediately intercept, so
153         // that these buttons are always clickable.
154         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
155             final int x = (int) ev.getX();
156             final int y = (int) ev.getY();
157             if (mMaximizeRect.contains(x, y)) {
158                 mClickTarget = mMaximize;
159             }
160             if (mCloseRect.contains(x, y)) {
161                 mClickTarget = mClose;
162             }
163         }
164         return mClickTarget != null;
165     }
166
167     @Override
168     public boolean onTouchEvent(MotionEvent event) {
169         if (mClickTarget != null) {
170             mGestureDetector.onTouchEvent(event);
171             final int action = event.getAction();
172             if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
173                 mClickTarget = null;
174             }
175             return true;
176         }
177         return false;
178     }
179
180     @Override
181     public boolean onTouch(View v, MotionEvent e) {
182         // Note: There are no mixed events. When a new device gets used (e.g. 1. Mouse, 2. touch)
183         // the old input device events get cancelled first. So no need to remember the kind of
184         // input device we are listening to.
185         final int x = (int) e.getX();
186         final int y = (int) e.getY();
187         final boolean fromMouse = e.getToolType(e.getActionIndex()) == MotionEvent.TOOL_TYPE_MOUSE;
188         final boolean primaryButton = (e.getButtonState() & MotionEvent.BUTTON_PRIMARY) != 0;
189         switch (e.getActionMasked()) {
190             case MotionEvent.ACTION_DOWN:
191                 if (!mShow) {
192                     // When there is no caption we should not react to anything.
193                     return false;
194                 }
195                 // Checking for a drag action is started if we aren't dragging already and the
196                 // starting event is either a left mouse button or any other input device.
197                 if (!fromMouse || primaryButton) {
198                     mCheckForDragging = true;
199                     mTouchDownX = x;
200                     mTouchDownY = y;
201                 }
202                 break;
203
204             case MotionEvent.ACTION_MOVE:
205                 if (!mDragging && mCheckForDragging && (fromMouse || passedSlop(x, y))) {
206                     mCheckForDragging = false;
207                     mDragging = true;
208                     startMovingTask(e.getRawX(), e.getRawY());
209                     // After the above call the framework will take over the input.
210                     // This handler will receive ACTION_CANCEL soon (possible after a few spurious
211                     // ACTION_MOVE events which are safe to ignore).
212                 }
213                 break;
214
215             case MotionEvent.ACTION_UP:
216             case MotionEvent.ACTION_CANCEL:
217                 if (!mDragging) {
218                     break;
219                 }
220                 // Abort the ongoing dragging.
221                 mDragging = false;
222                 return !mCheckForDragging;
223         }
224         return mDragging || mCheckForDragging;
225     }
226
227     @Override
228     public ArrayList<View> buildTouchDispatchChildList() {
229         mTouchDispatchList.ensureCapacity(3);
230         if (mCaption != null) {
231             mTouchDispatchList.add(mCaption);
232         }
233         if (mContent != null) {
234             mTouchDispatchList.add(mContent);
235         }
236         return mTouchDispatchList;
237     }
238
239     @Override
240     public boolean shouldDelayChildPressedState() {
241         return false;
242     }
243
244     private boolean passedSlop(int x, int y) {
245         return Math.abs(x - mTouchDownX) > mDragSlop || Math.abs(y - mTouchDownY) > mDragSlop;
246     }
247
248     /**
249      * The phone window configuration has changed and the caption needs to be updated.
250      * @param show True if the caption should be shown.
251      */
252     public void onConfigurationChanged(boolean show) {
253         mShow = show;
254         updateCaptionVisibility();
255     }
256
257     @Override
258     public void addView(View child, int index, ViewGroup.LayoutParams params) {
259         if (!(params instanceof MarginLayoutParams)) {
260             throw new IllegalArgumentException(
261                     "params " + params + " must subclass MarginLayoutParams");
262         }
263         // Make sure that we never get more then one client area in our view.
264         if (index >= 2 || getChildCount() >= 2) {
265             throw new IllegalStateException("DecorCaptionView can only handle 1 client view");
266         }
267         // To support the overlaying content in the caption, we need to put the content view as the
268         // first child to get the right Z-Ordering.
269         super.addView(child, 0, params);
270         mContent = child;
271     }
272
273     @Override
274     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
275         final int captionHeight;
276         if (mCaption.getVisibility() != View.GONE) {
277             measureChildWithMargins(mCaption, widthMeasureSpec, 0, heightMeasureSpec, 0);
278             captionHeight = mCaption.getMeasuredHeight();
279         } else {
280             captionHeight = 0;
281         }
282         if (mContent != null) {
283             if (mOverlayWithAppContent) {
284                 measureChildWithMargins(mContent, widthMeasureSpec, 0, heightMeasureSpec, 0);
285             } else {
286                 measureChildWithMargins(mContent, widthMeasureSpec, 0, heightMeasureSpec,
287                         captionHeight);
288             }
289         }
290
291         setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
292                 MeasureSpec.getSize(heightMeasureSpec));
293     }
294
295     @Override
296     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
297         final int captionHeight;
298         if (mCaption.getVisibility() != View.GONE) {
299             mCaption.layout(0, 0, mCaption.getMeasuredWidth(), mCaption.getMeasuredHeight());
300             captionHeight = mCaption.getBottom() - mCaption.getTop();
301             mMaximize.getHitRect(mMaximizeRect);
302             mClose.getHitRect(mCloseRect);
303         } else {
304             captionHeight = 0;
305             mMaximizeRect.setEmpty();
306             mCloseRect.setEmpty();
307         }
308
309         if (mContent != null) {
310             if (mOverlayWithAppContent) {
311                 mContent.layout(0, 0, mContent.getMeasuredWidth(), mContent.getMeasuredHeight());
312             } else {
313                 mContent.layout(0, captionHeight, mContent.getMeasuredWidth(),
314                         captionHeight + mContent.getMeasuredHeight());
315             }
316         }
317
318         // This assumes that the caption bar is at the top.
319         mOwner.notifyRestrictedCaptionAreaCallback(mMaximize.getLeft(), mMaximize.getTop(),
320                 mClose.getRight(), mClose.getBottom());
321     }
322     /**
323      * Determine if the workspace is entirely covered by the window.
324      * @return Returns true when the window is filling the entire screen/workspace.
325      **/
326     private boolean isFillingScreen() {
327         return (0 != ((getWindowSystemUiVisibility() | getSystemUiVisibility()) &
328                 (View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
329                         View.SYSTEM_UI_FLAG_IMMERSIVE | View.SYSTEM_UI_FLAG_LOW_PROFILE)));
330     }
331
332     /**
333      * Updates the visibility of the caption.
334      **/
335     private void updateCaptionVisibility() {
336         // Don't show the caption if the window has e.g. entered full screen.
337         boolean invisible = isFillingScreen() || !mShow;
338         mCaption.setVisibility(invisible ? GONE : VISIBLE);
339         mCaption.setOnTouchListener(this);
340     }
341
342     /**
343      * Maximize the window by moving it to the maximized workspace stack.
344      **/
345     private void maximizeWindow() {
346         Window.WindowControllerCallback callback = mOwner.getWindowControllerCallback();
347         if (callback != null) {
348             try {
349                 callback.exitFreeformMode();
350             } catch (RemoteException ex) {
351                 Log.e(TAG, "Cannot change task workspace.");
352             }
353         }
354     }
355
356     public boolean isCaptionShowing() {
357         return mShow;
358     }
359
360     public int getCaptionHeight() {
361         return (mCaption != null) ? mCaption.getHeight() : 0;
362     }
363
364     public void removeContentView() {
365         if (mContent != null) {
366             removeView(mContent);
367             mContent = null;
368         }
369     }
370
371     public View getCaption() {
372         return mCaption;
373     }
374
375     @Override
376     public LayoutParams generateLayoutParams(AttributeSet attrs) {
377         return new MarginLayoutParams(getContext(), attrs);
378     }
379
380     @Override
381     protected LayoutParams generateDefaultLayoutParams() {
382         return new MarginLayoutParams(MarginLayoutParams.MATCH_PARENT,
383                 MarginLayoutParams.MATCH_PARENT);
384     }
385
386     @Override
387     protected LayoutParams generateLayoutParams(LayoutParams p) {
388         return new MarginLayoutParams(p);
389     }
390
391     @Override
392     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
393         return p instanceof MarginLayoutParams;
394     }
395
396     @Override
397     public boolean onDown(MotionEvent e) {
398         return false;
399     }
400
401     @Override
402     public void onShowPress(MotionEvent e) {
403
404     }
405
406     @Override
407     public boolean onSingleTapUp(MotionEvent e) {
408         if (mClickTarget == mMaximize) {
409             maximizeWindow();
410         } else if (mClickTarget == mClose) {
411             mOwner.dispatchOnWindowDismissed(
412                     true /*finishTask*/, false /*suppressWindowTransition*/);
413         }
414         return true;
415     }
416
417     @Override
418     public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
419         return false;
420     }
421
422     @Override
423     public void onLongPress(MotionEvent e) {
424
425     }
426
427     @Override
428     public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
429         return false;
430     }
431 }