1/*
2 * Copyright (C) 2014 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.systemui.statusbar.phone;
18
19import android.app.ActivityManager;
20import android.app.IActivityManager;
21import android.content.Context;
22import android.content.pm.ActivityInfo;
23import android.content.res.Resources;
24import android.graphics.PixelFormat;
25import android.os.Binder;
26import android.os.RemoteException;
27import android.os.SystemProperties;
28import android.util.Log;
29import android.view.Gravity;
30import android.view.View;
31import android.view.ViewGroup;
32import android.view.WindowManager;
33
34import com.android.keyguard.R;
35import com.android.systemui.Dumpable;
36import com.android.systemui.keyguard.KeyguardViewMediator;
37import com.android.systemui.statusbar.RemoteInputController;
38import com.android.systemui.statusbar.StatusBarState;
39
40import java.io.FileDescriptor;
41import java.io.PrintWriter;
42import java.lang.reflect.Field;
43
44/**
45 * Encapsulates all logic for the status bar window state management.
46 */
47public class StatusBarWindowManager implements RemoteInputController.Callback, Dumpable {
48
49    private static final String TAG = "StatusBarWindowManager";
50
51    private final Context mContext;
52    private final WindowManager mWindowManager;
53    private final IActivityManager mActivityManager;
54    private View mStatusBarView;
55    private WindowManager.LayoutParams mLp;
56    private WindowManager.LayoutParams mLpChanged;
57    private boolean mHasTopUi;
58    private boolean mHasTopUiChanged;
59    private int mBarHeight;
60    private final boolean mKeyguardScreenRotation;
61    private final float mScreenBrightnessDoze;
62    private final State mCurrentState = new State();
63    private OtherwisedCollapsedListener mListener;
64
65    public StatusBarWindowManager(Context context) {
66        mContext = context;
67        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
68        mActivityManager = ActivityManager.getService();
69        mKeyguardScreenRotation = shouldEnableKeyguardScreenRotation();
70        mScreenBrightnessDoze = mContext.getResources().getInteger(
71                com.android.internal.R.integer.config_screenBrightnessDoze) / 255f;
72    }
73
74    private boolean shouldEnableKeyguardScreenRotation() {
75        Resources res = mContext.getResources();
76        return SystemProperties.getBoolean("lockscreen.rot_override", false)
77                || res.getBoolean(R.bool.config_enableLockScreenRotation);
78    }
79
80    /**
81     * Adds the status bar view to the window manager.
82     *
83     * @param statusBarView The view to add.
84     * @param barHeight The height of the status bar in collapsed state.
85     */
86    public void add(View statusBarView, int barHeight) {
87
88        // Now that the status bar window encompasses the sliding panel and its
89        // translucent backdrop, the entire thing is made TRANSLUCENT and is
90        // hardware-accelerated.
91        mLp = new WindowManager.LayoutParams(
92                ViewGroup.LayoutParams.MATCH_PARENT,
93                barHeight,
94                WindowManager.LayoutParams.TYPE_STATUS_BAR,
95                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
96                        | WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
97                        | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
98                        | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
99                        | WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
100                PixelFormat.TRANSLUCENT);
101        mLp.token = new Binder();
102        mLp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
103        mLp.gravity = Gravity.TOP;
104        mLp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
105        mLp.setTitle("StatusBar");
106        mLp.packageName = mContext.getPackageName();
107        mStatusBarView = statusBarView;
108        mBarHeight = barHeight;
109        mWindowManager.addView(mStatusBarView, mLp);
110        mLpChanged = new WindowManager.LayoutParams();
111        mLpChanged.copyFrom(mLp);
112    }
113
114    private void applyKeyguardFlags(State state) {
115        if (state.keyguardShowing) {
116            mLpChanged.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
117        } else {
118            mLpChanged.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
119        }
120
121        if (state.keyguardShowing && !state.backdropShowing && !state.dozing) {
122            mLpChanged.flags |= WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
123        } else {
124            mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
125        }
126    }
127
128    private void adjustScreenOrientation(State state) {
129        if (state.isKeyguardShowingAndNotOccluded()) {
130            if (mKeyguardScreenRotation) {
131                mLpChanged.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_USER;
132            } else {
133                mLpChanged.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
134            }
135        } else {
136            mLpChanged.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
137        }
138    }
139
140    private void applyFocusableFlag(State state) {
141        boolean panelFocusable = state.statusBarFocusable && state.panelExpanded;
142        if (state.bouncerShowing && (state.keyguardOccluded || state.keyguardNeedsInput)
143                || StatusBar.ENABLE_REMOTE_INPUT && state.remoteInputActive) {
144            mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
145            mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
146        } else if (state.isKeyguardShowingAndNotOccluded() || panelFocusable) {
147            mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
148            mLpChanged.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
149        } else {
150            mLpChanged.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
151            mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
152        }
153
154        mLpChanged.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
155    }
156
157    private void applyHeight(State state) {
158        boolean expanded = isExpanded(state);
159        if (state.forcePluginOpen) {
160            mListener.setWouldOtherwiseCollapse(expanded);
161            expanded = true;
162        }
163        if (expanded) {
164            mLpChanged.height = ViewGroup.LayoutParams.MATCH_PARENT;
165        } else {
166            mLpChanged.height = mBarHeight;
167        }
168    }
169
170    private boolean isExpanded(State state) {
171        return !state.forceCollapsed && (state.isKeyguardShowingAndNotOccluded()
172                || state.panelVisible || state.keyguardFadingAway || state.bouncerShowing
173                || state.headsUpShowing);
174    }
175
176    private void applyFitsSystemWindows(State state) {
177        boolean fitsSystemWindows = !state.isKeyguardShowingAndNotOccluded();
178        if (mStatusBarView.getFitsSystemWindows() != fitsSystemWindows) {
179            mStatusBarView.setFitsSystemWindows(fitsSystemWindows);
180            mStatusBarView.requestApplyInsets();
181        }
182    }
183
184    private void applyUserActivityTimeout(State state) {
185        if (state.isKeyguardShowingAndNotOccluded()
186                && state.statusBarState == StatusBarState.KEYGUARD
187                && !state.qsExpanded) {
188            mLpChanged.userActivityTimeout = KeyguardViewMediator.AWAKE_INTERVAL_DEFAULT_MS;
189        } else {
190            mLpChanged.userActivityTimeout = -1;
191        }
192    }
193
194    private void applyInputFeatures(State state) {
195        if (state.isKeyguardShowingAndNotOccluded()
196                && state.statusBarState == StatusBarState.KEYGUARD
197                && !state.qsExpanded && !state.forceUserActivity) {
198            mLpChanged.inputFeatures |=
199                    WindowManager.LayoutParams.INPUT_FEATURE_DISABLE_USER_ACTIVITY;
200        } else {
201            mLpChanged.inputFeatures &=
202                    ~WindowManager.LayoutParams.INPUT_FEATURE_DISABLE_USER_ACTIVITY;
203        }
204    }
205
206    private void apply(State state) {
207        applyKeyguardFlags(state);
208        applyForceStatusBarVisibleFlag(state);
209        applyFocusableFlag(state);
210        adjustScreenOrientation(state);
211        applyHeight(state);
212        applyUserActivityTimeout(state);
213        applyInputFeatures(state);
214        applyFitsSystemWindows(state);
215        applyModalFlag(state);
216        applyBrightness(state);
217        applyHasTopUi(state);
218        if (mLp.copyFrom(mLpChanged) != 0) {
219            mWindowManager.updateViewLayout(mStatusBarView, mLp);
220        }
221        if (mHasTopUi != mHasTopUiChanged) {
222            try {
223                mActivityManager.setHasTopUi(mHasTopUiChanged);
224            } catch (RemoteException e) {
225                Log.e(TAG, "Failed to call setHasTopUi", e);
226            }
227            mHasTopUi = mHasTopUiChanged;
228        }
229    }
230
231    private void applyForceStatusBarVisibleFlag(State state) {
232        if (state.forceStatusBarVisible) {
233            mLpChanged.privateFlags |= WindowManager
234                    .LayoutParams.PRIVATE_FLAG_FORCE_STATUS_BAR_VISIBLE_TRANSPARENT;
235        } else {
236            mLpChanged.privateFlags &= ~WindowManager
237                    .LayoutParams.PRIVATE_FLAG_FORCE_STATUS_BAR_VISIBLE_TRANSPARENT;
238        }
239    }
240
241    private void applyModalFlag(State state) {
242        if (state.headsUpShowing) {
243            mLpChanged.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
244        } else {
245            mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
246        }
247    }
248
249    private void applyBrightness(State state) {
250        if (state.forceDozeBrightness) {
251            mLpChanged.screenBrightness = mScreenBrightnessDoze;
252        } else {
253            mLpChanged.screenBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE;
254        }
255    }
256
257    private void applyHasTopUi(State state) {
258        mHasTopUiChanged = isExpanded(state);
259    }
260
261    public void setKeyguardShowing(boolean showing) {
262        mCurrentState.keyguardShowing = showing;
263        apply(mCurrentState);
264    }
265
266    public void setKeyguardOccluded(boolean occluded) {
267        mCurrentState.keyguardOccluded = occluded;
268        apply(mCurrentState);
269    }
270
271    public void setKeyguardNeedsInput(boolean needsInput) {
272        mCurrentState.keyguardNeedsInput = needsInput;
273        apply(mCurrentState);
274    }
275
276    public void setPanelVisible(boolean visible) {
277        mCurrentState.panelVisible = visible;
278        mCurrentState.statusBarFocusable = visible;
279        apply(mCurrentState);
280    }
281
282    public void setStatusBarFocusable(boolean focusable) {
283        mCurrentState.statusBarFocusable = focusable;
284        apply(mCurrentState);
285    }
286
287    public void setBouncerShowing(boolean showing) {
288        mCurrentState.bouncerShowing = showing;
289        apply(mCurrentState);
290    }
291
292    public void setBackdropShowing(boolean showing) {
293        mCurrentState.backdropShowing = showing;
294        apply(mCurrentState);
295    }
296
297    public void setKeyguardFadingAway(boolean keyguardFadingAway) {
298        mCurrentState.keyguardFadingAway = keyguardFadingAway;
299        apply(mCurrentState);
300    }
301
302    public void setQsExpanded(boolean expanded) {
303        mCurrentState.qsExpanded = expanded;
304        apply(mCurrentState);
305    }
306
307    public void setForceUserActivity(boolean forceUserActivity) {
308        mCurrentState.forceUserActivity = forceUserActivity;
309        apply(mCurrentState);
310    }
311
312    public void setHeadsUpShowing(boolean showing) {
313        mCurrentState.headsUpShowing = showing;
314        apply(mCurrentState);
315    }
316
317    /**
318     * @param state The {@link StatusBarState} of the status bar.
319     */
320    public void setStatusBarState(int state) {
321        mCurrentState.statusBarState = state;
322        apply(mCurrentState);
323    }
324
325    public void setForceStatusBarVisible(boolean forceStatusBarVisible) {
326        mCurrentState.forceStatusBarVisible = forceStatusBarVisible;
327        apply(mCurrentState);
328    }
329
330    /**
331     * Force the window to be collapsed, even if it should theoretically be expanded.
332     * Used for when a heads-up comes in but we still need to wait for the touchable regions to
333     * be computed.
334     */
335    public void setForceWindowCollapsed(boolean force) {
336        mCurrentState.forceCollapsed = force;
337        apply(mCurrentState);
338    }
339
340    public void setPanelExpanded(boolean isExpanded) {
341        mCurrentState.panelExpanded = isExpanded;
342        apply(mCurrentState);
343    }
344
345    @Override
346    public void onRemoteInputActive(boolean remoteInputActive) {
347        mCurrentState.remoteInputActive = remoteInputActive;
348        apply(mCurrentState);
349    }
350
351    /**
352     * Set whether the screen brightness is forced to the value we use for doze mode by the status
353     * bar window.
354     */
355    public void setForceDozeBrightness(boolean forceDozeBrightness) {
356        mCurrentState.forceDozeBrightness = forceDozeBrightness;
357        apply(mCurrentState);
358    }
359
360    public void setDozing(boolean dozing) {
361        mCurrentState.dozing = dozing;
362        apply(mCurrentState);
363    }
364
365    public void setBarHeight(int barHeight) {
366        mBarHeight = barHeight;
367        apply(mCurrentState);
368    }
369
370    public void setForcePluginOpen(boolean forcePluginOpen) {
371        mCurrentState.forcePluginOpen = forcePluginOpen;
372        apply(mCurrentState);
373    }
374
375    public void setStateListener(OtherwisedCollapsedListener listener) {
376        mListener = listener;
377    }
378
379    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
380        pw.println("StatusBarWindowManager state:");
381        pw.println(mCurrentState);
382    }
383
384    public boolean isShowingWallpaper() {
385        return !mCurrentState.backdropShowing;
386    }
387
388    private static class State {
389        boolean keyguardShowing;
390        boolean keyguardOccluded;
391        boolean keyguardNeedsInput;
392        boolean panelVisible;
393        boolean panelExpanded;
394        boolean statusBarFocusable;
395        boolean bouncerShowing;
396        boolean keyguardFadingAway;
397        boolean qsExpanded;
398        boolean headsUpShowing;
399        boolean forceStatusBarVisible;
400        boolean forceCollapsed;
401        boolean forceDozeBrightness;
402        boolean forceUserActivity;
403        boolean backdropShowing;
404
405        /**
406         * The {@link StatusBar} state from the status bar.
407         */
408        int statusBarState;
409
410        boolean remoteInputActive;
411        boolean forcePluginOpen;
412        boolean dozing;
413
414        private boolean isKeyguardShowingAndNotOccluded() {
415            return keyguardShowing && !keyguardOccluded;
416        }
417
418        @Override
419        public String toString() {
420            StringBuilder result = new StringBuilder();
421            String newLine = "\n";
422            result.append("Window State {");
423            result.append(newLine);
424
425            Field[] fields = this.getClass().getDeclaredFields();
426
427            // Print field names paired with their values
428            for (Field field : fields) {
429                result.append("  ");
430                try {
431                    result.append(field.getName());
432                    result.append(": ");
433                    //requires access to private field:
434                    result.append(field.get(this));
435                } catch (IllegalAccessException ex) {
436                }
437                result.append(newLine);
438            }
439            result.append("}");
440
441            return result.toString();
442        }
443    }
444
445    /**
446     * Custom listener to pipe data back to plugins about whether or not the status bar would be
447     * collapsed if not for the plugin.
448     * TODO: Find cleaner way to do this.
449     */
450    public interface OtherwisedCollapsedListener {
451        void setWouldOtherwiseCollapse(boolean otherwiseCollapse);
452    }
453}
454