blob: a4a59fc9d4a7d2876fbce59fd390d36795d93100 [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 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_BUBBLES_MANAGE_MENU_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DIALOG_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.pm.UserInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.inputmethodservice.InputMethodService;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.view.KeyEvent;
import androidx.annotation.NonNull;
import com.android.internal.annotations.VisibleForTesting;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.CoreStartable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.WMComponent;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.model.SysUiState;
import com.android.systemui.settings.UserTracker;
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.KeyguardStateController;
import com.android.systemui.tracing.ProtoTracer;
import com.android.systemui.tracing.nano.SystemUiTraceProto;
import com.android.wm.shell.nano.WmShellTraceProto;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedEventCallback;
import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
import com.android.wm.shell.onehanded.OneHandedUiEventLogger;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.protolog.ShellProtoLogImpl;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.sysui.ShellInterface;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.Executor;
import javax.inject.Inject;
/**
* A SystemUI service that starts with the SystemUI application and sets up any bindings between
* Shell and SysUI components. This service starts happens after the {@link WMComponent} has
* already been initialized and may only reference Shell components that are explicitly exported to
* SystemUI (see {@link WMComponent}.
*
* eg. SysUI application starts
* -> SystemUIFactory is initialized
* -> WMComponent is created
* -> WMShellBaseModule dependencies are injected
* -> WMShellModule (form-factory specific) dependencies are injected
* -> SysUIComponent is created
* -> WMComponents are explicitly provided to SysUIComponent for injection into SysUI code
* -> SysUI services are started
* -> WMShell starts and binds SysUI with Shell components via exported Shell interfaces
*/
@SysUISingleton
public final class WMShell extends CoreStartable
implements CommandQueue.Callbacks, ProtoTraceable<SystemUiTraceProto> {
private static final String TAG = WMShell.class.getName();
private static final int INVALID_SYSUI_STATE_MASK =
SYSUI_STATE_DIALOG_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_BUBBLES_MANAGE_MENU_EXPANDED
| SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
// Shell interfaces
private final ShellInterface mShell;
private final Optional<Pip> mPipOptional;
private final Optional<SplitScreen> mSplitScreenOptional;
private final Optional<OneHanded> mOneHandedOptional;
private final CommandQueue mCommandQueue;
private final ConfigurationController mConfigurationController;
private final KeyguardStateController mKeyguardStateController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final ScreenLifecycle mScreenLifecycle;
private final SysUiState mSysUiState;
private final WakefulnessLifecycle mWakefulnessLifecycle;
private final ProtoTracer mProtoTracer;
private final UserTracker mUserTracker;
private final Executor mSysUiMainExecutor;
// Listeners and callbacks. Note that we prefer member variable over anonymous class here to
// avoid the situation that some implementations, like KeyguardUpdateMonitor, use WeakReference
// internally and anonymous class could be released after registration.
private final ConfigurationController.ConfigurationListener mConfigurationListener =
new ConfigurationController.ConfigurationListener() {
@Override
public void onConfigChanged(Configuration newConfig) {
mShell.onConfigurationChanged(newConfig);
}
};
private final KeyguardStateController.Callback mKeyguardStateCallback =
new KeyguardStateController.Callback() {
@Override
public void onKeyguardShowingChanged() {
mShell.onKeyguardVisibilityChanged(mKeyguardStateController.isShowing(),
mKeyguardStateController.isOccluded(),
mKeyguardStateController.isAnimatingBetweenKeyguardAndSurfaceBehind());
}
};
private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
new KeyguardUpdateMonitorCallback() {
@Override
public void onKeyguardDismissAnimationFinished() {
mShell.onKeyguardDismissAnimationFinished();
}
};
private final UserTracker.Callback mUserChangedCallback =
new UserTracker.Callback() {
@Override
public void onUserChanged(int newUser, @NonNull Context userContext) {
mShell.onUserChanged(newUser, userContext);
}
@Override
public void onProfilesChanged(@NonNull List<UserInfo> profiles) {
mShell.onUserProfilesChanged(profiles);
}
};
private boolean mIsSysUiStateValid;
private WakefulnessLifecycle.Observer mWakefulnessObserver;
@Inject
public WMShell(Context context,
ShellInterface shell,
Optional<Pip> pipOptional,
Optional<SplitScreen> splitScreenOptional,
Optional<OneHanded> oneHandedOptional,
CommandQueue commandQueue,
ConfigurationController configurationController,
KeyguardStateController keyguardStateController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
ScreenLifecycle screenLifecycle,
SysUiState sysUiState,
ProtoTracer protoTracer,
WakefulnessLifecycle wakefulnessLifecycle,
UserTracker userTracker,
@Main Executor sysUiMainExecutor) {
super(context);
mShell = shell;
mCommandQueue = commandQueue;
mConfigurationController = configurationController;
mKeyguardStateController = keyguardStateController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mScreenLifecycle = screenLifecycle;
mSysUiState = sysUiState;
mPipOptional = pipOptional;
mSplitScreenOptional = splitScreenOptional;
mOneHandedOptional = oneHandedOptional;
mWakefulnessLifecycle = wakefulnessLifecycle;
mProtoTracer = protoTracer;
mUserTracker = userTracker;
mSysUiMainExecutor = sysUiMainExecutor;
}
@Override
public void start() {
// Notify with the initial configuration and subscribe for new config changes
mShell.onConfigurationChanged(mContext.getResources().getConfiguration());
mConfigurationController.addCallback(mConfigurationListener);
// Subscribe to keyguard changes
mKeyguardStateController.addCallback(mKeyguardStateCallback);
mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
// Subscribe to user changes
mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor());
mProtoTracer.add(this);
mCommandQueue.addCallback(this);
mPipOptional.ifPresent(this::initPip);
mSplitScreenOptional.ifPresent(this::initSplitScreen);
mOneHandedOptional.ifPresent(this::initOneHanded);
}
@VisibleForTesting
void initPip(Pip pip) {
mCommandQueue.addCallback(new CommandQueue.Callbacks() {
@Override
public void showPictureInPictureMenu() {
pip.showPictureInPictureMenu();
}
});
mSysUiState.addCallback(sysUiStateFlag -> {
mIsSysUiStateValid = (sysUiStateFlag & INVALID_SYSUI_STATE_MASK) == 0;
pip.onSystemUiStateChanged(mIsSysUiStateValid, sysUiStateFlag);
});
}
@VisibleForTesting
void initSplitScreen(SplitScreen splitScreen) {
mWakefulnessLifecycle.addObserver(new WakefulnessLifecycle.Observer() {
@Override
public void onFinishedWakingUp() {
splitScreen.onFinishedWakingUp();
}
});
}
@VisibleForTesting
void initOneHanded(OneHanded oneHanded) {
oneHanded.registerTransitionCallback(new OneHandedTransitionCallback() {
@Override
public void onStartTransition(boolean isEntering) {
mSysUiMainExecutor.execute(() -> {
mSysUiState.setFlag(SYSUI_STATE_ONE_HANDED_ACTIVE,
true).commitUpdate(DEFAULT_DISPLAY);
});
}
@Override
public void onStartFinished(Rect bounds) {
mSysUiMainExecutor.execute(() -> {
mSysUiState.setFlag(SYSUI_STATE_ONE_HANDED_ACTIVE,
true).commitUpdate(DEFAULT_DISPLAY);
});
}
@Override
public void onStopFinished(Rect bounds) {
mSysUiMainExecutor.execute(() -> {
mSysUiState.setFlag(SYSUI_STATE_ONE_HANDED_ACTIVE,
false).commitUpdate(DEFAULT_DISPLAY);
});
}
});
oneHanded.registerEventCallback(new OneHandedEventCallback() {
@Override
public void notifyExpandNotification() {
mSysUiMainExecutor.execute(
() -> mCommandQueue.handleSystemKey(
KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN));
}
});
mWakefulnessObserver =
new WakefulnessLifecycle.Observer() {
@Override
public void onFinishedWakingUp() {
// Reset locked for the case keyguard not shown.
oneHanded.setLockedDisabled(false /* locked */, false /* enabled */);
}
@Override
public void onStartedGoingToSleep() {
oneHanded.stopOneHanded();
// When user press power button going to sleep, temperory lock OHM disabled
// to avoid mis-trigger.
oneHanded.setLockedDisabled(true /* locked */, false /* enabled */);
}
};
mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
mScreenLifecycle.addObserver(new ScreenLifecycle.Observer() {
@Override
public void onScreenTurningOff() {
oneHanded.stopOneHanded(
OneHandedUiEventLogger.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(
OneHandedUiEventLogger.EVENT_ONE_HANDED_TRIGGER_POP_IME_OUT);
}
}
});
}
@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(PrintWriter pw, String[] args) {
// Handle commands if provided
if (mShell.handleCommand(args, pw)) {
return;
}
// Handle logging commands if provided
if (handleLoggingCommand(args, pw)) {
return;
}
// Dump WMShell stuff here if no commands were handled
mShell.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(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;
}
}