blob: 56cf9216d0c9645fd4bf066a83add3dcbcb3331d [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
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070019import android.content.Context;
Skuhnece2faa52015-08-11 10:36:38 -070020import android.graphics.Rect;
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070021import android.os.RemoteException;
22import android.util.AttributeSet;
Skuhne81c524a2015-08-12 13:34:14 -070023import android.view.MotionEvent;
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070024import android.view.View;
Skuhnef7b882c2015-08-11 17:18:58 -070025import android.widget.LinearLayout;
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070026import 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;
30import android.util.TypedValue;
31
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070032import com.android.internal.R;
33import com.android.internal.policy.PhoneWindow;
34
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070035/**
36 * This class represents the special screen elements to control a window on free form
37 * environment. All thse screen elements are added in the "non client area" which is the area of
38 * the window which is handled by the OS and not the application.
39 * As such this class handles the following things:
40 * <ul>
41 * <li>The caption, containing the system buttons like maximize, close and such as well as
42 * allowing the user to drag the window around.</li>
43 * <li>The shadow - which is changing dependent on the window focus.</li>
44 * <li>The border around the client area (if there is one).</li>
45 * <li>The resize handles which allow to resize the window.</li>
46 * </ul>
47 * After creating the view, the function
48 * {@link #setPhoneWindow(PhoneWindow owner, boolean windowHasShadow)} needs to be called to make
49 * the connection to it's owning PhoneWindow.
50 * Note: At this time the application can change various attributes of the DecorView which
51 * will break things (in settle/unexpected ways):
52 * <ul>
53 * <li>setElevation</li>
54 * <li>setOutlineProvider</li>
55 * <li>setSurfaceFormat</li>
56 * <li>..</li>
57 * </ul>
58 * This will be mitigated once b/22527834 will be addressed.
59 */
Chong Zhang509ea6b2015-09-30 14:09:52 -070060public class NonClientDecorView extends LinearLayout
61 implements View.OnClickListener, View.OnTouchListener {
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070062 private final static String TAG = "NonClientDecorView";
63 // The height of a window which has focus in DIP.
64 private final int DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP = 20;
65 // The height of a window which has not in DIP.
66 private final int DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP = 5;
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070067 private PhoneWindow mOwner = null;
Skuhne81c524a2015-08-12 13:34:14 -070068 private boolean mWindowHasShadow = false;
69 private boolean mShowDecor = false;
70
71 // True if the window is being dragged.
72 private boolean mDragging = false;
73
Skuhne81c524a2015-08-12 13:34:14 -070074 // True when the left mouse button got released while dragging.
75 private boolean mLeftMouseButtonReleased;
76
Skuhnea5a93ee2015-08-20 15:43:57 -070077 // True if this window is resizable (which is currently only true when the decor is shown).
Chong Zhang8e89b312015-09-09 15:09:30 -070078 public boolean mVisible = false;
Skuhnea5a93ee2015-08-20 15:43:57 -070079
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070080 // The current focus state of the window for updating the window elevation.
Skuhne81c524a2015-08-12 13:34:14 -070081 private boolean mWindowHasFocus = true;
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070082
83 // Cludge to address b/22668382: Set the shadow size to the maximum so that the layer
84 // size calculation takes the shadow size into account. We set the elevation currently
85 // to max until the first layout command has been executed.
Skuhne81c524a2015-08-12 13:34:14 -070086 private boolean mAllowUpdateElevation = false;
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070087
88 public NonClientDecorView(Context context) {
89 super(context);
90 }
91
92 public NonClientDecorView(Context context, AttributeSet attrs) {
93 super(context, attrs);
94 }
95
96 public NonClientDecorView(Context context, AttributeSet attrs, int defStyle) {
97 super(context, attrs, defStyle);
98 }
99
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -0700100 public void setPhoneWindow(PhoneWindow owner, boolean showDecor, boolean windowHasShadow) {
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700101 mOwner = owner;
102 mWindowHasShadow = windowHasShadow;
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -0700103 mShowDecor = showDecor;
Skuhnef7b882c2015-08-11 17:18:58 -0700104 updateCaptionVisibility();
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700105 if (mWindowHasShadow) {
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -0700106 initializeElevation();
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700107 }
108 // By changing the outline provider to BOUNDS, the window can remove its
109 // background without removing the shadow.
110 mOwner.getDecorView().setOutlineProvider(ViewOutlineProvider.BOUNDS);
111 findViewById(R.id.maximize_window).setOnClickListener(this);
112 findViewById(R.id.close_window).setOnClickListener(this);
113 }
114
Skuhne81c524a2015-08-12 13:34:14 -0700115 @Override
Chong Zhang509ea6b2015-09-30 14:09:52 -0700116 public boolean onTouch(View v, MotionEvent e) {
Skuhne81c524a2015-08-12 13:34:14 -0700117 // Note: There are no mixed events. When a new device gets used (e.g. 1. Mouse, 2. touch)
118 // the old input device events get cancelled first. So no need to remember the kind of
119 // input device we are listening to.
120 switch (e.getActionMasked()) {
121 case MotionEvent.ACTION_DOWN:
Skuhnea635a262015-08-26 15:45:58 -0700122 if (!mShowDecor) {
123 // When there is no decor we should not react to anything.
124 return false;
125 }
Skuhne81c524a2015-08-12 13:34:14 -0700126 // A drag action is started if we aren't dragging already and the starting event is
127 // either a left mouse button or any other input device.
128 if (!mDragging &&
129 (e.getToolType(e.getActionIndex()) != MotionEvent.TOOL_TYPE_MOUSE ||
130 (e.getButtonState() & MotionEvent.BUTTON_PRIMARY) != 0)) {
131 mDragging = true;
Skuhne81c524a2015-08-12 13:34:14 -0700132 mLeftMouseButtonReleased = false;
Chong Zhang8e89b312015-09-09 15:09:30 -0700133 startMovingTask(e.getRawX(), e.getRawY());
Skuhne81c524a2015-08-12 13:34:14 -0700134 }
135 break;
136
137 case MotionEvent.ACTION_MOVE:
138 if (mDragging && !mLeftMouseButtonReleased) {
139 if (e.getToolType(e.getActionIndex()) == MotionEvent.TOOL_TYPE_MOUSE &&
140 (e.getButtonState() & MotionEvent.BUTTON_PRIMARY) == 0) {
141 // There is no separate mouse button up call and if the user mixes mouse
142 // button drag actions, we stop dragging once he releases the button.
143 mLeftMouseButtonReleased = true;
144 break;
145 }
Skuhne81c524a2015-08-12 13:34:14 -0700146 }
147 break;
148
149 case MotionEvent.ACTION_UP:
Skuhne81c524a2015-08-12 13:34:14 -0700150 case MotionEvent.ACTION_CANCEL:
Skuhnea5a93ee2015-08-20 15:43:57 -0700151 if (!mDragging) {
152 break;
Skuhne81c524a2015-08-12 13:34:14 -0700153 }
Skuhnea5a93ee2015-08-20 15:43:57 -0700154 // Abort the ongoing dragging.
155 mDragging = false;
Skuhnea5a93ee2015-08-20 15:43:57 -0700156 return true;
Skuhne81c524a2015-08-12 13:34:14 -0700157 }
158 return mDragging;
159 }
160
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -0700161 /**
162 * The phone window configuration has changed and the decor needs to be updated.
163 * @param showDecor True if the decor should be shown.
164 * @param windowHasShadow True when the window should show a shadow.
165 **/
166 public void phoneWindowUpdated(boolean showDecor, boolean windowHasShadow) {
167 mShowDecor = showDecor;
Skuhnef7b882c2015-08-11 17:18:58 -0700168 updateCaptionVisibility();
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -0700169 if (windowHasShadow != mWindowHasShadow) {
170 mWindowHasShadow = windowHasShadow;
171 initializeElevation();
172 }
173 }
174
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700175 @Override
176 public void onClick(View view) {
177 if (view.getId() == R.id.maximize_window) {
Stefan Kuhne1b420572015-08-07 10:50:19 -0700178 maximizeWindow();
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700179 } else if (view.getId() == R.id.close_window) {
Stefan Kuhne1b420572015-08-07 10:50:19 -0700180 mOwner.dispatchOnWindowDismissed(true /*finishTask*/);
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700181 }
182 }
183
184 @Override
185 public void onWindowFocusChanged(boolean hasWindowFocus) {
186 mWindowHasFocus = hasWindowFocus;
187 updateElevation();
188 super.onWindowFocusChanged(hasWindowFocus);
189 }
190
191 @Override
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700192 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700193 // If the application changed its SystemUI metrics, we might also have to adapt
194 // our shadow elevation.
195 updateElevation();
196 mAllowUpdateElevation = true;
197
Skuhnef7b882c2015-08-11 17:18:58 -0700198 super.onLayout(changed, left, top, right, bottom);
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700199 }
200
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700201 @Override
Skuhnef7b882c2015-08-11 17:18:58 -0700202 public void addView(View child, int index, ViewGroup.LayoutParams params) {
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -0700203 // Make sure that we never get more then one client area in our view.
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700204 if (index >= 2 || getChildCount() >= 2) {
205 throw new IllegalStateException("NonClientDecorView can only handle 1 client view");
206 }
207 super.addView(child, index, params);
208 }
209
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -0700210 /**
211 * Determine if the workspace is entirely covered by the window.
212 * @return Returns true when the window is filling the entire screen/workspace.
213 **/
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700214 private boolean isFillingScreen() {
215 return (0 != ((getWindowSystemUiVisibility() | getSystemUiVisibility()) &
216 (View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
217 View.SYSTEM_UI_FLAG_IMMERSIVE | View.SYSTEM_UI_FLAG_LOW_PROFILE)));
218 }
219
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -0700220 /**
Skuhnef7b882c2015-08-11 17:18:58 -0700221 * Updates the visibility of the caption.
222 **/
223 private void updateCaptionVisibility() {
224 // Don't show the decor if the window has e.g. entered full screen.
225 boolean invisible = isFillingScreen() || !mShowDecor;
226 View caption = getChildAt(0);
227 caption.setVisibility(invisible ? GONE : VISIBLE);
Chong Zhang509ea6b2015-09-30 14:09:52 -0700228 caption.setOnTouchListener(this);
Chong Zhang8e89b312015-09-09 15:09:30 -0700229 mVisible = !invisible;
Skuhnef7b882c2015-08-11 17:18:58 -0700230 }
231
232 /**
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -0700233 * The elevation gets set for the first time and the framework needs to be informed that
234 * the surface layer gets created with the shadow size in mind.
235 **/
236 private void initializeElevation() {
237 // TODO(skuhne): Call setMaxElevation here accordingly after b/22668382 got fixed.
238 mAllowUpdateElevation = false;
239 if (mWindowHasShadow) {
240 updateElevation();
241 } else {
242 mOwner.setElevation(0);
243 }
244 }
245
246 /**
247 * The shadow height gets controlled by the focus to visualize highlighted windows.
248 * Note: This will overwrite application elevation properties.
249 * Note: Windows which have (temporarily) changed their attributes to cover the SystemUI
250 * will get no shadow as they are expected to be "full screen".
251 **/
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700252 private void updateElevation() {
253 float elevation = 0;
254 if (mWindowHasShadow) {
255 boolean fill = isFillingScreen();
256 elevation = fill ? 0 :
257 (mWindowHasFocus ? DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP :
258 DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP);
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -0700259 // TODO(skuhne): Remove this if clause once b/22668382 got fixed.
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700260 if (!mAllowUpdateElevation && !fill) {
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700261 elevation = DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP;
262 }
263 // Convert the DP elevation into physical pixels.
264 elevation = dipToPx(elevation);
265 }
266 // Don't change the elevation if it didn't change since it can require some time.
267 if (mOwner.getDecorView().getElevation() != elevation) {
268 mOwner.setElevation(elevation);
269 }
270 }
271
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -0700272 /**
273 * Converts a DIP measure into physical pixels.
274 * @param dip The dip value.
275 * @return Returns the number of pixels.
276 */
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700277 private float dipToPx(float dip) {
278 return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip,
279 getResources().getDisplayMetrics());
280 }
Stefan Kuhne1b420572015-08-07 10:50:19 -0700281
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -0700282 /**
283 * Maximize the window by moving it to the maximized workspace stack.
284 **/
Stefan Kuhne1b420572015-08-07 10:50:19 -0700285 private void maximizeWindow() {
Skuhnece2faa52015-08-11 10:36:38 -0700286 Window.WindowControllerCallback callback = mOwner.getWindowControllerCallback();
Stefan Kuhne1b420572015-08-07 10:50:19 -0700287 if (callback != null) {
288 try {
289 callback.changeWindowStack(
290 android.app.ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID);
291 } catch (RemoteException ex) {
292 Log.e(TAG, "Cannot change task workspace.");
293 }
294 }
295 }
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700296}