/* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.statusbar.phone; import static com.android.systemui.Dependency.MAIN_HANDLER_NAME; import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT; import android.content.Context; import android.graphics.Rect; import android.os.Handler; import android.os.RemoteException; import android.util.Log; import android.view.IWindowManager; import android.view.MotionEvent; import android.view.View; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.Dependency; import com.android.systemui.SysUiServiceProvider; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationRemoteInputManager; import javax.inject.Inject; import javax.inject.Named; /** A controller to control all auto-hide things. */ public class AutoHideController implements CommandQueue.Callbacks { private static final String TAG = "AutoHideController"; private final IWindowManager mWindowManagerService; private final Handler mHandler; private final NotificationRemoteInputManager mRemoteInputManager; private final CommandQueue mCommandQueue; private StatusBar mStatusBar; private NavigationBarFragment mNavigationBar; @VisibleForTesting int mDisplayId; @VisibleForTesting int mSystemUiVisibility; // last value sent to window manager private int mLastDispatchedSystemUiVisibility = ~View.SYSTEM_UI_FLAG_VISIBLE; private boolean mAutoHideSuspended; private static final long AUTOHIDE_TIMEOUT_MS = 2250; private final Runnable mAutoHide = () -> { int requested = mSystemUiVisibility & ~getTransientMask(); if (mSystemUiVisibility != requested) { notifySystemUiVisibilityChanged(requested); } }; @Inject public AutoHideController(Context context, @Named(MAIN_HANDLER_NAME) Handler handler) { mCommandQueue = SysUiServiceProvider.getComponent(context, CommandQueue.class); mCommandQueue.addCallback(this); mHandler = handler; mRemoteInputManager = Dependency.get(NotificationRemoteInputManager.class); mWindowManagerService = Dependency.get(IWindowManager.class); mDisplayId = context.getDisplayId(); } @Override public void onDisplayRemoved(int displayId) { if (displayId == mDisplayId) { mCommandQueue.removeCallback(this); } } void setStatusBar(StatusBar statusBar) { mStatusBar = statusBar; } void setNavigationBar(NavigationBarFragment navigationBar) { mNavigationBar = navigationBar; } @Override public void setSystemUiVisibility(int displayId, int vis, int fullscreenStackVis, int dockedStackVis, int mask, Rect fullscreenStackBounds, Rect dockedStackBounds, boolean navbarColorManagedByIme) { if (displayId != mDisplayId) { return; } int oldVal = mSystemUiVisibility; int newVal = (oldVal & ~mask) | (vis & mask); int diff = newVal ^ oldVal; if (diff != 0) { mSystemUiVisibility = newVal; // ready to unhide if (hasStatusBar() && (vis & View.STATUS_BAR_UNHIDE) != 0) { mSystemUiVisibility &= ~View.STATUS_BAR_UNHIDE; } if (hasNavigationBar() && (vis & View.NAVIGATION_BAR_UNHIDE) != 0) { mSystemUiVisibility &= ~View.NAVIGATION_BAR_UNHIDE; } // Re-send setSystemUiVisibility to update un-hide status. if (mSystemUiVisibility != newVal) { mCommandQueue.setSystemUiVisibility(mDisplayId, mSystemUiVisibility, fullscreenStackVis, dockedStackVis, mask, fullscreenStackBounds, dockedStackBounds, navbarColorManagedByIme); } notifySystemUiVisibilityChanged(mSystemUiVisibility); } } @VisibleForTesting void notifySystemUiVisibilityChanged(int vis) { try { if (mLastDispatchedSystemUiVisibility != vis) { mWindowManagerService.statusBarVisibilityChanged(mDisplayId, vis); mLastDispatchedSystemUiVisibility = vis; } } catch (RemoteException ex) { Log.w(TAG, "Cannot get WindowManager"); } } void resumeSuspendedAutoHide() { if (mAutoHideSuspended) { scheduleAutoHide(); Runnable checkBarModesRunnable = getCheckBarModesRunnable(); if (checkBarModesRunnable != null) { mHandler.postDelayed(checkBarModesRunnable, 500); // longer than home -> launcher } } } void suspendAutoHide() { mHandler.removeCallbacks(mAutoHide); Runnable checkBarModesRunnable = getCheckBarModesRunnable(); if (checkBarModesRunnable != null) { mHandler.removeCallbacks(checkBarModesRunnable); } mAutoHideSuspended = (mSystemUiVisibility & getTransientMask()) != 0; } void touchAutoHide() { // update transient bar auto hide if ((hasStatusBar() && mStatusBar.getStatusBarMode() == MODE_SEMI_TRANSPARENT) || hasNavigationBar() && mNavigationBar.isSemiTransparent()) { scheduleAutoHide(); } else { cancelAutoHide(); } } private Runnable getCheckBarModesRunnable() { if (hasStatusBar()) { return () -> mStatusBar.checkBarModes(); } else if (hasNavigationBar()) { return () -> mNavigationBar.checkNavBarModes(); } else { return null; } } private void cancelAutoHide() { mAutoHideSuspended = false; mHandler.removeCallbacks(mAutoHide); } private void scheduleAutoHide() { cancelAutoHide(); mHandler.postDelayed(mAutoHide, AUTOHIDE_TIMEOUT_MS); } void checkUserAutoHide(MotionEvent event) { boolean shouldAutoHide = (mSystemUiVisibility & getTransientMask()) != 0 // a transient bar is revealed. && event.getAction() == MotionEvent.ACTION_OUTSIDE // touch outside the source bar. && event.getX() == 0 && event.getY() == 0; if (hasStatusBar()) { // a touch outside both bars shouldAutoHide &= !mRemoteInputManager.getController().isRemoteInputActive(); } if (shouldAutoHide) { userAutoHide(); } } private void userAutoHide() { cancelAutoHide(); mHandler.postDelayed(mAutoHide, 350); // longer than app gesture -> flag clear } private int getTransientMask() { int mask = 0; if (hasStatusBar()) { mask |= View.STATUS_BAR_TRANSIENT; } if (hasNavigationBar()) { mask |= View.NAVIGATION_BAR_TRANSIENT; } return mask; } boolean hasNavigationBar() { return mNavigationBar != null; } @VisibleForTesting boolean hasStatusBar() { return mStatusBar != null; } }