blob: 33b8e0515ab07f1c33e7cdceb508b59fb6e895a7 [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;
Skuhnef7b882c2015-08-11 17:18:58 -070026import android.widget.LinearLayout;
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070027import android.view.ViewGroup;
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -070028import android.view.ViewOutlineProvider;
29import android.view.Window;
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070030import android.util.Log;
31import android.util.TypedValue;
32
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070033import com.android.internal.R;
Wale Ogunwalebf9eefc2015-11-17 14:47:52 -080034import com.android.internal.policy.DecorView;
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070035import com.android.internal.policy.PhoneWindow;
36
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070037/**
38 * This class represents the special screen elements to control a window on free form
39 * environment. All thse screen elements are added in the "non client area" which is the area of
40 * the window which is handled by the OS and not the application.
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 * <li>The shadow - which is changing dependent on the window focus.</li>
46 * <li>The border around the client area (if there is one).</li>
47 * <li>The resize handles which allow to resize the window.</li>
48 * </ul>
49 * After creating the view, the function
Wale Ogunwalef3a62fb2015-10-14 17:07:29 -070050 * {@link #setPhoneWindow} needs to be called to make
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070051 * the connection to it's owning PhoneWindow.
52 * Note: At this time the application can change various attributes of the DecorView which
53 * will break things (in settle/unexpected ways):
54 * <ul>
55 * <li>setElevation</li>
56 * <li>setOutlineProvider</li>
57 * <li>setSurfaceFormat</li>
58 * <li>..</li>
59 * </ul>
60 * This will be mitigated once b/22527834 will be addressed.
61 */
Chong Zhang509ea6b2015-09-30 14:09:52 -070062public class NonClientDecorView extends LinearLayout
Wale Ogunwalebf9eefc2015-11-17 14:47:52 -080063 implements View.OnClickListener, View.OnTouchListener {
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070064 private final static String TAG = "NonClientDecorView";
65 // The height of a window which has focus in DIP.
66 private final int DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP = 20;
67 // The height of a window which has not in DIP.
68 private final int DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP = 5;
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070069 private PhoneWindow mOwner = null;
Skuhne81c524a2015-08-12 13:34:14 -070070 private boolean mWindowHasShadow = false;
Wale Ogunwale8cc5a742015-11-17 15:41:05 -080071 private boolean mShowDecor = false;
Skuhne81c524a2015-08-12 13:34:14 -070072
73 // True if the window is being dragged.
74 private boolean mDragging = false;
75
Skuhne81c524a2015-08-12 13:34:14 -070076 // True when the left mouse button got released while dragging.
77 private boolean mLeftMouseButtonReleased;
78
Skuhnea5a93ee2015-08-20 15:43:57 -070079 // True if this window is resizable (which is currently only true when the decor is shown).
Chong Zhang8e89b312015-09-09 15:09:30 -070080 public boolean mVisible = false;
Skuhnea5a93ee2015-08-20 15:43:57 -070081
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070082 // The current focus state of the window for updating the window elevation.
Skuhne81c524a2015-08-12 13:34:14 -070083 private boolean mWindowHasFocus = true;
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070084
85 // Cludge to address b/22668382: Set the shadow size to the maximum so that the layer
86 // size calculation takes the shadow size into account. We set the elevation currently
87 // to max until the first layout command has been executed.
Skuhne81c524a2015-08-12 13:34:14 -070088 private boolean mAllowUpdateElevation = false;
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070089
90 public NonClientDecorView(Context context) {
91 super(context);
92 }
93
94 public NonClientDecorView(Context context, AttributeSet attrs) {
95 super(context, attrs);
96 }
97
98 public NonClientDecorView(Context context, AttributeSet attrs, int defStyle) {
99 super(context, attrs, defStyle);
100 }
101
Wale Ogunwalebf9eefc2015-11-17 14:47:52 -0800102 public void setPhoneWindow(PhoneWindow owner, boolean showDecor, boolean windowHasShadow) {
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700103 mOwner = owner;
104 mWindowHasShadow = windowHasShadow;
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -0700105 mShowDecor = showDecor;
Skuhnef7b882c2015-08-11 17:18:58 -0700106 updateCaptionVisibility();
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700107 if (mWindowHasShadow) {
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -0700108 initializeElevation();
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700109 }
110 // By changing the outline provider to BOUNDS, the window can remove its
111 // background without removing the shadow.
112 mOwner.getDecorView().setOutlineProvider(ViewOutlineProvider.BOUNDS);
Skuhneb8160872015-09-22 09:51:39 -0700113
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700114 findViewById(R.id.maximize_window).setOnClickListener(this);
115 findViewById(R.id.close_window).setOnClickListener(this);
116 }
117
Skuhne81c524a2015-08-12 13:34:14 -0700118 @Override
Chong Zhang509ea6b2015-09-30 14:09:52 -0700119 public boolean onTouch(View v, MotionEvent e) {
Skuhne81c524a2015-08-12 13:34:14 -0700120 // Note: There are no mixed events. When a new device gets used (e.g. 1. Mouse, 2. touch)
121 // the old input device events get cancelled first. So no need to remember the kind of
122 // input device we are listening to.
123 switch (e.getActionMasked()) {
124 case MotionEvent.ACTION_DOWN:
Skuhnea635a262015-08-26 15:45:58 -0700125 if (!mShowDecor) {
126 // When there is no decor we should not react to anything.
127 return false;
128 }
Skuhne81c524a2015-08-12 13:34:14 -0700129 // A drag action is started if we aren't dragging already and the starting event is
130 // either a left mouse button or any other input device.
131 if (!mDragging &&
132 (e.getToolType(e.getActionIndex()) != MotionEvent.TOOL_TYPE_MOUSE ||
133 (e.getButtonState() & MotionEvent.BUTTON_PRIMARY) != 0)) {
134 mDragging = true;
Skuhne81c524a2015-08-12 13:34:14 -0700135 mLeftMouseButtonReleased = false;
Chong Zhang8e89b312015-09-09 15:09:30 -0700136 startMovingTask(e.getRawX(), e.getRawY());
Skuhne81c524a2015-08-12 13:34:14 -0700137 }
138 break;
139
140 case MotionEvent.ACTION_MOVE:
141 if (mDragging && !mLeftMouseButtonReleased) {
142 if (e.getToolType(e.getActionIndex()) == MotionEvent.TOOL_TYPE_MOUSE &&
143 (e.getButtonState() & MotionEvent.BUTTON_PRIMARY) == 0) {
144 // There is no separate mouse button up call and if the user mixes mouse
145 // button drag actions, we stop dragging once he releases the button.
146 mLeftMouseButtonReleased = true;
147 break;
148 }
Skuhne81c524a2015-08-12 13:34:14 -0700149 }
150 break;
151
152 case MotionEvent.ACTION_UP:
Skuhne81c524a2015-08-12 13:34:14 -0700153 case MotionEvent.ACTION_CANCEL:
Skuhnea5a93ee2015-08-20 15:43:57 -0700154 if (!mDragging) {
155 break;
Skuhne81c524a2015-08-12 13:34:14 -0700156 }
Skuhnea5a93ee2015-08-20 15:43:57 -0700157 // Abort the ongoing dragging.
158 mDragging = false;
Skuhnea5a93ee2015-08-20 15:43:57 -0700159 return true;
Skuhne81c524a2015-08-12 13:34:14 -0700160 }
161 return mDragging;
162 }
163
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -0700164 /**
165 * The phone window configuration has changed and the decor needs to be updated.
166 * @param showDecor True if the decor should be shown.
167 * @param windowHasShadow True when the window should show a shadow.
168 **/
Wale Ogunwale0d7e9122015-11-17 10:45:06 -0800169 public void onConfigurationChanged(boolean showDecor, boolean windowHasShadow) {
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -0700170 mShowDecor = showDecor;
Skuhnef7b882c2015-08-11 17:18:58 -0700171 updateCaptionVisibility();
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -0700172 if (windowHasShadow != mWindowHasShadow) {
173 mWindowHasShadow = windowHasShadow;
174 initializeElevation();
175 }
176 }
177
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700178 @Override
179 public void onClick(View view) {
180 if (view.getId() == R.id.maximize_window) {
Stefan Kuhne1b420572015-08-07 10:50:19 -0700181 maximizeWindow();
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700182 } else if (view.getId() == R.id.close_window) {
Stefan Kuhne1b420572015-08-07 10:50:19 -0700183 mOwner.dispatchOnWindowDismissed(true /*finishTask*/);
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700184 }
185 }
186
187 @Override
188 public void onWindowFocusChanged(boolean hasWindowFocus) {
189 mWindowHasFocus = hasWindowFocus;
190 updateElevation();
191 super.onWindowFocusChanged(hasWindowFocus);
192 }
193
194 @Override
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700195 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700196 // If the application changed its SystemUI metrics, we might also have to adapt
197 // our shadow elevation.
198 updateElevation();
199 mAllowUpdateElevation = true;
200
Skuhnef7b882c2015-08-11 17:18:58 -0700201 super.onLayout(changed, left, top, right, bottom);
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700202 }
203
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700204 @Override
Skuhnef7b882c2015-08-11 17:18:58 -0700205 public void addView(View child, int index, ViewGroup.LayoutParams params) {
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -0700206 // Make sure that we never get more then one client area in our view.
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700207 if (index >= 2 || getChildCount() >= 2) {
208 throw new IllegalStateException("NonClientDecorView can only handle 1 client view");
209 }
210 super.addView(child, index, params);
211 }
212
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -0700213 /**
214 * Determine if the workspace is entirely covered by the window.
215 * @return Returns true when the window is filling the entire screen/workspace.
216 **/
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700217 private boolean isFillingScreen() {
218 return (0 != ((getWindowSystemUiVisibility() | getSystemUiVisibility()) &
219 (View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
220 View.SYSTEM_UI_FLAG_IMMERSIVE | View.SYSTEM_UI_FLAG_LOW_PROFILE)));
221 }
222
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -0700223 /**
Skuhnef7b882c2015-08-11 17:18:58 -0700224 * Updates the visibility of the caption.
225 **/
226 private void updateCaptionVisibility() {
227 // Don't show the decor if the window has e.g. entered full screen.
228 boolean invisible = isFillingScreen() || !mShowDecor;
229 View caption = getChildAt(0);
230 caption.setVisibility(invisible ? GONE : VISIBLE);
Chong Zhang509ea6b2015-09-30 14:09:52 -0700231 caption.setOnTouchListener(this);
Chong Zhang8e89b312015-09-09 15:09:30 -0700232 mVisible = !invisible;
Skuhnef7b882c2015-08-11 17:18:58 -0700233 }
234
235 /**
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -0700236 * The elevation gets set for the first time and the framework needs to be informed that
237 * the surface layer gets created with the shadow size in mind.
238 **/
239 private void initializeElevation() {
240 // TODO(skuhne): Call setMaxElevation here accordingly after b/22668382 got fixed.
241 mAllowUpdateElevation = false;
242 if (mWindowHasShadow) {
243 updateElevation();
244 } else {
245 mOwner.setElevation(0);
246 }
247 }
248
249 /**
250 * The shadow height gets controlled by the focus to visualize highlighted windows.
251 * Note: This will overwrite application elevation properties.
252 * Note: Windows which have (temporarily) changed their attributes to cover the SystemUI
253 * will get no shadow as they are expected to be "full screen".
254 **/
Wale Ogunwalebf9eefc2015-11-17 14:47:52 -0800255 public void updateElevation() {
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700256 float elevation = 0;
Skuhneb8160872015-09-22 09:51:39 -0700257 // Do not use a shadow when we are in resizing mode (mRenderer not null) since the shadow
258 // is bound to the content size and not the target size.
Wale Ogunwalebf9eefc2015-11-17 14:47:52 -0800259 if (mWindowHasShadow
260 && ((DecorView) mOwner.getDecorView()).mBackdropFrameRenderer == null) {
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700261 boolean fill = isFillingScreen();
262 elevation = fill ? 0 :
263 (mWindowHasFocus ? DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP :
264 DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP);
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -0700265 // TODO(skuhne): Remove this if clause once b/22668382 got fixed.
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700266 if (!mAllowUpdateElevation && !fill) {
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700267 elevation = DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP;
268 }
269 // Convert the DP elevation into physical pixels.
270 elevation = dipToPx(elevation);
271 }
272 // Don't change the elevation if it didn't change since it can require some time.
273 if (mOwner.getDecorView().getElevation() != elevation) {
274 mOwner.setElevation(elevation);
275 }
276 }
277
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -0700278 /**
279 * Converts a DIP measure into physical pixels.
280 * @param dip The dip value.
281 * @return Returns the number of pixels.
282 */
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700283 private float dipToPx(float dip) {
284 return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip,
285 getResources().getDisplayMetrics());
286 }
Stefan Kuhne1b420572015-08-07 10:50:19 -0700287
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -0700288 /**
289 * Maximize the window by moving it to the maximized workspace stack.
290 **/
Stefan Kuhne1b420572015-08-07 10:50:19 -0700291 private void maximizeWindow() {
Skuhnece2faa52015-08-11 10:36:38 -0700292 Window.WindowControllerCallback callback = mOwner.getWindowControllerCallback();
Stefan Kuhne1b420572015-08-07 10:50:19 -0700293 if (callback != null) {
294 try {
Wale Ogunwale3797c222015-10-27 14:21:58 -0700295 callback.changeWindowStack(FULLSCREEN_WORKSPACE_STACK_ID);
Stefan Kuhne1b420572015-08-07 10:50:19 -0700296 } catch (RemoteException ex) {
297 Log.e(TAG, "Cannot change task workspace.");
298 }
299 }
300 }
Wale Ogunwale8cc5a742015-11-17 15:41:05 -0800301
302 public boolean isShowingDecor() {
303 return mShowDecor;
304 }
305
306 public int getDecorCaptionHeight() {
307 final View caption = getChildAt(0);
308 return (caption != null) ? caption.getHeight() : 0;
309 }
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700310}