blob: d343783f5a5d404431d186f78cfd842785e09844 [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;
24import android.util.Log;
25import android.util.TypedValue;
26
27import android.view.ViewOutlineProvider;
28import android.view.WindowInsets;
Stefan Kuhne1b420572015-08-07 10:50:19 -070029import android.view.Window;
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;
67
68 // The current focus state of the window for updating the window elevation.
69 boolean mWindowHasFocus = true;
70
71 // Cludge to address b/22668382: Set the shadow size to the maximum so that the layer
72 // size calculation takes the shadow size into account. We set the elevation currently
73 // to max until the first layout command has been executed.
74 boolean mAllowUpdateElevation = false;
75
76 public NonClientDecorView(Context context) {
77 super(context);
78 }
79
80 public NonClientDecorView(Context context, AttributeSet attrs) {
81 super(context, attrs);
82 }
83
84 public NonClientDecorView(Context context, AttributeSet attrs, int defStyle) {
85 super(context, attrs, defStyle);
86 }
87
88 public void setPhoneWindow(PhoneWindow owner, boolean windowHasShadow) {
89 mOwner = owner;
90 mWindowHasShadow = windowHasShadow;
91 if (mWindowHasShadow) {
92 // TODO(skuhne): Call setMaxElevation here as soon as b/22668382 got fixed.
93 updateElevation();
94 }
95 // By changing the outline provider to BOUNDS, the window can remove its
96 // background without removing the shadow.
97 mOwner.getDecorView().setOutlineProvider(ViewOutlineProvider.BOUNDS);
98 findViewById(R.id.maximize_window).setOnClickListener(this);
99 findViewById(R.id.close_window).setOnClickListener(this);
100 }
101
102 @Override
103 public void onClick(View view) {
104 if (view.getId() == R.id.maximize_window) {
Stefan Kuhne1b420572015-08-07 10:50:19 -0700105 maximizeWindow();
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700106 } else if (view.getId() == R.id.close_window) {
Stefan Kuhne1b420572015-08-07 10:50:19 -0700107 mOwner.dispatchOnWindowDismissed(true /*finishTask*/);
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700108 }
109 }
110
111 @Override
112 public void onWindowFocusChanged(boolean hasWindowFocus) {
113 mWindowHasFocus = hasWindowFocus;
114 updateElevation();
115 super.onWindowFocusChanged(hasWindowFocus);
116 }
117
118 @Override
119 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
120 measureChildren(widthMeasureSpec, heightMeasureSpec);
121 final int width = MeasureSpec.getSize(widthMeasureSpec);
122 final int height = MeasureSpec.getSize(heightMeasureSpec);
123 setMeasuredDimension(width, height);
124 }
125
126 @Override
127 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
128 // The system inset needs only to be applied to the caption. The client area of
129 // the window will automatically be adjusted by the the DecorView.
130 WindowInsets insets = getRootWindowInsets();
131 int systemMargin = insets.getSystemWindowInsetTop();
132
133 final int leftPos = getPaddingLeft();
134 final int rightPos = right - left - getPaddingRight();
135 final int topPos = getPaddingTop();
136 final int bottomPos = bottom - top - getPaddingBottom();
137
138 // On top we have the caption which has to fill left to right with a fixed height.
139 final int width = rightPos - leftPos;
140 final View caption = getChildAt(0);
141
142 // If the application changed its SystemUI metrics, we might also have to adapt
143 // our shadow elevation.
144 updateElevation();
145 mAllowUpdateElevation = true;
146
147 // Remove the decor temporarily if the window entered a full screen/immersive mode.
148 final int captionHeight = isFillingScreen() ? 0 : caption.getMeasuredHeight();
149 caption.layout(leftPos, topPos + systemMargin, leftPos + width,
150 topPos + systemMargin + captionHeight);
151
152 // Note: We should never have more then 1 additional item in here.
153 if (getChildCount() > 1) {
154 getChildAt(1).layout(leftPos, topPos + captionHeight, leftPos + width, bottomPos);
155 }
156 }
157
158 // Make sure that we never get more then one client area in our view.
159 @Override
160 public void addView(View child, int index, LayoutParams params) {
161 if (index >= 2 || getChildCount() >= 2) {
162 throw new IllegalStateException("NonClientDecorView can only handle 1 client view");
163 }
164 super.addView(child, index, params);
165 }
166
167 // Returns true when the window is filling the entire screen and the non client area
168 // should not be shown.
169 private boolean isFillingScreen() {
170 return (0 != ((getWindowSystemUiVisibility() | getSystemUiVisibility()) &
171 (View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
172 View.SYSTEM_UI_FLAG_IMMERSIVE | View.SYSTEM_UI_FLAG_LOW_PROFILE)));
173 }
174
175 // The shadow height gets controlled by the focus to visualize highlighted windows.
176 // Note: This will overwrite application elevation properties.
177 // Note: Windows which have (temporarily) changed their attributes to cover the SystemUI
178 // will get no shadow as they are expected to be "full screen".
179 private void updateElevation() {
180 float elevation = 0;
181 if (mWindowHasShadow) {
182 boolean fill = isFillingScreen();
183 elevation = fill ? 0 :
184 (mWindowHasFocus ? DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP :
185 DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP);
186 if (!mAllowUpdateElevation && !fill) {
187 // TODO(skuhne): Change this to setMaxElevation as soon as b/22668382 got fixed
188 // and remove this cludge.
189 elevation = DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP;
190 }
191 // Convert the DP elevation into physical pixels.
192 elevation = dipToPx(elevation);
193 }
194 // Don't change the elevation if it didn't change since it can require some time.
195 if (mOwner.getDecorView().getElevation() != elevation) {
196 mOwner.setElevation(elevation);
197 }
198 }
199
200 private float dipToPx(float dip) {
201 return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip,
202 getResources().getDisplayMetrics());
203 }
Stefan Kuhne1b420572015-08-07 10:50:19 -0700204
205 // Maximize the window by moving it to the maximize stack.
206 private void maximizeWindow() {
207 Window.WindowStackCallback callback = mOwner.getWindowStackCallback();
208 if (callback != null) {
209 try {
210 callback.changeWindowStack(
211 android.app.ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID);
212 } catch (RemoteException ex) {
213 Log.e(TAG, "Cannot change task workspace.");
214 }
215 }
216 }
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700217}