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