blob: 04f1f86c3ac7a54245be385a8cceac522607ac4f [file] [log] [blame]
/*
* Copyright (C) 2020 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.wmshell;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_GLOBAL_ACTIONS_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ONE_HANDED_ACTIVE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.inputmethodservice.InputMethodService;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.view.KeyEvent;
import com.android.internal.annotations.VisibleForTesting;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.Dependency;
import com.android.systemui.SystemUI;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.shared.tracing.ProtoTraceable;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.tracing.ProtoTracer;
import com.android.systemui.tracing.nano.SystemUiTraceProto;
import com.android.wm.shell.ShellDump;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
import com.android.wm.shell.nano.WmShellTraceProto;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedEvents;
import com.android.wm.shell.onehanded.OneHandedGestureHandler.OneHandedGestureEventCallback;
import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.protolog.ShellProtoLogImpl;
import com.android.wm.shell.splitscreen.SplitScreen;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Optional;
import javax.inject.Inject;
/**
* Proxy in SysUiScope to delegate events to controllers in WM Shell library.
*/
@SysUISingleton
public final class WMShell extends SystemUI
implements CommandQueue.Callbacks, ProtoTraceable<SystemUiTraceProto> {
private static final String TAG = WMShell.class.getName();
private static final int INVALID_SYSUI_STATE_MASK =
SYSUI_STATE_GLOBAL_ACTIONS_SHOWING
| SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
| SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED
| SYSUI_STATE_BOUNCER_SHOWING
| SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
| SYSUI_STATE_BUBBLES_EXPANDED
| SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
private final CommandQueue mCommandQueue;
private final ConfigurationController mConfigurationController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final NavigationModeController mNavigationModeController;
private final ScreenLifecycle mScreenLifecycle;
private final SysUiState mSysUiState;
private final Optional<Pip> mPipOptional;
private final Optional<SplitScreen> mSplitScreenOptional;
private final Optional<OneHanded> mOneHandedOptional;
private final Optional<HideDisplayCutout> mHideDisplayCutoutOptional;
private final ProtoTracer mProtoTracer;
private final Optional<ShellDump> mShellDump;
private boolean mIsSysUiStateValid;
private KeyguardUpdateMonitorCallback mSplitScreenKeyguardCallback;
private KeyguardUpdateMonitorCallback mPipKeyguardCallback;
private KeyguardUpdateMonitorCallback mOneHandedKeyguardCallback;
@Inject
public WMShell(Context context, CommandQueue commandQueue,
ConfigurationController configurationController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
NavigationModeController navigationModeController,
ScreenLifecycle screenLifecycle,
SysUiState sysUiState,
Optional<Pip> pipOptional,
Optional<SplitScreen> splitScreenOptional,
Optional<OneHanded> oneHandedOptional,
Optional<HideDisplayCutout> hideDisplayCutoutOptional,
ProtoTracer protoTracer,
Optional<ShellDump> shellDump) {
super(context);
mCommandQueue = commandQueue;
mConfigurationController = configurationController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mNavigationModeController = navigationModeController;
mScreenLifecycle = screenLifecycle;
mSysUiState = sysUiState;
mPipOptional = pipOptional;
mSplitScreenOptional = splitScreenOptional;
mOneHandedOptional = oneHandedOptional;
mHideDisplayCutoutOptional = hideDisplayCutoutOptional;
mProtoTracer = protoTracer;
mProtoTracer.add(this);
mShellDump = shellDump;
}
@Override
public void start() {
mCommandQueue.addCallback(this);
mPipOptional.ifPresent(this::initPip);
mSplitScreenOptional.ifPresent(this::initSplitScreen);
mOneHandedOptional.ifPresent(this::initOneHanded);
mHideDisplayCutoutOptional.ifPresent(this::initHideDisplayCutout);
}
@VisibleForTesting
void initPip(Pip pip) {
mCommandQueue.addCallback(new CommandQueue.Callbacks() {
@Override
public void showPictureInPictureMenu() {
pip.showPictureInPictureMenu();
}
});
mPipKeyguardCallback = new KeyguardUpdateMonitorCallback() {
@Override
public void onKeyguardVisibilityChanged(boolean showing) {
if (showing) {
pip.hidePipMenu(null, null);
}
}
};
mKeyguardUpdateMonitor.registerCallback(mPipKeyguardCallback);
mSysUiState.addCallback(sysUiStateFlag -> {
mIsSysUiStateValid = (sysUiStateFlag & INVALID_SYSUI_STATE_MASK) == 0;
pip.onSystemUiStateChanged(mIsSysUiStateValid, sysUiStateFlag);
});
mConfigurationController.addCallback(new ConfigurationController.ConfigurationListener() {
@Override
public void onDensityOrFontScaleChanged() {
pip.onDensityOrFontScaleChanged();
}
@Override
public void onOverlayChanged() {
pip.onOverlayChanged();
}
});
// The media session listener needs to be re-registered when switching users
UserInfoController userInfoController = Dependency.get(UserInfoController.class);
userInfoController.addCallback((String name, Drawable picture, String userAccount) ->
pip.registerSessionListenerForCurrentUser());
}
@VisibleForTesting
void initSplitScreen(SplitScreen splitScreen) {
mSplitScreenKeyguardCallback = new KeyguardUpdateMonitorCallback() {
@Override
public void onKeyguardVisibilityChanged(boolean showing) {
// Hide the divider when keyguard is showing. Even though keyguard/statusbar is
// above everything, it is actually transparent except for notifications, so
// we still need to hide any surfaces that are below it.
// TODO(b/148906453): Figure out keyguard dismiss animation for divider view.
splitScreen.onKeyguardVisibilityChanged(showing);
}
};
mKeyguardUpdateMonitor.registerCallback(mSplitScreenKeyguardCallback);
}
@VisibleForTesting
void initOneHanded(OneHanded oneHanded) {
int currentMode = mNavigationModeController.addListener(mode ->
oneHanded.setThreeButtonModeEnabled(mode == NAV_BAR_MODE_3BUTTON));
oneHanded.setThreeButtonModeEnabled(currentMode == NAV_BAR_MODE_3BUTTON);
oneHanded.registerTransitionCallback(new OneHandedTransitionCallback() {
@Override
public void onStartFinished(Rect bounds) {
mSysUiState.setFlag(SYSUI_STATE_ONE_HANDED_ACTIVE,
true).commitUpdate(DEFAULT_DISPLAY);
}
@Override
public void onStopFinished(Rect bounds) {
mSysUiState.setFlag(SYSUI_STATE_ONE_HANDED_ACTIVE,
false).commitUpdate(DEFAULT_DISPLAY);
}
});
oneHanded.registerGestureCallback(new OneHandedGestureEventCallback() {
@Override
public void onStart() {
if (oneHanded.isOneHandedEnabled()) {
oneHanded.startOneHanded();
} else if (oneHanded.isSwipeToNotificationEnabled()) {
mCommandQueue.handleSystemKey(KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN);
}
}
@Override
public void onStop() {
if (oneHanded.isOneHandedEnabled()) {
oneHanded.stopOneHanded(OneHandedEvents.EVENT_ONE_HANDED_TRIGGER_GESTURE_OUT);
} else if (oneHanded.isSwipeToNotificationEnabled()) {
mCommandQueue.handleSystemKey(KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP);
}
}
});
mOneHandedKeyguardCallback = new KeyguardUpdateMonitorCallback() {
@Override
public void onKeyguardBouncerChanged(boolean bouncer) {
if (bouncer) {
oneHanded.stopOneHanded();
}
}
@Override
public void onKeyguardVisibilityChanged(boolean showing) {
oneHanded.stopOneHanded();
}
};
mKeyguardUpdateMonitor.registerCallback(mOneHandedKeyguardCallback);
mScreenLifecycle.addObserver(new ScreenLifecycle.Observer() {
@Override
public void onScreenTurningOff() {
oneHanded.stopOneHanded(
OneHandedEvents.EVENT_ONE_HANDED_TRIGGER_SCREEN_OFF_OUT);
}
});
mCommandQueue.addCallback(new CommandQueue.Callbacks() {
@Override
public void onCameraLaunchGestureDetected(int source) {
oneHanded.stopOneHanded();
}
@Override
public void setImeWindowStatus(int displayId, IBinder token, int vis,
int backDisposition, boolean showImeSwitcher) {
if (displayId == DEFAULT_DISPLAY && (vis & InputMethodService.IME_VISIBLE) != 0) {
oneHanded.stopOneHanded(OneHandedEvents.EVENT_ONE_HANDED_TRIGGER_POP_IME_OUT);
}
}
});
}
@VisibleForTesting
void initHideDisplayCutout(HideDisplayCutout hideDisplayCutout) {
mConfigurationController.addCallback(new ConfigurationController.ConfigurationListener() {
@Override
public void onConfigChanged(Configuration newConfig) {
hideDisplayCutout.onConfigurationChanged(newConfig);
}
});
}
@Override
public void writeToProto(SystemUiTraceProto proto) {
if (proto.wmShell == null) {
proto.wmShell = new WmShellTraceProto();
}
// Dump to WMShell proto here
// TODO: Figure out how we want to synchronize while dumping to proto
}
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
// Handle commands if provided
if (handleLoggingCommand(args, pw)) {
return;
}
// Dump WMShell stuff here if no commands were handled
mShellDump.ifPresent((shellDump) -> shellDump.dump(pw));
}
@Override
public void handleWindowManagerLoggingCommand(String[] args, ParcelFileDescriptor outFd) {
PrintWriter pw = new PrintWriter(new ParcelFileDescriptor.AutoCloseOutputStream(outFd));
handleLoggingCommand(args, pw);
pw.flush();
pw.close();
}
private boolean handleLoggingCommand(String[] args, PrintWriter pw) {
ShellProtoLogImpl protoLogImpl = ShellProtoLogImpl.getSingleInstance();
for (int i = 0; i < args.length; i++) {
switch (args[i]) {
case "enable-text": {
String[] groups = Arrays.copyOfRange(args, i + 1, args.length);
int result = protoLogImpl.startTextLogging(mContext, groups, pw);
if (result == 0) {
pw.println("Starting logging on groups: " + Arrays.toString(groups));
}
return true;
}
case "disable-text": {
String[] groups = Arrays.copyOfRange(args, i + 1, args.length);
int result = protoLogImpl.stopTextLogging(groups, pw);
if (result == 0) {
pw.println("Stopping logging on groups: " + Arrays.toString(groups));
}
return true;
}
}
}
return false;
}
}