blob: 57039b7c8e24137d732b62c9145b6c676950cff1 [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
19import android.app.ActivityManager;
20import android.app.ActivityManager.RunningTaskInfo;
21import android.app.ActivityManagerNative;
22import android.content.Context;
23import android.os.RemoteException;
24import android.util.AttributeSet;
25import android.view.View;
26import android.view.ViewGroup;
27import android.util.Log;
28import android.util.TypedValue;
29
30import android.view.ViewOutlineProvider;
31import android.view.WindowInsets;
32import com.android.internal.R;
33import com.android.internal.policy.PhoneWindow;
34
35import java.util.List;
36
37/**
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
50 * {@link #setPhoneWindow(PhoneWindow owner, boolean windowHasShadow)} needs to be called to make
51 * 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 */
62public class NonClientDecorView extends ViewGroup implements View.OnClickListener {
63 private final static String TAG = "NonClientDecorView";
64 // The height of a window which has focus in DIP.
65 private final int DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP = 20;
66 // The height of a window which has not in DIP.
67 private final int DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP = 5;
68
69 private PhoneWindow mOwner = null;
70 boolean mWindowHasShadow = false;
71
72 // The current focus state of the window for updating the window elevation.
73 boolean mWindowHasFocus = true;
74
75 // Cludge to address b/22668382: Set the shadow size to the maximum so that the layer
76 // size calculation takes the shadow size into account. We set the elevation currently
77 // to max until the first layout command has been executed.
78 boolean mAllowUpdateElevation = false;
79
80 public NonClientDecorView(Context context) {
81 super(context);
82 }
83
84 public NonClientDecorView(Context context, AttributeSet attrs) {
85 super(context, attrs);
86 }
87
88 public NonClientDecorView(Context context, AttributeSet attrs, int defStyle) {
89 super(context, attrs, defStyle);
90 }
91
92 public void setPhoneWindow(PhoneWindow owner, boolean windowHasShadow) {
93 mOwner = owner;
94 mWindowHasShadow = windowHasShadow;
95 if (mWindowHasShadow) {
96 // TODO(skuhne): Call setMaxElevation here as soon as b/22668382 got fixed.
97 updateElevation();
98 }
99 // By changing the outline provider to BOUNDS, the window can remove its
100 // background without removing the shadow.
101 mOwner.getDecorView().setOutlineProvider(ViewOutlineProvider.BOUNDS);
102 findViewById(R.id.maximize_window).setOnClickListener(this);
103 findViewById(R.id.close_window).setOnClickListener(this);
104 }
105
106 @Override
107 public void onClick(View view) {
108 if (view.getId() == R.id.maximize_window) {
109 // TODO(skuhne): Add code to maximize window.
110 } else if (view.getId() == R.id.close_window) {
111 // TODO(skuhne): This is not the right way to kill an app and we should add a high level
112 // function for it.
113 final ActivityManager m =
114 (ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE);
115 List<RunningTaskInfo> runningTaskInfoList = m.getRunningTasks(1);
116 if (!runningTaskInfoList.isEmpty()) {
117 try {
118 ActivityManagerNative.getDefault().removeTask(runningTaskInfoList.get(0).id);
119 } catch (RemoteException ex) {
120 Log.e(TAG, "Couldn't close task with the close button.");
121 }
122 }
123 }
124 }
125
126 @Override
127 public void onWindowFocusChanged(boolean hasWindowFocus) {
128 mWindowHasFocus = hasWindowFocus;
129 updateElevation();
130 super.onWindowFocusChanged(hasWindowFocus);
131 }
132
133 @Override
134 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
135 measureChildren(widthMeasureSpec, heightMeasureSpec);
136 final int width = MeasureSpec.getSize(widthMeasureSpec);
137 final int height = MeasureSpec.getSize(heightMeasureSpec);
138 setMeasuredDimension(width, height);
139 }
140
141 @Override
142 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
143 // The system inset needs only to be applied to the caption. The client area of
144 // the window will automatically be adjusted by the the DecorView.
145 WindowInsets insets = getRootWindowInsets();
146 int systemMargin = insets.getSystemWindowInsetTop();
147
148 final int leftPos = getPaddingLeft();
149 final int rightPos = right - left - getPaddingRight();
150 final int topPos = getPaddingTop();
151 final int bottomPos = bottom - top - getPaddingBottom();
152
153 // On top we have the caption which has to fill left to right with a fixed height.
154 final int width = rightPos - leftPos;
155 final View caption = getChildAt(0);
156
157 // If the application changed its SystemUI metrics, we might also have to adapt
158 // our shadow elevation.
159 updateElevation();
160 mAllowUpdateElevation = true;
161
162 // Remove the decor temporarily if the window entered a full screen/immersive mode.
163 final int captionHeight = isFillingScreen() ? 0 : caption.getMeasuredHeight();
164 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
173 // Make sure that we never get more then one client area in our view.
174 @Override
175 public void addView(View child, int index, LayoutParams params) {
176 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
182 // Returns true when the window is filling the entire screen and the non client area
183 // should not be shown.
184 private boolean isFillingScreen() {
185 return (0 != ((getWindowSystemUiVisibility() | getSystemUiVisibility()) &
186 (View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
187 View.SYSTEM_UI_FLAG_IMMERSIVE | View.SYSTEM_UI_FLAG_LOW_PROFILE)));
188 }
189
190 // The shadow height gets controlled by the focus to visualize highlighted windows.
191 // Note: This will overwrite application elevation properties.
192 // Note: Windows which have (temporarily) changed their attributes to cover the SystemUI
193 // will get no shadow as they are expected to be "full screen".
194 private void updateElevation() {
195 float elevation = 0;
196 if (mWindowHasShadow) {
197 boolean fill = isFillingScreen();
198 elevation = fill ? 0 :
199 (mWindowHasFocus ? DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP :
200 DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP);
201 if (!mAllowUpdateElevation && !fill) {
202 // TODO(skuhne): Change this to setMaxElevation as soon as b/22668382 got fixed
203 // and remove this cludge.
204 elevation = DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP;
205 }
206 // Convert the DP elevation into physical pixels.
207 elevation = dipToPx(elevation);
208 }
209 // Don't change the elevation if it didn't change since it can require some time.
210 if (mOwner.getDecorView().getElevation() != elevation) {
211 mOwner.setElevation(elevation);
212 }
213 }
214
215 private float dipToPx(float dip) {
216 return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip,
217 getResources().getDisplayMetrics());
218 }
219}