blob: 52ffb46bb39c0a49847133937a207e4bc1d274df [file] [log] [blame]
/*
* Copyright (C) 2022 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.wm.shell.sysui;
import static android.content.pm.ActivityInfo.CONFIG_ASSETS_PATHS;
import static android.content.pm.ActivityInfo.CONFIG_FONT_SCALE;
import static android.content.pm.ActivityInfo.CONFIG_LAYOUT_DIRECTION;
import static android.content.pm.ActivityInfo.CONFIG_LOCALE;
import static android.content.pm.ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
import static android.content.pm.ActivityInfo.CONFIG_UI_MODE;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SYSUI_EVENTS;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.annotations.ExternalThread;
import java.io.PrintWriter;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* Handles event callbacks from SysUI that can be used within the Shell.
*/
public class ShellController {
private static final String TAG = ShellController.class.getSimpleName();
private final ShellInit mShellInit;
private final ShellCommandHandler mShellCommandHandler;
private final ShellExecutor mMainExecutor;
private final ShellInterfaceImpl mImpl = new ShellInterfaceImpl();
private final CopyOnWriteArrayList<ConfigurationChangeListener> mConfigChangeListeners =
new CopyOnWriteArrayList<>();
private final CopyOnWriteArrayList<KeyguardChangeListener> mKeyguardChangeListeners =
new CopyOnWriteArrayList<>();
private Configuration mLastConfiguration;
public ShellController(ShellInit shellInit, ShellCommandHandler shellCommandHandler,
ShellExecutor mainExecutor) {
mShellInit = shellInit;
mShellCommandHandler = shellCommandHandler;
mMainExecutor = mainExecutor;
}
/**
* Returns the external interface to this controller.
*/
public ShellInterface asShell() {
return mImpl;
}
/**
* Adds a new configuration listener. The configuration change callbacks are not made in any
* particular order.
*/
public void addConfigurationChangeListener(ConfigurationChangeListener listener) {
mConfigChangeListeners.remove(listener);
mConfigChangeListeners.add(listener);
}
/**
* Removes an existing configuration listener.
*/
public void removeConfigurationChangeListener(ConfigurationChangeListener listener) {
mConfigChangeListeners.remove(listener);
}
/**
* Adds a new Keyguard listener. The Keyguard change callbacks are not made in any
* particular order.
*/
public void addKeyguardChangeListener(KeyguardChangeListener listener) {
mKeyguardChangeListeners.remove(listener);
mKeyguardChangeListeners.add(listener);
}
/**
* Removes an existing Keyguard listener.
*/
public void removeKeyguardChangeListener(KeyguardChangeListener listener) {
mKeyguardChangeListeners.remove(listener);
}
@VisibleForTesting
void onConfigurationChanged(Configuration newConfig) {
// The initial config is send on startup and doesn't trigger listener callbacks
if (mLastConfiguration == null) {
mLastConfiguration = new Configuration(newConfig);
ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "Initial Configuration: %s", newConfig);
return;
}
final int diff = newConfig.diff(mLastConfiguration);
ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "New configuration change: %s", newConfig);
ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "\tchanges=%s",
Configuration.configurationDiffToString(diff));
final boolean densityFontScaleChanged = (diff & CONFIG_FONT_SCALE) != 0
|| (diff & ActivityInfo.CONFIG_DENSITY) != 0;
final boolean smallestScreenWidthChanged = (diff & CONFIG_SMALLEST_SCREEN_SIZE) != 0;
final boolean themeChanged = (diff & CONFIG_ASSETS_PATHS) != 0
|| (diff & CONFIG_UI_MODE) != 0;
final boolean localOrLayoutDirectionChanged = (diff & CONFIG_LOCALE) != 0
|| (diff & CONFIG_LAYOUT_DIRECTION) != 0;
// Update the last configuration and call listeners
mLastConfiguration.updateFrom(newConfig);
for (ConfigurationChangeListener listener : mConfigChangeListeners) {
listener.onConfigurationChanged(newConfig);
if (densityFontScaleChanged) {
listener.onDensityOrFontScaleChanged();
}
if (smallestScreenWidthChanged) {
listener.onSmallestScreenWidthChanged();
}
if (themeChanged) {
listener.onThemeChanged();
}
if (localOrLayoutDirectionChanged) {
listener.onLocaleOrLayoutDirectionChanged();
}
}
}
@VisibleForTesting
void onKeyguardVisibilityChanged(boolean visible, boolean occluded, boolean animatingDismiss) {
for (KeyguardChangeListener listener : mKeyguardChangeListeners) {
listener.onKeyguardVisibilityChanged(visible, occluded, animatingDismiss);
}
}
@VisibleForTesting
void onKeyguardDismissAnimationFinished() {
for (KeyguardChangeListener listener : mKeyguardChangeListeners) {
listener.onKeyguardDismissAnimationFinished();
}
}
public void dump(@NonNull PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
pw.println(prefix + TAG);
pw.println(innerPrefix + "mConfigChangeListeners=" + mConfigChangeListeners.size());
pw.println(innerPrefix + "mLastConfiguration=" + mLastConfiguration);
pw.println(innerPrefix + "mKeyguardChangeListeners=" + mKeyguardChangeListeners.size());
}
/**
* The interface for calls from outside the Shell, within the host process.
*/
@ExternalThread
private class ShellInterfaceImpl implements ShellInterface {
@Override
public void onInit() {
try {
mMainExecutor.executeBlocking(() -> mShellInit.init());
} catch (InterruptedException e) {
throw new RuntimeException("Failed to initialize the Shell in 2s", e);
}
}
@Override
public void dump(PrintWriter pw) {
try {
mMainExecutor.executeBlocking(() -> mShellCommandHandler.dump(pw));
} catch (InterruptedException e) {
throw new RuntimeException("Failed to dump the Shell in 2s", e);
}
}
@Override
public boolean handleCommand(String[] args, PrintWriter pw) {
try {
boolean[] result = new boolean[1];
mMainExecutor.executeBlocking(() -> {
result[0] = mShellCommandHandler.handleCommand(args, pw);
});
return result[0];
} catch (InterruptedException e) {
throw new RuntimeException("Failed to handle Shell command in 2s", e);
}
}
@Override
public void onConfigurationChanged(Configuration newConfiguration) {
mMainExecutor.execute(() ->
ShellController.this.onConfigurationChanged(newConfiguration));
}
@Override
public void onKeyguardVisibilityChanged(boolean visible, boolean occluded,
boolean animatingDismiss) {
mMainExecutor.execute(() ->
ShellController.this.onKeyguardVisibilityChanged(visible, occluded,
animatingDismiss));
}
@Override
public void onKeyguardDismissAnimationFinished() {
mMainExecutor.execute(() ->
ShellController.this.onKeyguardDismissAnimationFinished());
}
}
}