blob: 16e82961670409422251f522fe0aee8d70a3e534 [file] [log] [blame]
Stefan Kuhne61b47bb2015-07-28 14:04:25 -07001/*
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
17package com.android.internal.widget;
18
Wale Ogunwale3797c222015-10-27 14:21:58 -070019import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
20
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070021import android.content.Context;
22import android.os.RemoteException;
23import android.util.AttributeSet;
Skuhne81c524a2015-08-12 13:34:14 -070024import android.view.MotionEvent;
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070025import android.view.View;
26import android.view.ViewGroup;
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -070027import android.view.ViewOutlineProvider;
28import android.view.Window;
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070029import android.util.Log;
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070030
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070031import com.android.internal.R;
32import com.android.internal.policy.PhoneWindow;
33
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070034/**
Wale Ogunwale62a91d62015-11-18 11:44:10 -080035 * This class represents the special screen elements to control a window on freeform
36 * environment.
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070037 * As such this class handles the following things:
38 * <ul>
39 * <li>The caption, containing the system buttons like maximize, close and such as well as
40 * allowing the user to drag the window around.</li>
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070041 * After creating the view, the function
Wale Ogunwalef3a62fb2015-10-14 17:07:29 -070042 * {@link #setPhoneWindow} needs to be called to make
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070043 * the connection to it's owning PhoneWindow.
44 * Note: At this time the application can change various attributes of the DecorView which
45 * will break things (in settle/unexpected ways):
46 * <ul>
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070047 * <li>setOutlineProvider</li>
48 * <li>setSurfaceFormat</li>
49 * <li>..</li>
50 * </ul>
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070051 */
Filip Gruszczynski63250652015-11-18 14:43:01 -080052public class DecorCaptionView extends ViewGroup
Wale Ogunwalebf9eefc2015-11-17 14:47:52 -080053 implements View.OnClickListener, View.OnTouchListener {
Wale Ogunwale62a91d62015-11-18 11:44:10 -080054 private final static String TAG = "DecorCaptionView";
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070055 private PhoneWindow mOwner = null;
Wale Ogunwale62a91d62015-11-18 11:44:10 -080056 private boolean mShow = false;
Skuhne81c524a2015-08-12 13:34:14 -070057
58 // True if the window is being dragged.
59 private boolean mDragging = false;
60
Skuhne81c524a2015-08-12 13:34:14 -070061 // True when the left mouse button got released while dragging.
62 private boolean mLeftMouseButtonReleased;
63
Filip Gruszczynski63250652015-11-18 14:43:01 -080064 private boolean mOverlayWithAppContent = false;
65
66 private View mCaption;
67 private View mContent;
68
Wale Ogunwale62a91d62015-11-18 11:44:10 -080069 public DecorCaptionView(Context context) {
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070070 super(context);
71 }
72
Wale Ogunwale62a91d62015-11-18 11:44:10 -080073 public DecorCaptionView(Context context, AttributeSet attrs) {
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070074 super(context, attrs);
75 }
76
Wale Ogunwale62a91d62015-11-18 11:44:10 -080077 public DecorCaptionView(Context context, AttributeSet attrs, int defStyle) {
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070078 super(context, attrs, defStyle);
79 }
80
Filip Gruszczynski63250652015-11-18 14:43:01 -080081 @Override
82 protected void onFinishInflate() {
83 super.onFinishInflate();
84 mCaption = getChildAt(0);
85 }
86
Wale Ogunwale62a91d62015-11-18 11:44:10 -080087 public void setPhoneWindow(PhoneWindow owner, boolean show) {
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070088 mOwner = owner;
Wale Ogunwale62a91d62015-11-18 11:44:10 -080089 mShow = show;
Filip Gruszczynski63250652015-11-18 14:43:01 -080090 mOverlayWithAppContent = owner.getOverlayDecorCaption();
Skuhnef7b882c2015-08-11 17:18:58 -070091 updateCaptionVisibility();
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070092 // By changing the outline provider to BOUNDS, the window can remove its
93 // background without removing the shadow.
94 mOwner.getDecorView().setOutlineProvider(ViewOutlineProvider.BOUNDS);
Skuhneb8160872015-09-22 09:51:39 -070095
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070096 findViewById(R.id.maximize_window).setOnClickListener(this);
97 findViewById(R.id.close_window).setOnClickListener(this);
98 }
99
Skuhne81c524a2015-08-12 13:34:14 -0700100 @Override
Chong Zhang509ea6b2015-09-30 14:09:52 -0700101 public boolean onTouch(View v, MotionEvent e) {
Skuhne81c524a2015-08-12 13:34:14 -0700102 // Note: There are no mixed events. When a new device gets used (e.g. 1. Mouse, 2. touch)
103 // the old input device events get cancelled first. So no need to remember the kind of
104 // input device we are listening to.
105 switch (e.getActionMasked()) {
106 case MotionEvent.ACTION_DOWN:
Wale Ogunwale62a91d62015-11-18 11:44:10 -0800107 if (!mShow) {
108 // When there is no caption we should not react to anything.
Skuhnea635a262015-08-26 15:45:58 -0700109 return false;
110 }
Skuhne81c524a2015-08-12 13:34:14 -0700111 // A drag action is started if we aren't dragging already and the starting event is
112 // either a left mouse button or any other input device.
113 if (!mDragging &&
114 (e.getToolType(e.getActionIndex()) != MotionEvent.TOOL_TYPE_MOUSE ||
115 (e.getButtonState() & MotionEvent.BUTTON_PRIMARY) != 0)) {
116 mDragging = true;
Skuhne81c524a2015-08-12 13:34:14 -0700117 mLeftMouseButtonReleased = false;
Chong Zhang8e89b312015-09-09 15:09:30 -0700118 startMovingTask(e.getRawX(), e.getRawY());
Skuhne81c524a2015-08-12 13:34:14 -0700119 }
120 break;
121
122 case MotionEvent.ACTION_MOVE:
123 if (mDragging && !mLeftMouseButtonReleased) {
124 if (e.getToolType(e.getActionIndex()) == MotionEvent.TOOL_TYPE_MOUSE &&
125 (e.getButtonState() & MotionEvent.BUTTON_PRIMARY) == 0) {
126 // There is no separate mouse button up call and if the user mixes mouse
127 // button drag actions, we stop dragging once he releases the button.
128 mLeftMouseButtonReleased = true;
129 break;
130 }
Skuhne81c524a2015-08-12 13:34:14 -0700131 }
132 break;
133
134 case MotionEvent.ACTION_UP:
Skuhne81c524a2015-08-12 13:34:14 -0700135 case MotionEvent.ACTION_CANCEL:
Skuhnea5a93ee2015-08-20 15:43:57 -0700136 if (!mDragging) {
137 break;
Skuhne81c524a2015-08-12 13:34:14 -0700138 }
Skuhnea5a93ee2015-08-20 15:43:57 -0700139 // Abort the ongoing dragging.
140 mDragging = false;
Skuhnea5a93ee2015-08-20 15:43:57 -0700141 return true;
Skuhne81c524a2015-08-12 13:34:14 -0700142 }
143 return mDragging;
144 }
145
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -0700146 /**
Wale Ogunwale62a91d62015-11-18 11:44:10 -0800147 * The phone window configuration has changed and the caption needs to be updated.
148 * @param show True if the caption should be shown.
Wale Ogunwale2b547c32015-11-18 10:33:22 -0800149 */
Wale Ogunwale62a91d62015-11-18 11:44:10 -0800150 public void onConfigurationChanged(boolean show) {
151 mShow = show;
Skuhnef7b882c2015-08-11 17:18:58 -0700152 updateCaptionVisibility();
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -0700153 }
154
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700155 @Override
156 public void onClick(View view) {
157 if (view.getId() == R.id.maximize_window) {
Stefan Kuhne1b420572015-08-07 10:50:19 -0700158 maximizeWindow();
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700159 } else if (view.getId() == R.id.close_window) {
Stefan Kuhne1b420572015-08-07 10:50:19 -0700160 mOwner.dispatchOnWindowDismissed(true /*finishTask*/);
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700161 }
162 }
163
164 @Override
Skuhnef7b882c2015-08-11 17:18:58 -0700165 public void addView(View child, int index, ViewGroup.LayoutParams params) {
Filip Gruszczynski63250652015-11-18 14:43:01 -0800166 if (!(params instanceof MarginLayoutParams)) {
167 throw new IllegalArgumentException(
168 "params " + params + " must subclass MarginLayoutParams");
169 }
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -0700170 // Make sure that we never get more then one client area in our view.
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700171 if (index >= 2 || getChildCount() >= 2) {
Wale Ogunwale62a91d62015-11-18 11:44:10 -0800172 throw new IllegalStateException("DecorCaptionView can only handle 1 client view");
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700173 }
Filip Gruszczynski63250652015-11-18 14:43:01 -0800174 // To support the overlaying content in the caption, we need to put the content view as the
175 // first child to get the right Z-Ordering.
176 super.addView(child, 0, params);
177 mContent = child;
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700178 }
179
Filip Gruszczynski63250652015-11-18 14:43:01 -0800180 @Override
181 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
182 final int captionHeight;
183 if (mCaption.getVisibility() != View.GONE) {
184 measureChildWithMargins(mCaption, widthMeasureSpec, 0, heightMeasureSpec, 0);
185 captionHeight = mCaption.getMeasuredHeight();
186 } else {
187 captionHeight = 0;
188 }
189 if (mContent != null) {
190 if (mOverlayWithAppContent) {
191 measureChildWithMargins(mContent, widthMeasureSpec, 0, heightMeasureSpec, 0);
192 } else {
193 measureChildWithMargins(mContent, widthMeasureSpec, 0, heightMeasureSpec,
194 captionHeight);
195 }
196 }
197
198 setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
199 MeasureSpec.getSize(heightMeasureSpec));
200 }
201
202 @Override
203 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
204 final int captionHeight;
205 if (mCaption.getVisibility() != View.GONE) {
206 mCaption.layout(0, 0, mCaption.getMeasuredWidth(), mCaption.getMeasuredHeight());
207 captionHeight = mCaption.getBottom() - mCaption.getTop();
208 } else {
209 captionHeight = 0;
210 }
211
212 if (mContent != null) {
213 if (mOverlayWithAppContent) {
214 mContent.layout(0, 0, mContent.getMeasuredWidth(), mContent.getMeasuredHeight());
215 } else {
216 mContent.layout(0, captionHeight, mContent.getMeasuredWidth(),
217 captionHeight + mContent.getMeasuredHeight());
218 }
219 }
220 }
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -0700221 /**
222 * Determine if the workspace is entirely covered by the window.
223 * @return Returns true when the window is filling the entire screen/workspace.
224 **/
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700225 private boolean isFillingScreen() {
226 return (0 != ((getWindowSystemUiVisibility() | getSystemUiVisibility()) &
227 (View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
228 View.SYSTEM_UI_FLAG_IMMERSIVE | View.SYSTEM_UI_FLAG_LOW_PROFILE)));
229 }
230
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -0700231 /**
Skuhnef7b882c2015-08-11 17:18:58 -0700232 * Updates the visibility of the caption.
233 **/
234 private void updateCaptionVisibility() {
Wale Ogunwale62a91d62015-11-18 11:44:10 -0800235 // Don't show the caption if the window has e.g. entered full screen.
236 boolean invisible = isFillingScreen() || !mShow;
Filip Gruszczynski63250652015-11-18 14:43:01 -0800237 mCaption.setVisibility(invisible ? GONE : VISIBLE);
238 mCaption.setOnTouchListener(this);
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700239 }
Stefan Kuhne1b420572015-08-07 10:50:19 -0700240
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -0700241 /**
242 * Maximize the window by moving it to the maximized workspace stack.
243 **/
Stefan Kuhne1b420572015-08-07 10:50:19 -0700244 private void maximizeWindow() {
Skuhnece2faa52015-08-11 10:36:38 -0700245 Window.WindowControllerCallback callback = mOwner.getWindowControllerCallback();
Stefan Kuhne1b420572015-08-07 10:50:19 -0700246 if (callback != null) {
247 try {
Wale Ogunwale3797c222015-10-27 14:21:58 -0700248 callback.changeWindowStack(FULLSCREEN_WORKSPACE_STACK_ID);
Stefan Kuhne1b420572015-08-07 10:50:19 -0700249 } catch (RemoteException ex) {
250 Log.e(TAG, "Cannot change task workspace.");
251 }
252 }
253 }
Wale Ogunwale8cc5a742015-11-17 15:41:05 -0800254
Wale Ogunwale62a91d62015-11-18 11:44:10 -0800255 public boolean isCaptionShowing() {
256 return mShow;
Wale Ogunwale8cc5a742015-11-17 15:41:05 -0800257 }
258
Wale Ogunwale62a91d62015-11-18 11:44:10 -0800259 public int getCaptionHeight() {
Filip Gruszczynski63250652015-11-18 14:43:01 -0800260 return (mCaption != null) ? mCaption.getHeight() : 0;
261 }
262
263 public void removeContentView() {
264 if (mContent != null) {
265 removeView(mContent);
266 mContent = null;
267 }
268 }
269
270 public View getCaption() {
271 return mCaption;
272 }
273
274 @Override
275 public LayoutParams generateLayoutParams(AttributeSet attrs) {
276 return new MarginLayoutParams(getContext(), attrs);
277 }
278
279 @Override
280 protected LayoutParams generateDefaultLayoutParams() {
281 return new MarginLayoutParams(MarginLayoutParams.MATCH_PARENT,
282 MarginLayoutParams.MATCH_PARENT);
283 }
284
285 @Override
286 protected LayoutParams generateLayoutParams(LayoutParams p) {
287 return new MarginLayoutParams(p);
288 }
289
290 @Override
291 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
292 return p instanceof MarginLayoutParams;
Wale Ogunwale8cc5a742015-11-17 15:41:05 -0800293 }
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700294}