blob: d8626cd34ff112a80555f9f277e1bd2b2c46266b [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;
20import android.os.RemoteException;
21import android.util.AttributeSet;
22import android.view.View;
23import android.view.ViewGroup;
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -070024import android.view.ViewOutlineProvider;
25import android.view.Window;
26import android.view.WindowInsets;
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070027import android.util.Log;
28import android.util.TypedValue;
29
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070030import com.android.internal.R;
31import com.android.internal.policy.PhoneWindow;
32
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070033/**
34 * This class represents the special screen elements to control a window on free form
35 * environment. All thse screen elements are added in the "non client area" which is the area of
36 * the window which is handled by the OS and not the application.
37 * 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>
41 * <li>The shadow - which is changing dependent on the window focus.</li>
42 * <li>The border around the client area (if there is one).</li>
43 * <li>The resize handles which allow to resize the window.</li>
44 * </ul>
45 * After creating the view, the function
46 * {@link #setPhoneWindow(PhoneWindow owner, boolean windowHasShadow)} 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>setElevation</li>
52 * <li>setOutlineProvider</li>
53 * <li>setSurfaceFormat</li>
54 * <li>..</li>
55 * </ul>
56 * This will be mitigated once b/22527834 will be addressed.
57 */
58public class NonClientDecorView extends ViewGroup implements View.OnClickListener {
59 private final static String TAG = "NonClientDecorView";
60 // The height of a window which has focus in DIP.
61 private final int DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP = 20;
62 // The height of a window which has not in DIP.
63 private final int DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP = 5;
64
65 private PhoneWindow mOwner = null;
66 boolean mWindowHasShadow = false;
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -070067 boolean mShowDecor = false;
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070068
69 // The current focus state of the window for updating the window elevation.
70 boolean mWindowHasFocus = true;
71
72 // Cludge to address b/22668382: Set the shadow size to the maximum so that the layer
73 // size calculation takes the shadow size into account. We set the elevation currently
74 // to max until the first layout command has been executed.
75 boolean mAllowUpdateElevation = false;
76
77 public NonClientDecorView(Context context) {
78 super(context);
79 }
80
81 public NonClientDecorView(Context context, AttributeSet attrs) {
82 super(context, attrs);
83 }
84
85 public NonClientDecorView(Context context, AttributeSet attrs, int defStyle) {
86 super(context, attrs, defStyle);
87 }
88
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -070089 public void setPhoneWindow(PhoneWindow owner, boolean showDecor, boolean windowHasShadow) {
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070090 mOwner = owner;
91 mWindowHasShadow = windowHasShadow;
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -070092 mShowDecor = showDecor;
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070093 if (mWindowHasShadow) {
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -070094 initializeElevation();
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070095 }
96 // By changing the outline provider to BOUNDS, the window can remove its
97 // background without removing the shadow.
98 mOwner.getDecorView().setOutlineProvider(ViewOutlineProvider.BOUNDS);
99 findViewById(R.id.maximize_window).setOnClickListener(this);
100 findViewById(R.id.close_window).setOnClickListener(this);
101 }
102
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -0700103 /**
104 * The phone window configuration has changed and the decor needs to be updated.
105 * @param showDecor True if the decor should be shown.
106 * @param windowHasShadow True when the window should show a shadow.
107 **/
108 public void phoneWindowUpdated(boolean showDecor, boolean windowHasShadow) {
109 mShowDecor = showDecor;
110 if (windowHasShadow != mWindowHasShadow) {
111 mWindowHasShadow = windowHasShadow;
112 initializeElevation();
113 }
114 }
115
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700116 @Override
117 public void onClick(View view) {
118 if (view.getId() == R.id.maximize_window) {
Stefan Kuhne1b420572015-08-07 10:50:19 -0700119 maximizeWindow();
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700120 } else if (view.getId() == R.id.close_window) {
Stefan Kuhne1b420572015-08-07 10:50:19 -0700121 mOwner.dispatchOnWindowDismissed(true /*finishTask*/);
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700122 }
123 }
124
125 @Override
126 public void onWindowFocusChanged(boolean hasWindowFocus) {
127 mWindowHasFocus = hasWindowFocus;
128 updateElevation();
129 super.onWindowFocusChanged(hasWindowFocus);
130 }
131
132 @Override
133 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
134 measureChildren(widthMeasureSpec, heightMeasureSpec);
135 final int width = MeasureSpec.getSize(widthMeasureSpec);
136 final int height = MeasureSpec.getSize(heightMeasureSpec);
137 setMeasuredDimension(width, height);
138 }
139
140 @Override
141 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
142 // The system inset needs only to be applied to the caption. The client area of
143 // the window will automatically be adjusted by the the DecorView.
144 WindowInsets insets = getRootWindowInsets();
145 int systemMargin = insets.getSystemWindowInsetTop();
146
147 final int leftPos = getPaddingLeft();
148 final int rightPos = right - left - getPaddingRight();
149 final int topPos = getPaddingTop();
150 final int bottomPos = bottom - top - getPaddingBottom();
151
152 // On top we have the caption which has to fill left to right with a fixed height.
153 final int width = rightPos - leftPos;
154 final View caption = getChildAt(0);
155
156 // If the application changed its SystemUI metrics, we might also have to adapt
157 // our shadow elevation.
158 updateElevation();
159 mAllowUpdateElevation = true;
160
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -0700161 // Don't show the decor if the window has e.g. entered full screen.
162 final int captionHeight =
163 (isFillingScreen() || !mShowDecor) ? 0 : caption.getMeasuredHeight();
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700164 caption.layout(leftPos, topPos + systemMargin, leftPos + width,
165 topPos + systemMargin + captionHeight);
166
167 // Note: We should never have more then 1 additional item in here.
168 if (getChildCount() > 1) {
169 getChildAt(1).layout(leftPos, topPos + captionHeight, leftPos + width, bottomPos);
170 }
171 }
172
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700173 @Override
174 public void addView(View child, int index, LayoutParams params) {
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -0700175 // Make sure that we never get more then one client area in our view.
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700176 if (index >= 2 || getChildCount() >= 2) {
177 throw new IllegalStateException("NonClientDecorView can only handle 1 client view");
178 }
179 super.addView(child, index, params);
180 }
181
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -0700182 /**
183 * Determine if the workspace is entirely covered by the window.
184 * @return Returns true when the window is filling the entire screen/workspace.
185 **/
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700186 private boolean isFillingScreen() {
187 return (0 != ((getWindowSystemUiVisibility() | getSystemUiVisibility()) &
188 (View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
189 View.SYSTEM_UI_FLAG_IMMERSIVE | View.SYSTEM_UI_FLAG_LOW_PROFILE)));
190 }
191
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -0700192 /**
193 * The elevation gets set for the first time and the framework needs to be informed that
194 * the surface layer gets created with the shadow size in mind.
195 **/
196 private void initializeElevation() {
197 // TODO(skuhne): Call setMaxElevation here accordingly after b/22668382 got fixed.
198 mAllowUpdateElevation = false;
199 if (mWindowHasShadow) {
200 updateElevation();
201 } else {
202 mOwner.setElevation(0);
203 }
204 }
205
206 /**
207 * The shadow height gets controlled by the focus to visualize highlighted windows.
208 * Note: This will overwrite application elevation properties.
209 * Note: Windows which have (temporarily) changed their attributes to cover the SystemUI
210 * will get no shadow as they are expected to be "full screen".
211 **/
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700212 private void updateElevation() {
213 float elevation = 0;
214 if (mWindowHasShadow) {
215 boolean fill = isFillingScreen();
216 elevation = fill ? 0 :
217 (mWindowHasFocus ? DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP :
218 DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP);
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -0700219 // TODO(skuhne): Remove this if clause once b/22668382 got fixed.
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700220 if (!mAllowUpdateElevation && !fill) {
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700221 elevation = DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP;
222 }
223 // Convert the DP elevation into physical pixels.
224 elevation = dipToPx(elevation);
225 }
226 // Don't change the elevation if it didn't change since it can require some time.
227 if (mOwner.getDecorView().getElevation() != elevation) {
228 mOwner.setElevation(elevation);
229 }
230 }
231
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -0700232 /**
233 * Converts a DIP measure into physical pixels.
234 * @param dip The dip value.
235 * @return Returns the number of pixels.
236 */
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700237 private float dipToPx(float dip) {
238 return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip,
239 getResources().getDisplayMetrics());
240 }
Stefan Kuhne1b420572015-08-07 10:50:19 -0700241
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -0700242 /**
243 * Maximize the window by moving it to the maximized workspace stack.
244 **/
Stefan Kuhne1b420572015-08-07 10:50:19 -0700245 private void maximizeWindow() {
246 Window.WindowStackCallback callback = mOwner.getWindowStackCallback();
247 if (callback != null) {
248 try {
249 callback.changeWindowStack(
250 android.app.ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID);
251 } catch (RemoteException ex) {
252 Log.e(TAG, "Cannot change task workspace.");
253 }
254 }
255 }
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700256}