feat: Add option to clear home screen in settings (#6125)

Signed-off-by: abhixv <abhi.sharma1@hotmail.com>
This commit is contained in:
Abhishek Sharma
2026-01-10 20:51:01 +07:00
committed by Pun Butrach
parent 9898749619
commit 5f3a03f4fb
1577 changed files with 112563 additions and 80248 deletions
@@ -9,7 +9,6 @@ import app.lawnchair.compatlib.RemoteTransitionCompat
import app.lawnchair.compatlib.eleven.QuickstepCompatFactoryVR
import app.lawnchair.compatlib.fifteen.QuickstepCompatFactoryVV
import app.lawnchair.compatlib.fourteen.QuickstepCompatFactoryVU
import app.lawnchair.compatlib.sixteen.QuickstepCompatFactoryVBaklava
import app.lawnchair.compatlib.ten.QuickstepCompatFactoryVQ
import app.lawnchair.compatlib.thirteen.QuickstepCompatFactoryVT
import app.lawnchair.compatlib.twelve.QuickstepCompatFactoryVS
@@ -40,13 +39,8 @@ object LawnchairQuickstepCompat {
@JvmField
val ATLEAST_V: Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.BAKLAVA)
@JvmField
val ATLEAST_BAKLAVA: Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA
@JvmStatic
val factory: QuickstepCompatFactory = when {
ATLEAST_BAKLAVA -> QuickstepCompatFactoryVBaklava()
ATLEAST_V -> QuickstepCompatFactoryVV()
ATLEAST_U -> QuickstepCompatFactoryVU()
ATLEAST_T -> QuickstepCompatFactoryVT()
@@ -37,43 +37,51 @@ import kotlinx.coroutines.launch
* @param operand The [Evaluator.ConditionOperand] to apply to the conditions.
*/
@OptIn(ExperimentalCoroutinesApi::class)
class CombinedCondition(
class CombinedCondition
constructor(
private val scope: CoroutineScope,
private val conditions: Collection<Condition>,
@Evaluator.ConditionOperand private val operand: Int,
@Evaluator.ConditionOperand private val operand: Int
) : Condition(scope, null, false) {
private var job: Job? = null
private val _startStrategy by lazy { calculateStartStrategy() }
override suspend fun start() {
val groupedConditions = conditions.groupBy { it.isOverridingCondition }
override fun start() {
job =
scope.launch {
val groupedConditions = conditions.groupBy { it.isOverridingCondition }
lazilyEvaluate(
conditions = groupedConditions.getOrDefault(true, emptyList()),
filterUnknown = true,
)
.distinctUntilChanged()
.flatMapLatest { overriddenValue ->
// If there are overriding conditions with values set, they take precedence.
if (overriddenValue == null) {
lazilyEvaluate(
conditions = groupedConditions.getOrDefault(false, emptyList()),
filterUnknown = false,
lazilyEvaluate(
conditions = groupedConditions.getOrDefault(true, emptyList()),
filterUnknown = true
)
} else {
flowOf(overriddenValue)
}
}
.collect { conditionMet ->
if (conditionMet == null) {
clearCondition()
} else {
updateCondition(conditionMet)
}
.distinctUntilChanged()
.flatMapLatest { overriddenValue ->
// If there are overriding conditions with values set, they take precedence.
if (overriddenValue == null) {
lazilyEvaluate(
conditions = groupedConditions.getOrDefault(false, emptyList()),
filterUnknown = false
)
} else {
flowOf(overriddenValue)
}
}
.collect { conditionMet ->
if (conditionMet == null) {
clearCondition()
} else {
updateCondition(conditionMet)
}
}
}
}
override fun stop() {}
override fun stop() {
job?.cancel()
job = null
}
/**
* Evaluates a list of conditions lazily with support for short-circuiting. Conditions are
@@ -180,6 +188,7 @@ class CombinedCondition(
return startStrategy
}
override val startStrategy: Int
get() = _startStrategy
override fun getStartStrategy(): Int {
return _startStrategy
}
}
@@ -14,12 +14,12 @@ import kotlinx.coroutines.launch
fun Flow<Boolean>.toCondition(
scope: CoroutineScope,
@StartStrategy strategy: Int,
initialValue: Boolean? = null,
initialValue: Boolean? = null
): Condition {
return object : Condition(scope, initialValue, false) {
var job: Job? = null
override suspend fun start() {
override fun start() {
job = scope.launch { collect { updateCondition(it) } }
}
@@ -28,8 +28,7 @@ fun Flow<Boolean>.toCondition(
job = null
}
override val startStrategy: Int
get() = strategy
override fun getStartStrategy() = strategy
}
}
@@ -37,16 +36,13 @@ fun Flow<Boolean>.toCondition(
fun Condition.toFlow(): Flow<Boolean?> {
return callbackFlow {
val callback =
object : Condition.Callback {
override fun onConditionChanged(condition: Condition) {
if (condition.isConditionSet) {
trySend(condition.isConditionMet)
} else {
trySend(null)
}
Condition.Callback { condition ->
if (condition.isConditionSet) {
trySend(condition.isConditionMet)
} else {
trySend(null)
}
}
addCallback(callback)
callback.onConditionChanged(this@toFlow)
awaitClose { removeCallback(callback) }
@@ -128,20 +128,19 @@ public class PipSurfaceTransactionHelper {
mTmpDestinationRect.inset(insets);
// Scale by the shortest edge and offset such that the top/left of the scaled inset
// source rect aligns with the top/left of the destination bounds
final float scale = Math.max((float) destinationBounds.width() / sourceBounds.width(),
(float) destinationBounds.height() / sourceBounds.height());
final float scale = sourceBounds.width() <= sourceBounds.height()
? (float) destinationBounds.width() / sourceBounds.width()
: (float) destinationBounds.height() / sourceBounds.height();
mTmpTransform.setRotate(degree, 0, 0);
mTmpTransform.postScale(scale, scale);
final float cornerRadius = getScaledCornerRadius(mTmpDestinationRect, destinationBounds);
// adjust the positions, take account also the insets
final float adjustedPositionX, adjustedPositionY;
if (degree < 0) {
// Counter-clockwise rotation.
adjustedPositionX = positionX - insets.top * scale;
adjustedPositionX = positionX + insets.top * scale;
adjustedPositionY = positionY + insets.left * scale;
} else {
// Clockwise rotation.
adjustedPositionX = positionX + insets.top * scale;
adjustedPositionX = positionX - insets.top * scale;
adjustedPositionY = positionY - insets.left * scale;
}
tx.setMatrix(leash, mTmpTransform, mTmpFloat9)
@@ -27,10 +27,9 @@ public interface PluginEnabler {
int DISABLED_INVALID_VERSION = 2;
int DISABLED_FROM_EXPLICIT_CRASH = 3;
int DISABLED_FROM_SYSTEM_CRASH = 4;
int DISABLED_UNKNOWN = 100;
@IntDef({ENABLED, DISABLED_MANUALLY, DISABLED_INVALID_VERSION, DISABLED_FROM_EXPLICIT_CRASH,
DISABLED_FROM_SYSTEM_CRASH, DISABLED_UNKNOWN})
DISABLED_FROM_SYSTEM_CRASH})
@interface DisableReason {
}
@@ -32,9 +32,6 @@ import com.android.systemui.plugins.Plugin;
import com.android.systemui.plugins.PluginFragment;
import com.android.systemui.plugins.PluginLifecycleManager;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.PluginProtector;
import com.android.systemui.plugins.PluginWrapper;
import com.android.systemui.plugins.ProtectedPluginListener;
import dalvik.system.PathClassLoader;
@@ -52,8 +49,7 @@ import java.util.function.Supplier;
*
* @param <T> The type of plugin that this contains.
*/
public class PluginInstance<T extends Plugin>
implements PluginLifecycleManager, ProtectedPluginListener {
public class PluginInstance<T extends Plugin> implements PluginLifecycleManager {
private static final String TAG = "PluginInstance";
private final Context mAppContext;
@@ -62,7 +58,6 @@ public class PluginInstance<T extends Plugin>
private final PluginFactory<T> mPluginFactory;
private final String mTag;
private boolean mHasError = false;
private BiConsumer<String, String> mLogConsumer = null;
private Context mPluginContext;
private T mPlugin;
@@ -92,11 +87,6 @@ public class PluginInstance<T extends Plugin>
return mTag;
}
/** */
public boolean hasError() {
return mHasError;
}
public void setLogFunc(BiConsumer logConsumer) {
mLogConsumer = logConsumer;
}
@@ -107,22 +97,8 @@ public class PluginInstance<T extends Plugin>
}
}
@Override
public synchronized boolean onFail(String className, String methodName, Throwable failure) {
Log.e(TAG, "Failure from " + mPlugin + ". Disabling Plugin.");
mHasError = true;
unloadPlugin();
mListener.onPluginDetached(this);
return true;
}
/** Alerts listener and plugin that the plugin has been created. */
public synchronized void onCreate() {
if (mHasError) {
log("Previous Fatal Exception detected for plugin class");
return;
}
boolean loadPlugin = mListener.onPluginAttached(this);
if (!loadPlugin) {
if (mPlugin != null) {
@@ -133,17 +109,13 @@ public class PluginInstance<T extends Plugin>
}
if (mPlugin == null) {
log("onCreate: auto-load");
log("onCreate auto-load");
loadPlugin();
return;
}
if (!checkVersion()) {
log("onCreate: version check failed");
return;
}
log("onCreate: load callbacks");
mPluginFactory.checkVersion(mPlugin);
if (!(mPlugin instanceof PluginFragment)) {
// Only call onCreate for plugins that aren't fragments, as fragments
// will get the onCreate as part of the fragment lifecycle.
@@ -154,12 +126,6 @@ public class PluginInstance<T extends Plugin>
/** Alerts listener and plugin that the plugin is being shutdown. */
public synchronized void onDestroy() {
if (mHasError) {
// Detached in error handler
log("onDestroy - no-op");
return;
}
log("onDestroy");
unloadPlugin();
mListener.onPluginDetached(this);
@@ -168,37 +134,28 @@ public class PluginInstance<T extends Plugin>
/** Returns the current plugin instance (if it is loaded). */
@Nullable
public T getPlugin() {
return mHasError ? null : mPlugin;
return mPlugin;
}
/**
* Loads and creates the plugin if it does not exist.
*/
public synchronized void loadPlugin() {
if (mHasError) {
log("Previous Fatal Exception detected for plugin class");
return;
}
if (mPlugin != null) {
log("Load request when already loaded");
return;
}
// Both of these calls take about 1 - 1.5 seconds in test runs
mPlugin = mPluginFactory.createPlugin(this);
mPlugin = mPluginFactory.createPlugin();
mPluginContext = mPluginFactory.createPluginContext();
if (mPlugin == null || mPluginContext == null) {
Log.e(mTag, "Requested load, but failed");
return;
}
if (!checkVersion()) {
log("loadPlugin: version check failed");
return;
}
log("Loaded plugin; running callbacks");
mPluginFactory.checkVersion(mPlugin);
if (!(mPlugin instanceof PluginFragment)) {
// Only call onCreate for plugins that aren't fragments, as fragments
// will get the onCreate as part of the fragment lifecycle.
@@ -207,29 +164,6 @@ public class PluginInstance<T extends Plugin>
mListener.onPluginLoaded(mPlugin, mPluginContext, this);
}
/**
* Checks the plugin version, and permanently destroys the plugin instance on a failure
*/
private synchronized boolean checkVersion() {
if (mHasError) {
return false;
}
if (mPlugin == null) {
return true;
}
if (mPluginFactory.checkVersion(mPlugin)) {
return true;
}
Log.wtf(TAG, "Version check failed for " + mPlugin.getClass().getSimpleName());
mHasError = true;
unloadPlugin();
mListener.onPluginDetached(this);
return false;
}
/**
* Unloads and destroys the current plugin instance if it exists.
*
@@ -270,7 +204,7 @@ public class PluginInstance<T extends Plugin>
}
public VersionInfo getVersionInfo() {
return mPluginFactory.getVersionInfo(mPlugin);
return mPluginFactory.checkVersion(mPlugin);
}
@VisibleForTesting
@@ -361,19 +295,16 @@ public class PluginInstance<T extends Plugin>
/** Class that compares a plugin class against an implementation for version matching. */
public interface VersionChecker {
/** Compares two plugin classes. Returns true when match. */
<T extends Plugin> boolean checkVersion(
/** Compares two plugin classes. */
<T extends Plugin> VersionInfo checkVersion(
Class<T> instanceClass, Class<T> pluginClass, Plugin plugin);
/** Returns VersionInfo for the target class */
<T extends Plugin> VersionInfo getVersionInfo(Class<T> instanceclass);
}
/** Class that compares a plugin class against an implementation for version matching. */
public static class VersionCheckerImpl implements VersionChecker {
@Override
/** Compares two plugin classes. */
public <T extends Plugin> boolean checkVersion(
public <T extends Plugin> VersionInfo checkVersion(
Class<T> instanceClass, Class<T> pluginClass, Plugin plugin) {
VersionInfo pluginVersion = new VersionInfo().addClass(pluginClass);
VersionInfo instanceVersion = new VersionInfo().addClass(instanceClass);
@@ -382,17 +313,11 @@ public class PluginInstance<T extends Plugin>
} else if (plugin != null) {
int fallbackVersion = plugin.getVersion();
if (fallbackVersion != pluginVersion.getDefaultVersion()) {
return false;
throw new VersionInfo.InvalidVersionException("Invalid legacy version", false);
}
return null;
}
return true;
}
@Override
/** Returns the version info for the class */
public <T extends Plugin> VersionInfo getVersionInfo(Class<T> instanceClass) {
VersionInfo instanceVersion = new VersionInfo().addClass(instanceClass);
return instanceVersion.hasVersionInfo() ? instanceVersion : null;
return instanceVersion;
}
}
@@ -439,16 +364,20 @@ public class PluginInstance<T extends Plugin>
}
/** Creates the related plugin object from the factory */
public T createPlugin(ProtectedPluginListener listener) {
public T createPlugin() {
try {
ClassLoader loader = mClassLoaderFactory.get();
Class<T> instanceClass = (Class<T>) Class.forName(
mComponentName.getClassName(), true, loader);
T result = (T) mInstanceFactory.create(instanceClass);
Log.v(TAG, "Created plugin: " + result);
return PluginProtector.protectIfAble(result, listener);
} catch (ReflectiveOperationException ex) {
Log.wtf(TAG, "Failed to load plugin", ex);
return result;
} catch (ClassNotFoundException ex) {
Log.e(TAG, "Failed to load plugin", ex);
} catch (IllegalAccessException ex) {
Log.e(TAG, "Failed to load plugin", ex);
} catch (InstantiationException ex) {
Log.e(TAG, "Failed to load plugin", ex);
}
return null;
}
@@ -465,27 +394,13 @@ public class PluginInstance<T extends Plugin>
return null;
}
/** Check Version for the instance */
public boolean checkVersion(T instance) {
/** Check Version and create VersionInfo for instance */
public VersionInfo checkVersion(T instance) {
if (instance == null) {
instance = createPlugin(null);
}
if (instance instanceof PluginWrapper) {
instance = ((PluginWrapper<T>) instance).getPlugin();
instance = createPlugin();
}
return mVersionChecker.checkVersion(
(Class<T>) instance.getClass(), mPluginClass, instance);
}
/** Get Version Info for the instance */
public VersionInfo getVersionInfo(T instance) {
if (instance == null) {
instance = createPlugin(null);
}
if (instance instanceof PluginWrapper) {
instance = ((PluginWrapper<T>) instance).getPlugin();
}
return mVersionChecker.getVersionInfo((Class<T>) instance.getClass());
}
}
}
@@ -28,8 +28,6 @@ import android.util.ArraySet;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.Nullable;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.systemui.plugins.Plugin;
import com.android.systemui.plugins.PluginListener;
@@ -64,7 +62,7 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage
public PluginManagerImpl(Context context,
PluginActionManager.Factory actionManagerFactory,
boolean debuggable,
@Nullable UncaughtExceptionPreHandlerManager preHandlerManager,
UncaughtExceptionPreHandlerManager preHandlerManager,
PluginEnabler pluginEnabler,
PluginPrefs pluginPrefs,
List<String> privilegedPlugins) {
@@ -75,9 +73,7 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage
mPluginPrefs = pluginPrefs;
mPluginEnabler = pluginEnabler;
if (preHandlerManager != null) {
preHandlerManager.registerHandler(new PluginExceptionHandler());
}
preHandlerManager.registerHandler(new PluginExceptionHandler());
}
public boolean isDebuggable() {
@@ -21,7 +21,6 @@ import android.graphics.Insets;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.UserHandle;
import android.view.KeyEvent;
import android.view.MotionEvent;
import com.android.internal.util.ScreenshotRequest;
@@ -69,10 +68,10 @@ interface ISystemUiProxy {
/**
* Indicates that the given Assist invocation types should be handled by Launcher via
* LauncherProxy#onAssistantOverrideInvoked and should not be invoked by SystemUI.
* OverviewProxy#onAssistantOverrideInvoked and should not be invoked by SystemUI.
*
* @param invocationTypes The invocation types that will henceforth be handled via
* LauncherProxy (Launcher); other invocation types should be handled by SysUI.
* OverviewProxy (Launcher); other invocation types should be handled by SysUI.
*/
oneway void setAssistantOverridesRequested(in int[] invocationTypes) = 53;
@@ -103,9 +102,9 @@ interface ISystemUiProxy {
oneway void expandNotificationPanel() = 29;
/**
* Notifies SystemUI of a back KeyEvent.
* Notifies SystemUI to invoke Back.
*/
oneway void onBackEvent(in KeyEvent keyEvent) = 44;
oneway void onBackPressed() = 44;
/** Sets home rotation enabled. */
oneway void setHomeRotationEnabled(boolean enabled) = 45;
@@ -121,7 +120,7 @@ interface ISystemUiProxy {
oneway void notifyTaskbarAutohideSuspend(boolean suspend) = 48;
/**
* Notifies that the IME switcher button has been pressed.
* Notifies SystemUI to invoke IME Switcher.
*/
oneway void onImeSwitcherPressed() = 49;
@@ -168,15 +167,5 @@ interface ISystemUiProxy {
*/
oneway void toggleQuickSettingsPanel() = 56;
/**
* Notifies that the IME Switcher button has been long pressed.
*/
oneway void onImeSwitcherLongPress() = 57;
/**
* Updates contextual education stats when target gesture type is triggered.
*/
oneway void updateContextualEduStats(boolean isTrackpadGesture, String gestureType) = 58;
// Next id = 59
// Next id = 57
}
@@ -19,8 +19,8 @@ package com.android.systemui.shared.recents.model;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.wm.shell.shared.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
import static com.android.wm.shell.shared.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE;
import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE;
import android.app.ActivityManager;
import android.app.ActivityManager.TaskDescription;
@@ -72,25 +72,6 @@ public class Task {
@ViewDebug.ExportedProperty(category = "recents")
public final int displayId;
/**
* The component of the first activity in the task, can be considered the "application" of
* this task.
*/
@Nullable
public ComponentName baseActivity;
/**
* The number of activities in this task (including running).
*/
public int numActivities;
/**
* Whether the top activity is to be displayed. See {@link android.R.attr#windowNoDisplay}.
*/
public boolean isTopActivityNoDisplay;
/**
* Whether fillsParent() is false for every activity in the tasks stack.
*/
public boolean isActivityStackTransparent;
// The source component name which started this task
public final ComponentName sourceComponent;
@@ -109,10 +90,6 @@ public class Task {
this.userId = t.userId;
this.lastActiveTime = t.lastActiveTime;
this.displayId = t.displayId;
this.baseActivity = t.baseActivity;
this.numActivities = t.numActivities;
this.isTopActivityNoDisplay = t.isTopActivityNoDisplay;
this.isActivityStackTransparent = t.isActivityStackTransparent;
updateHashCode();
}
@@ -129,9 +106,7 @@ public class Task {
}
public TaskKey(int id, int windowingMode, @NonNull Intent intent,
ComponentName sourceComponent, int userId, long lastActiveTime, int displayId,
@Nullable ComponentName baseActivity, int numActivities,
boolean isTopActivityNoDisplay, boolean isActivityStackTransparent) {
ComponentName sourceComponent, int userId, long lastActiveTime, int displayId) {
this.id = id;
this.windowingMode = windowingMode;
this.baseIntent = intent;
@@ -139,10 +114,6 @@ public class Task {
this.userId = userId;
this.lastActiveTime = lastActiveTime;
this.displayId = displayId;
this.baseActivity = baseActivity;
this.numActivities = numActivities;
this.isTopActivityNoDisplay = isTopActivityNoDisplay;
this.isActivityStackTransparent = isActivityStackTransparent;
updateHashCode();
}
@@ -214,10 +185,6 @@ public class Task {
parcel.writeLong(lastActiveTime);
parcel.writeInt(displayId);
parcel.writeTypedObject(sourceComponent, flags);
parcel.writeTypedObject(baseActivity, flags);
parcel.writeInt(numActivities);
parcel.writeBoolean(isTopActivityNoDisplay);
parcel.writeBoolean(isActivityStackTransparent);
}
private static TaskKey readFromParcel(Parcel parcel) {
@@ -228,14 +195,9 @@ public class Task {
long lastActiveTime = parcel.readLong();
int displayId = parcel.readInt();
ComponentName sourceComponent = parcel.readTypedObject(ComponentName.CREATOR);
ComponentName baseActivity = parcel.readTypedObject(ComponentName.CREATOR);
int numActivities = parcel.readInt();
boolean isTopActivityNoDisplay = parcel.readBoolean();
boolean isActivityStackTransparent = parcel.readBoolean();
return new TaskKey(id, windowingMode, baseIntent, sourceComponent, userId,
lastActiveTime, displayId, baseActivity, numActivities, isTopActivityNoDisplay,
isActivityStackTransparent);
lastActiveTime, displayId);
}
@Override
@@ -256,7 +218,6 @@ public class Task {
@ViewDebug.ExportedProperty(category="recents")
public String title;
@ViewDebug.ExportedProperty(category="recents")
@Nullable
public String titleDescription;
@ViewDebug.ExportedProperty(category="recents")
public int colorPrimary;
@@ -281,11 +242,9 @@ public class Task {
public Rect appBounds;
@ViewDebug.ExportedProperty(category="recents")
public boolean isVisible;
@ViewDebug.ExportedProperty(category = "recents")
public boolean isMinimized;
// Last snapshot data, only used for recent tasks
public ActivityManager.RecentTaskInfo.PersistedTaskSnapshotData lastSnapshotData =
new ActivityManager.RecentTaskInfo.PersistedTaskSnapshotData();
public Task() {
// Do nothing
@@ -309,13 +268,6 @@ public class Task {
taskInfo.topActivity);
}
/**
* Creates a task object from the given [taskInfo].
*/
public static Task from(TaskInfo taskInfo) {
return from(new TaskKey(taskInfo), taskInfo, /* isLocked= */ false);
}
public Task(TaskKey key) {
this.key = key;
this.taskDescription = new TaskDescription();
@@ -324,10 +276,9 @@ public class Task {
public Task(Task other) {
this(other.key, other.colorPrimary, other.colorBackground, other.isDockable,
other.isLocked, other.taskDescription, other.topActivity);
lastSnapshotData.set(other.lastSnapshotData);
positionInParent = other.positionInParent;
appBounds = other.appBounds;
isVisible = other.isVisible;
isMinimized = other.isMinimized;
}
/**
@@ -355,10 +306,33 @@ public class Task {
: key.baseIntent.getComponent();
}
public void setLastSnapshotData(ActivityManager.RecentTaskInfo rawTask) {
lastSnapshotData.set(rawTask.lastSnapshotData);
}
public TaskKey getKey() {
return key;
}
/**
* Returns the visible width to height ratio. Returns 0f if snapshot data is not available.
*/
public float getVisibleThumbnailRatio(boolean clipInsets) {
if (lastSnapshotData.taskSize == null || lastSnapshotData.contentInsets == null) {
return 0f;
}
float availableWidth = lastSnapshotData.taskSize.x;
float availableHeight = lastSnapshotData.taskSize.y;
if (clipInsets) {
availableWidth -=
(lastSnapshotData.contentInsets.left + lastSnapshotData.contentInsets.right);
availableHeight -=
(lastSnapshotData.contentInsets.top + lastSnapshotData.contentInsets.bottom);
}
return availableWidth / availableHeight;
}
@Override
public boolean equals(Object o) {
if (o == this) {
@@ -374,11 +348,6 @@ public class Task {
return key.equals(t.key);
}
@Override
public int hashCode() {
return key.hashCode();
}
@Override
public String toString() {
return "[" + key.toString() + "] " + title;
@@ -10,6 +10,7 @@ import android.graphics.Rect;
import android.graphics.RectF;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.wm.shell.util.SplitBounds;
/**
* Utility class to position the thumbnail in the TaskView
@@ -34,6 +35,8 @@ public class PreviewPositionHelper {
private final Matrix mMatrix = new Matrix();
private boolean mIsOrientationChanged;
private SplitBounds mSplitBounds;
private int mDesiredStagePosition;
public Matrix getMatrix() {
return mMatrix;
@@ -47,6 +50,11 @@ public class PreviewPositionHelper {
return mIsOrientationChanged;
}
public void setSplitBounds(SplitBounds splitBounds, int desiredStagePosition) {
mSplitBounds = splitBounds;
mDesiredStagePosition = desiredStagePosition;
}
/**
* Updates the matrix based on the provided parameters
*/
@@ -208,19 +216,4 @@ public class PreviewPositionHelper {
}
mMatrix.postTranslate(translateX, translateY);
}
/**
* A factory that returns a new instance of the {@link PreviewPositionHelper}.
* <p>{@link PreviewPositionHelper} is a stateful helper, and hence when using it in distinct
* scenarios, prefer fetching an object using this factory</p>
* <p>Additionally, helpful for injecting mocks in tests</p>
*/
public static class PreviewPositionHelperFactory {
/**
* Returns a new {@link PreviewPositionHelper} for use in a distinct scenario.
*/
public PreviewPositionHelper create() {
return new PreviewPositionHelper();
}
}
}
@@ -16,19 +16,16 @@
package com.android.systemui.shared.recents.utilities;
import static android.app.StatusBarManager.NAVBAR_BACK_DISMISS_IME;
import static android.app.StatusBarManager.NAVBAR_IME_SWITCHER_BUTTON_VISIBLE;
import static android.app.StatusBarManager.NAVBAR_IME_VISIBLE;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT;
import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN;
import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN;
import android.annotation.TargetApi;
import android.app.StatusBarManager.NavbarFlags;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Rect;
import android.inputmethodservice.InputMethodService;
import android.inputmethodservice.InputMethodService.BackDispositionMode;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
@@ -36,9 +33,6 @@ import android.util.DisplayMetrics;
import android.view.Surface;
import android.view.WindowManager;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.utils.windowmanager.WindowManagerUtils;
/* Common code */
public class Utilities {
@@ -108,52 +102,44 @@ public class Utilities {
}
/**
* Updates the navigation bar state flags with the given IME state.
*
* @param oldFlags current navigation bar state flags.
* @param backDisposition the IME back disposition mode. Only takes effect if
* {@code isImeVisible} is {@code true}.
* @param isImeVisible whether the IME is currently visible.
* @param showImeSwitcher whether the IME Switcher button should be shown. Only takes effect if
* {@code isImeVisible} is {@code true}.
* @return updated set of flags from InputMethodService based off {@param oldHints}
* Leaves original hints unmodified
*/
@NavbarFlags
public static int updateNavbarFlagsFromIme(@NavbarFlags int oldFlags,
@BackDispositionMode int backDisposition, boolean isImeVisible,
boolean showImeSwitcher) {
int flags = oldFlags;
public static int calculateBackDispositionHints(int oldHints, int backDisposition,
boolean imeShown, boolean showImeSwitcher) {
int hints = oldHints;
switch (backDisposition) {
case InputMethodService.BACK_DISPOSITION_DEFAULT:
case InputMethodService.BACK_DISPOSITION_WILL_NOT_DISMISS:
case InputMethodService.BACK_DISPOSITION_WILL_DISMISS:
if (isImeVisible) {
flags |= NAVBAR_BACK_DISMISS_IME;
if (imeShown) {
hints |= NAVIGATION_HINT_BACK_ALT;
} else {
flags &= ~NAVBAR_BACK_DISMISS_IME;
hints &= ~NAVIGATION_HINT_BACK_ALT;
}
break;
case InputMethodService.BACK_DISPOSITION_ADJUST_NOTHING:
flags &= ~NAVBAR_BACK_DISMISS_IME;
hints &= ~NAVIGATION_HINT_BACK_ALT;
break;
}
if (isImeVisible) {
flags |= NAVBAR_IME_VISIBLE;
if (imeShown) {
hints |= NAVIGATION_HINT_IME_SHOWN;
} else {
flags &= ~NAVBAR_IME_VISIBLE;
hints &= ~NAVIGATION_HINT_IME_SHOWN;
}
if (showImeSwitcher && isImeVisible) {
flags |= NAVBAR_IME_SWITCHER_BUTTON_VISIBLE;
if (showImeSwitcher) {
hints |= NAVIGATION_HINT_IME_SWITCHER_SHOWN;
} else {
flags &= ~NAVBAR_IME_SWITCHER_BUTTON_VISIBLE;
hints &= ~NAVIGATION_HINT_IME_SWITCHER_SHOWN;
}
return flags;
return hints;
}
/** @return whether or not {@param context} represents that of a large screen device or not */
@TargetApi(Build.VERSION_CODES.R)
public static boolean isLargeScreen(Context context) {
return isLargeScreen(WindowManagerUtils.getWindowManager(context), context.getResources());
return isLargeScreen(context.getSystemService(WindowManager.class), context.getResources());
}
/** @return whether or not {@param context} represents that of a large screen device or not */
@@ -169,10 +155,4 @@ public class Utilities {
float densityRatio = (float) densityDpi / DisplayMetrics.DENSITY_DEFAULT;
return (size / densityRatio);
}
/** Whether a task is in freeform mode. */
public static boolean isFreeformTask(Task task) {
return task != null && task.getKey() != null
&& task.getKey().windowingMode == WINDOWING_MODE_FREEFORM;
}
}
@@ -35,6 +35,36 @@ import java.util.function.Consumer;
*/
public class RecentsTransition {
/**
* Creates a new transition aspect scaled transition activity options.
*/
public static ActivityOptions createAspectScaleAnimation(Context context, Handler handler,
boolean scaleUp, AppTransitionAnimationSpecsFuture animationSpecsFuture,
final Runnable animationStartCallback) {
final OnAnimationStartedListener animStartedListener = new OnAnimationStartedListener() {
private boolean mHandled;
@Override
public void onAnimationStarted(long elapsedRealTime) {
// OnAnimationStartedListener can be called numerous times, so debounce here to
// prevent multiple callbacks
if (mHandled) {
return;
}
mHandled = true;
if (animationStartCallback != null) {
animationStartCallback.run();
}
}
};
final ActivityOptions opts = ActivityOptions.makeMultiThumbFutureAspectScaleAnimation(
context, handler,
animationSpecsFuture != null ? animationSpecsFuture.getFuture() : null,
animStartedListener, scaleUp);
return opts;
}
/**
* Wraps a animation-start callback in a binder that can be called from window manager.
*/
@@ -136,9 +136,9 @@ constructor(
val sampledRegionWithOffset = convertBounds(screenLocationBounds)
if (
sampledRegionWithOffset.left < 0.0 ||
sampledRegionWithOffset.right > 1.0 ||
sampledRegionWithOffset.top < 0.0 ||
sampledRegionWithOffset.bottom > 1.0
sampledRegionWithOffset.right > 1.0 ||
sampledRegionWithOffset.top < 0.0 ||
sampledRegionWithOffset.bottom > 1.0
) {
if (DEBUG)
Log.d(
@@ -192,7 +192,7 @@ constructor(
pw.println("screen width: ${displaySize.x}, screen height: ${displaySize.y}")
pw.println(
"sampledRegionWithOffset: ${convertBounds(
calculateScreenLocation(sampledView) ?: RectF())}"
calculateScreenLocation(sampledView) ?: RectF())}"
)
pw.println(
"initialSampling for ${if (isLockscreen) "lockscreen" else "homescreen" }" +
@@ -39,7 +39,6 @@ import androidx.annotation.BoolRes;
import androidx.core.view.OneShotPreDrawListener;
import com.android.systemui.shared.rotation.FloatingRotationButtonPositionCalculator.Position;
import com.android.systemui.utils.windowmanager.WindowManagerUtils;
/**
* Containing logic for the rotation button on the physical left bottom corner of the screen.
@@ -89,7 +88,7 @@ public class FloatingRotationButton implements RotationButton {
@DimenRes int taskbarBottomMargin, @DimenRes int buttonDiameter,
@DimenRes int rippleMaxWidth, @BoolRes int floatingRotationBtnPositionLeftResource) {
mContext = context;
mWindowManager = WindowManagerUtils.getWindowManager(mContext);
mWindowManager = mContext.getSystemService(WindowManager.class);
mKeyButtonContainer = (ViewGroup) LayoutInflater.from(mContext).inflate(layout, null);
mKeyButtonView = mKeyButtonContainer.findViewById(keyButtonId);
mKeyButtonView.setVisibility(View.VISIBLE);
@@ -126,7 +125,6 @@ public class FloatingRotationButton implements RotationButton {
taskbarMarginLeft, taskbarMarginBottom, floatingRotationButtonPositionLeft);
final int diameter = res.getDimensionPixelSize(mButtonDiameterResource);
mKeyButtonView.setDiameter(diameter);
mContainerSize = diameter + Math.max(defaultMargin, Math.max(taskbarMarginLeft,
taskbarMarginBottom));
}
@@ -197,7 +195,6 @@ public class FloatingRotationButton implements RotationButton {
public void updateIcon(int lightIconColor, int darkIconColor) {
mAnimatedDrawable = (AnimatedVectorDrawable) mKeyButtonView.getContext()
.getDrawable(mRotationButtonController.getIconResId());
mAnimatedDrawable.setBounds(0, 0, mKeyButtonView.getWidth(), mKeyButtonView.getHeight());
mKeyButtonView.setImageDrawable(mAnimatedDrawable);
mKeyButtonView.setColors(lightIconColor, darkIconColor);
}
@@ -251,14 +248,8 @@ public class FloatingRotationButton implements RotationButton {
updateDimensionResources();
if (mIsShowing) {
updateIcon(mRotationButtonController.getLightIconColor(),
mRotationButtonController.getDarkIconColor());
final LayoutParams layoutParams = adjustViewPositionAndCreateLayoutParams();
mWindowManager.updateViewLayout(mKeyButtonContainer, layoutParams);
if (mAnimatedDrawable != null) {
mAnimatedDrawable.reset();
mAnimatedDrawable.start();
}
}
}
@@ -37,7 +37,6 @@ public class FloatingRotationButtonView extends ImageView {
private static final float BACKGROUND_ALPHA = 0.92f;
private KeyButtonRipple mRipple;
private int mDiameter;
private final Paint mOvalBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
private final Configuration mLastConfiguration;
@@ -94,25 +93,10 @@ public class FloatingRotationButtonView extends ImageView {
mRipple.setDarkIntensity(darkIntensity);
}
/**
* Sets the view's diameter.
*
* @param diameter the diameter value for the view
*/
void setDiameter(int diameter) {
mDiameter = diameter;
}
@Override
public void draw(Canvas canvas) {
int d = Math.min(getWidth(), getHeight());
canvas.drawOval(0, 0, d, d, mOvalBgPaint);
super.draw(canvas);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(mDiameter, mDiameter);
}
}
@@ -53,9 +53,6 @@ import android.view.accessibility.AccessibilityManager;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
@@ -145,14 +142,12 @@ public class RotationButtonController {
};
private final IRotationWatcher.Stub mRotationWatcher = new IRotationWatcher.Stub() {
@WorkerThread
@Override
public void onRotationChanged(final int rotation) {
@Nullable Boolean rotationLocked = RotationPolicyUtil.isRotationLocked(mContext);
// We need this to be scheduled as early as possible to beat the redrawing of
// window in response to the orientation change.
mMainThreadHandler.postAtFrontOfQueue(() -> {
onRotationWatcherChanged(rotation, rotationLocked);
onRotationWatcherChanged(rotation);
});
}
};
@@ -286,8 +281,8 @@ public class RotationButtonController {
TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
}
public void setRotationLockedAtAngle(
@Nullable Boolean isLocked, int rotationSuggestion, String caller) {
public void setRotationLockedAtAngle(int rotationSuggestion, String caller) {
final Boolean isLocked = isRotationLocked();
if (isLocked == null) {
// Ignore if we can't read the setting for the current user
return;
@@ -296,6 +291,21 @@ public class RotationButtonController {
/* rotation= */ rotationSuggestion, caller);
}
/**
* @return whether rotation is currently locked, or <code>null</code> if the setting couldn't
* be read
*/
public Boolean isRotationLocked() {
try {
return RotationPolicy.isRotationLocked(mContext);
} catch (SecurityException e) {
// TODO(b/279561841): RotationPolicy uses the current user to resolve the setting which
// may change before the rotation watcher can be unregistered
Log.e(TAG, "Failed to get isRotationLocked", e);
return null;
}
}
public void setRotateSuggestionButtonState(boolean visible) {
setRotateSuggestionButtonState(visible, false /* force */);
}
@@ -459,7 +469,7 @@ public class RotationButtonController {
* Called when the rotation watcher rotation changes, either from the watcher registered
* internally in this class, or a signal propagated from NavBarHelper.
*/
public void onRotationWatcherChanged(int rotation, @Nullable Boolean isRotationLocked) {
public void onRotationWatcherChanged(int rotation) {
if (!mListenersRegistered) {
// Ignore if not registered
return;
@@ -467,16 +477,17 @@ public class RotationButtonController {
// If the screen rotation changes while locked, potentially update lock to flow with
// new screen rotation and hide any showing suggestions.
if (isRotationLocked == null) {
Boolean rotationLocked = isRotationLocked();
if (rotationLocked == null) {
// Ignore if we can't read the setting for the current user
return;
}
// The isVisible check makes the rotation button disappear when we are not locked
// (e.g. for tabletop auto-rotate).
if (isRotationLocked || mRotationButton.isVisible()) {
if (rotationLocked || mRotationButton.isVisible()) {
// Do not allow a change in rotation to set user rotation when docked.
if (shouldOverrideUserLockPrefs(rotation) && isRotationLocked && !mDocked) {
setRotationLockedAtAngle(true, rotation, /* caller= */
if (shouldOverrideUserLockPrefs(rotation) && rotationLocked && !mDocked) {
setRotationLockedAtAngle(rotation, /* caller= */
"RotationButtonController#onRotationWatcherChanged");
}
setRotateSuggestionButtonState(false /* visible */, true /* forced */);
@@ -581,8 +592,7 @@ public class RotationButtonController {
private void onRotateSuggestionClick(View v) {
mUiEventLogger.log(RotationButtonEvent.ROTATION_SUGGESTION_ACCEPTED);
incrementNumAcceptedRotationSuggestionsIfNeeded();
setRotationLockedAtAngle(
RotationPolicyUtil.isRotationLocked(mContext), mLastRotationSuggestion,
setRotationLockedAtAngle(mLastRotationSuggestion,
/* caller= */ "RotationButtonController#onRotateSuggestionClick");
Log.i(TAG, "onRotateSuggestionClick() mLastRotationSuggestion=" + mLastRotationSuggestion);
v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
@@ -16,7 +16,6 @@
package com.android.systemui.shared.shadow
import android.content.Context
import android.content.res.TypedArray
import android.graphics.Canvas
import android.graphics.drawable.Drawable
import android.util.AttributeSet
@@ -32,23 +31,19 @@ constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
defStyleRes: Int = 0,
defStyleRes: Int = 0
) : TextView(context, attrs, defStyleAttr, defStyleRes) {
private lateinit var mKeyShadowInfo: ShadowInfo
private lateinit var mAmbientShadowInfo: ShadowInfo
private val mKeyShadowInfo: ShadowInfo
private val mAmbientShadowInfo: ShadowInfo
init {
updateShadowDrawables(
val attributes =
context.obtainStyledAttributes(
attrs,
R.styleable.DoubleShadowTextView,
defStyleAttr,
defStyleRes,
defStyleRes
)
)
}
private fun updateShadowDrawables(attributes: TypedArray) {
val drawableSize: Int
val drawableInsetSize: Int
try {
@@ -75,17 +70,17 @@ constructor(
ambientShadowBlur,
ambientShadowOffsetX,
ambientShadowOffsetY,
ambientShadowAlpha,
ambientShadowAlpha
)
drawableSize =
attributes.getDimensionPixelSize(
R.styleable.DoubleShadowTextView_drawableIconSize,
0,
0
)
drawableInsetSize =
attributes.getDimensionPixelSize(
R.styleable.DoubleShadowTextView_drawableIconInsetSize,
0,
0
)
} finally {
attributes.recycle()
@@ -100,19 +95,12 @@ constructor(
mAmbientShadowInfo,
drawable,
drawableSize,
drawableInsetSize,
drawableInsetSize
)
}
setCompoundDrawablesRelative(drawables[0], drawables[1], drawables[2], drawables[3])
}
override fun setTextAppearance(resId: Int) {
super.setTextAppearance(resId)
updateShadowDrawables(
context.obtainStyledAttributes(resId, R.styleable.DoubleShadowTextView)
)
}
public override fun onDraw(canvas: Canvas) {
applyShadows(mKeyShadowInfo, mAmbientShadowInfo, this, canvas) { super.onDraw(canvas) }
}
@@ -18,16 +18,13 @@ package com.android.systemui.shared.system;
import static android.app.ActivityManager.LOCK_TASK_MODE_LOCKED;
import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE;
import static android.app.ActivityTaskManager.getService;
import static app.lawnchair.compat.LawnchairQuickstepCompat.ATLEAST_R;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Activity;
import android.app.ActivityClient;
import android.app.ActivityManager;
import android.app.ActivityManager.RecentTaskInfo;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
@@ -41,7 +38,7 @@ import android.content.pm.UserInfo;
import android.graphics.Rect;
import android.os.Build;
import android.os.Bundle;
import android.os.DeadSystemException;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -49,6 +46,8 @@ import android.os.SystemClock;
import android.provider.Settings;
import android.util.Log;
import android.view.Display;
import android.view.IRecentsAnimationController;
import android.view.RemoteAnimationTarget;
import android.window.TaskSnapshot;
import com.android.internal.app.IVoiceInteractionManagerService;
@@ -59,6 +58,11 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.function.Consumer;
import app.lawnchair.compat.LawnchairQuickstepCompat;
import app.lawnchair.compatlib.RecentsAnimationRunnerCompat;
import app.lawnchair.compatlib.eleven.ActivityManagerCompatVR;
public class ActivityManagerWrapper {
@@ -83,17 +87,13 @@ public class ActivityManagerWrapper {
/**
* @return the current user's id.
*/
public int getCurrentUserId() throws DeadSystemException {
public int getCurrentUserId() {
UserInfo ui;
try {
ui = ActivityManager.getService().getCurrentUser();
return ui != null ? ui.id : 0;
} catch (RemoteException e) {
if (ATLEAST_R) {
throw e.rethrowFromSystemServer();
} else {
throw new DeadSystemException();
}
throw e.rethrowFromSystemServer();
}
}
@@ -104,6 +104,14 @@ public class ActivityManagerWrapper {
return getRunningTask(false /* filterVisibleRecents */);
}
/**
* @return a list of the recents tasks.
*/
@NonNull
public List<ActivityManager.RecentTaskInfo> getRecentTasks(int numTasks, int userId) {
return LawnchairQuickstepCompat.getActivityManagerCompat().getRecentTasks(numTasks, userId);
}
/**
* @return the top running task filtering only for tasks that can be visible in the recent tasks
* list (can be {@code null}).
@@ -212,13 +220,96 @@ public class ActivityManagerWrapper {
}
/**
* Preloads the recents activity. The caller should manage the thread on which this is called.
* Starts the recents activity. The caller should manage the thread on which this is called.
*/
public void preloadRecentsActivity(Intent intent) {
public void startRecentsActivity(Intent intent, long eventTime,
final RecentsAnimationListener animationHandler, final Consumer<Boolean> resultCallback,
Handler resultCallbackHandler) {
boolean result = startRecentsActivity(intent, eventTime, animationHandler);
if (resultCallback != null && resultCallbackHandler != null) {
resultCallbackHandler.post(new Runnable() {
@Override
public void run() {
resultCallback.accept(result);
}
});
}
}
/**
* Starts the recents activity. The caller should manage the thread on which this is called.
*/
public boolean startRecentsActivity(
Intent intent, long eventTime, RecentsAnimationListener animationHandler) {
try {
getService().preloadRecentsActivity(intent);
RecentsAnimationRunnerCompat runner = null;
if (animationHandler != null) {
runner = new RecentsAnimationRunnerCompat() {
@Override
public void onAnimationStart(IRecentsAnimationController controller,
RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
Rect homeContentInsets, Rect minimizedHomeBounds) {
final RecentsAnimationControllerCompat controllerCompat =
new RecentsAnimationControllerCompat(controller);
animationHandler.onAnimationStart(controllerCompat, apps,
wallpapers, homeContentInsets, minimizedHomeBounds, new Bundle());
}
@Override
public void onAnimationCanceled(int[] taskIds, TaskSnapshot[] taskSnapshots) {
animationHandler.onAnimationCanceled(
ThumbnailData.wrap(taskIds, taskSnapshots));
}
/**
* compat for android 12/11/10
*/
public void onAnimationCanceled(Object taskSnapshot) {
if (LawnchairQuickstepCompat.ATLEAST_S) {
animationHandler.onAnimationCanceled(
ThumbnailData.wrap(new int[]{0}, new TaskSnapshot[]{(TaskSnapshot) taskSnapshot}));
} else if (LawnchairQuickstepCompat.ATLEAST_R) {
ActivityManagerCompatVR compat = (ActivityManagerCompatVR) LawnchairQuickstepCompat.getActivityManagerCompat();
ActivityManagerCompatVR.ThumbnailData data = compat.convertTaskSnapshotToThumbnailData(taskSnapshot);
HashMap<Integer, ThumbnailData> thumbnailDatas = new HashMap<>();
if (data != null) {
thumbnailDatas.put(0, new ThumbnailData());
}
animationHandler.onAnimationCanceled(thumbnailDatas);
} else {
animationHandler.onAnimationCanceled(new HashMap<>());
}
}
/**
* compat for android 12/11
*/
public void onTaskAppeared(RemoteAnimationTarget app) {
animationHandler.onTasksAppeared(new RemoteAnimationTarget[]{app});
}
@Override
public void onTasksAppeared(RemoteAnimationTarget[] apps) {
animationHandler.onTasksAppeared(apps);
}
};
}
LawnchairQuickstepCompat.getActivityManagerCompat().startRecentsActivity(intent, eventTime, runner);
return true;
} catch (Exception e) {
Log.w(TAG, "Failed to preload recents activity", e);
return false;
}
}
/**
* Cancels the remote recents animation started from {@link #startRecentsActivity}.
*/
public void cancelRecentsAnimation(boolean restoreHomeRootTaskPosition) {
try {
getService().cancelRecentsAnimation(restoreHomeRootTaskPosition);
} catch (RemoteException e) {
Log.e(TAG, "Failed to cancel recents animation", e);
}
}
@@ -229,6 +320,24 @@ public class ActivityManagerWrapper {
return startActivityFromRecents(taskKey.id, options);
}
/**
* Preloads the recents activity. The caller should manage the thread on which this is called.
*/
public void preloadRecentsActivity(Intent intent) {
try {
Class<?> activityTaskManagerClass = Class.forName("android.app.ActivityTaskManager");
Method getServiceMethod = activityTaskManagerClass.getMethod("getService");
Object activityTaskManagerService = getServiceMethod.invoke(null);
Method preloadRecentsActivityMethod = activityTaskManagerService.getClass()
.getMethod("preloadRecentsActivity", Intent.class);
preloadRecentsActivityMethod.invoke(activityTaskManagerService, intent);
} catch (Throwable e) {
Log.w(TAG, "Failed to preload recents activity", e);
startRecentsActivity(intent, 0, null, null, null);
}
}
/**
* Starts a task from Recents synchronously.
*/
@@ -254,17 +363,6 @@ public class ActivityManagerWrapper {
}
}
/**
* Sets whether or not the specified task is perceptible.
*/
public boolean setTaskIsPerceptible(int taskId, boolean isPerceptible) {
try {
return getService().setTaskIsPerceptible(taskId, isPerceptible);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Removes a task by id.
*/
@@ -321,7 +419,7 @@ public class ActivityManagerWrapper {
* Shows a voice session identified by {@code token}
* @return true if the session was shown, false otherwise
*/
public boolean showVoiceSession(IBinder token, @NonNull Bundle args, int flags,
public boolean showVoiceSession(@NonNull IBinder token, @NonNull Bundle args, int flags,
@Nullable String attributionTag) {
IVoiceInteractionManagerService service = IVoiceInteractionManagerService.Stub.asInterface(
ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE));
@@ -356,9 +454,4 @@ public class ActivityManagerWrapper {
return info.configuration.windowConfiguration.getActivityType()
== WindowConfiguration.ACTIVITY_TYPE_HOME;
}
public boolean isRunningInTestHarness() {
return ActivityManager.isRunningInTestHarness()
|| ActivityManager.isRunningInUserTestHarness();
}
}
@@ -21,6 +21,8 @@ import static android.view.CrossWindowBlurListeners.CROSS_WINDOW_BLUR_SUPPORTED;
import android.app.ActivityManager;
import android.os.SystemProperties;
import app.lawnchair.compat.LawnchairQuickstepCompat;
public abstract class BlurUtils {
/**
@@ -29,7 +31,7 @@ public abstract class BlurUtils {
* @return {@code true} when supported.
*/
public static boolean supportsBlursOnWindows() {
return CROSS_WINDOW_BLUR_SUPPORTED && ActivityManager.isHighEndGfx()
return LawnchairQuickstepCompat.ATLEAST_R && CROSS_WINDOW_BLUR_SUPPORTED && ActivityManager.isHighEndGfx()
&& !SystemProperties.getBoolean("persist.sysui.disableBlur", false);
}
}
@@ -32,7 +32,7 @@ public final class InteractionJankMonitorWrapper {
* @param cujType the specific {@link Cuj.CujType}.
*/
public static void begin(View v, @Cuj.CujType int cujType) {
if (true) return; // LC-Ignored
if (true) return;
InteractionJankMonitor.getInstance().begin(v, cujType);
}
@@ -44,7 +44,7 @@ public final class InteractionJankMonitorWrapper {
* @param timeout duration to cancel the instrumentation in ms
*/
public static void begin(View v, @Cuj.CujType int cujType, long timeout) {
if (true) return; // LC-Ignored
if (true) return;
Configuration.Builder builder =
Configuration.Builder.withView(cujType, v)
.setTimeout(timeout);
@@ -59,7 +59,7 @@ public final class InteractionJankMonitorWrapper {
* @param tag the tag to distinguish different flow of same type CUJ.
*/
public static void begin(View v, @Cuj.CujType int cujType, String tag) {
if (true) return; // LC-Ignored
if (true) return;
Configuration.Builder builder =
Configuration.Builder.withView(cujType, v);
if (!TextUtils.isEmpty(tag)) {
@@ -74,7 +74,7 @@ public final class InteractionJankMonitorWrapper {
* @param cujType the specific {@link Cuj.CujType}.
*/
public static void end(@Cuj.CujType int cujType) {
if (true) return; // LC-Ignored
if (true) return;
InteractionJankMonitor.getInstance().end(cujType);
}
@@ -82,13 +82,13 @@ public final class InteractionJankMonitorWrapper {
* Cancel the trace session.
*/
public static void cancel(@Cuj.CujType int cujType) {
if (true) return; // LC-Ignored
if (true) return;
InteractionJankMonitor.getInstance().cancel(cujType);
}
/** Return true if currently instrumenting a trace session. */
public static boolean isInstrumenting(@Cuj.CujType int cujType) {
if (true) return true; // LC-Ignored
if (true) return true;
return InteractionJankMonitor.getInstance().isInstrumenting(cujType);
}
}
@@ -25,17 +25,9 @@ import static com.android.systemui.shared.Flags.shadeAllowBackGesture;
import android.annotation.LongDef;
import android.content.Context;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.IBinder;
import android.os.IInterface;
import android.os.RemoteException;
import android.util.Log;
import android.view.ViewConfiguration;
import android.view.WindowManagerPolicyConstants;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.policy.ScreenDecorationsUtils;
import java.lang.annotation.Retention;
@@ -49,7 +41,10 @@ import app.lawnchair.compat.LawnchairQuickstepCompat;
*/
public class QuickStepContract {
private static final String TAG = "QuickStepContract";
public static final String KEY_EXTRA_SYSUI_PROXY = "extra_sysui_proxy";
public static final String KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER = "extra_unfold_animation";
// See ISysuiUnlockAnimationController.aidl
public static final String KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER = "unlock_animation";
public static final String NAV_BAR_MODE_3BUTTON_OVERLAY =
WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY;
@@ -99,12 +94,12 @@ public class QuickStepContract {
public static final long SYSUI_STATE_ONE_HANDED_ACTIVE = 1L << 16;
// Allow system gesture no matter the system bar(s) is visible or not
public static final long SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY = 1L << 17;
// The IME is visible.
public static final long SYSUI_STATE_IME_VISIBLE = 1L << 18;
// The IME is showing
public static final long SYSUI_STATE_IME_SHOWING = 1L << 18;
// The window magnification is overlapped with system gesture insets at the bottom.
public static final long SYSUI_STATE_MAGNIFICATION_OVERLAP = 1L << 19;
// The IME Switcher button is visible.
public static final long SYSUI_STATE_IME_SWITCHER_BUTTON_VISIBLE = 1L << 20;
// ImeSwitcher is showing
public static final long SYSUI_STATE_IME_SWITCHER_SHOWING = 1L << 20;
// Device dozing/AOD state
public static final long SYSUI_STATE_DEVICE_DOZING = 1L << 21;
// The home feature is disabled (either by SUW/SysUI/device policy)
@@ -131,14 +126,6 @@ public class QuickStepContract {
public static final long SYSUI_STATE_SHORTCUT_HELPER_SHOWING = 1L << 32;
// Touchpad gestures are disabled
public static final long SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED = 1L << 33;
// PiP animation is running
public static final long SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING = 1L << 34;
// Communal hub is showing
public static final long SYSUI_STATE_COMMUNAL_HUB_SHOWING = 1L << 35;
// The back button is visually adjusted to indicate that it will dismiss the IME when pressed.
// This only takes effect while the IME is visible. By default, it is set while the IME is
// visible, but may be overridden by the backDispositionMode set by the IME.
public static final long SYSUI_STATE_BACK_DISMISS_IME = 1L << 36;
// Mask for SystemUiStateFlags to isolate SYSUI_STATE_AWAKE and
// SYSUI_STATE_WAKEFULNESS_TRANSITION, to match WAKEFULNESS_* constants
@@ -152,7 +139,7 @@ public class QuickStepContract {
SYSUI_STATE_WAKEFULNESS_TRANSITION | SYSUI_STATE_AWAKE;
// Whether the back gesture is allowed (or ignored) by the Shade
public static final boolean ALLOW_BACK_GESTURE_IN_SHADE = shadeAllowBackGesture();
public static final boolean ALLOW_BACK_GESTURE_IN_SHADE = false;
@Retention(RetentionPolicy.SOURCE)
@LongDef({SYSUI_STATE_SCREEN_PINNING,
@@ -173,9 +160,9 @@ public class QuickStepContract {
SYSUI_STATE_DIALOG_SHOWING,
SYSUI_STATE_ONE_HANDED_ACTIVE,
SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY,
SYSUI_STATE_IME_VISIBLE,
SYSUI_STATE_IME_SHOWING,
SYSUI_STATE_MAGNIFICATION_OVERLAP,
SYSUI_STATE_IME_SWITCHER_BUTTON_VISIBLE,
SYSUI_STATE_IME_SWITCHER_SHOWING,
SYSUI_STATE_DEVICE_DOZING,
SYSUI_STATE_BACK_DISABLED,
SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED,
@@ -188,9 +175,6 @@ public class QuickStepContract {
SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY,
SYSUI_STATE_SHORTCUT_HELPER_SHOWING,
SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED,
SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING,
SYSUI_STATE_COMMUNAL_HUB_SHOWING,
SYSUI_STATE_BACK_DISMISS_IME,
})
public @interface SystemUiStateFlags {}
@@ -250,14 +234,14 @@ public class QuickStepContract {
if ((flags & SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY) != 0) {
str.add("allow_gesture");
}
if ((flags & SYSUI_STATE_IME_VISIBLE) != 0) {
if ((flags & SYSUI_STATE_IME_SHOWING) != 0) {
str.add("ime_visible");
}
if ((flags & SYSUI_STATE_MAGNIFICATION_OVERLAP) != 0) {
str.add("magnification_overlap");
}
if ((flags & SYSUI_STATE_IME_SWITCHER_BUTTON_VISIBLE) != 0) {
str.add("ime_switcher_button_visible");
if ((flags & SYSUI_STATE_IME_SWITCHER_SHOWING) != 0) {
str.add("ime_switcher_showing");
}
if ((flags & SYSUI_STATE_DEVICE_DOZING) != 0) {
str.add("device_dozing");
@@ -295,15 +279,6 @@ public class QuickStepContract {
if ((flags & SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED) != 0) {
str.add("touchpad_gestures_disabled");
}
if ((flags & SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING) != 0) {
str.add("disable_gesture_pip_animating");
}
if ((flags & SYSUI_STATE_COMMUNAL_HUB_SHOWING) != 0) {
str.add("communal_hub_showing");
}
if ((flags & SYSUI_STATE_BACK_DISMISS_IME) != 0) {
str.add("back_dismiss_ime");
}
return str.toString();
}
@@ -360,15 +335,6 @@ public class QuickStepContract {
|| (sysuiStateFlags & SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING) != 0) {
return false;
}
// Disable back gesture on the hub, but not when the shade is showing.
if ((sysuiStateFlags & SYSUI_STATE_COMMUNAL_HUB_SHOWING) != 0) {
// Use QS expanded signal as the notification panel is always considered visible
// expanded when on the lock screen and when opening hub over lock screen. This does
// mean that back gesture is disabled when opening shade over hub while in portrait
// mode, since QS is not expanded.
// TODO(b/370108274): allow back gesture on shade over hub in portrait
return (sysuiStateFlags & SYSUI_STATE_QUICK_SETTINGS_EXPANDED) == 0;
}
if ((sysuiStateFlags & SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY) != 0) {
sysuiStateFlags &= ~SYSUI_STATE_NAV_BAR_HIDDEN;
}
@@ -412,18 +378,21 @@ public class QuickStepContract {
return mode == NAV_BAR_MODE_3BUTTON;
}
// LC-specific
public static boolean sRecentsDisabled = false;
public static boolean sHasCustomCornerRadius = false;
public static float sCustomCornerRadius = 0f;
/**
* Corner radius that should be used on windows in order to cover the display.
* These values are expressed in pixels because they should not respect display or font
* scaling. The corner radius may change when folding/unfolding the device.
*/
public static boolean sRecentsDisabled = false;
public static boolean sHasCustomCornerRadius = false;
public static float sCustomCornerRadius = 0f;
/**
* Corner radius that should be used on windows in order to cover the display.
* These values are expressed in pixels because they should not respect display or font
* scaling, this means that we don't have to reload them on config changes.
*/
public static float getWindowCornerRadius(Context context) {
// LC-Wrapped
if (sRecentsDisabled || !LawnchairQuickstepCompat.ATLEAST_S) {
return 0;
}
@@ -441,27 +410,11 @@ public class QuickStepContract {
* If live rounded corners are supported on windows.
*/
public static boolean supportsRoundedCornersOnWindows(Resources resources) {
// LC-Wrapped
try {
return ScreenDecorationsUtils.supportsRoundedCornersOnWindows(resources);
} catch (Throwable t) {
return false;
}
}
/**
* Adds the provided interface to the bundle using the interface descriptor as the key
*/
public static void addInterface(@Nullable IInterface iInterface, @NonNull Bundle out) {
if (iInterface != null) {
IBinder binder = iInterface.asBinder();
if (binder != null) {
try {
out.putIBinder(binder.getInterfaceDescriptor(), binder);
} catch (RemoteException e) {
Log.d(TAG, "Invalid interface description " + binder, e);
}
}
}
}
}
@@ -19,13 +19,13 @@ package com.android.systemui.shared.system;
import android.os.Build;
import android.os.RemoteException;
import android.util.Log;
import android.view.RemoteAnimationTarget;
import android.view.IRecentsAnimationController;
import android.view.SurfaceControl;
import android.window.PictureInPictureSurfaceTransaction;
import android.window.WindowAnimationState;
import android.window.TaskSnapshot;
import com.android.internal.os.IResultReceiver;
import com.android.wm.shell.recents.IRecentsAnimationController;
import com.android.systemui.shared.recents.model.ThumbnailData;
import java.lang.reflect.Method;
@@ -41,6 +41,18 @@ public class RecentsAnimationControllerCompat {
mAnimationController = animationController;
}
public ThumbnailData screenshotTask(int taskId) {
try {
final TaskSnapshot snapshot = mAnimationController.screenshotTask(taskId);
if (snapshot != null) {
return ThumbnailData.fromSnapshot(snapshot);
}
} catch (RemoteException e) {
Log.e(TAG, "Failed to screenshot task", e);
}
return new ThumbnailData();
}
public void setInputConsumerEnabled(boolean enabled) {
try {
mAnimationController.setInputConsumerEnabled(enabled);
@@ -49,6 +61,14 @@ public class RecentsAnimationControllerCompat {
}
}
public void setAnimationTargetsBehindSystemBars(boolean behindSystemBars) {
try {
mAnimationController.setAnimationTargetsBehindSystemBars(behindSystemBars);
} catch (RemoteException e) {
Log.e(TAG, "Failed to set whether animation targets are behind system bars", e);
}
}
/**
* Sets the final surface transaction on a Task. This is used by Launcher to notify the system
* that animating Activity to PiP has completed and the associated task surface should be
@@ -103,6 +123,22 @@ public class RecentsAnimationControllerCompat {
}
}
public void setDeferCancelUntilNextTransition(boolean defer, boolean screenshot) {
try {
mAnimationController.setDeferCancelUntilNextTransition(defer, screenshot);
} catch (RemoteException e) {
Log.e(TAG, "Failed to set deferred cancel with screenshot", e);
}
}
public void cleanupScreenshot() {
try {
mAnimationController.cleanupScreenshot();
} catch (RemoteException e) {
Log.e(TAG, "Failed to clean up screenshot of recents animation", e);
}
}
/**
* @see {{@link IRecentsAnimationController#setWillFinishToHome(boolean)}}.
*/
@@ -115,13 +151,14 @@ public class RecentsAnimationControllerCompat {
}
/**
* @see IRecentsAnimationController#handOffAnimation
* @see IRecentsAnimationController#removeTask
*/
public void handOffAnimation(RemoteAnimationTarget[] targets, WindowAnimationState[] states) {
public boolean removeTask(int taskId) {
try {
mAnimationController.handOffAnimation(targets, states);
return mAnimationController.removeTask(taskId);
} catch (RemoteException e) {
Log.e(TAG, "Failed to hand off animation", e);
Log.e(TAG, "Failed to remove remote animation target", e);
return false;
}
}
@@ -135,4 +172,15 @@ public class RecentsAnimationControllerCompat {
Log.e(TAG, "Failed to detach the navigation bar from app", e);
}
}
/**
* @see IRecentsAnimationController#animateNavigationBarToApp(long)
*/
public void animateNavigationBarToApp(long duration) {
try {
mAnimationController.animateNavigationBarToApp(duration);
} catch (RemoteException e) {
Log.e(TAG, "Failed to animate the navigation bar to app", e);
}
}
}
@@ -16,10 +16,10 @@
package com.android.systemui.shared.system;
import android.annotation.Nullable;
import android.graphics.Rect;
import android.os.Bundle;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -32,7 +32,13 @@ public interface RecentsAnimationListener {
*/
void onAnimationStart(RecentsAnimationControllerCompat controller,
RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
Rect homeContentInsets, Rect minimizedHomeBounds, Bundle extras, TransitionInfo info);
Rect homeContentInsets, Rect minimizedHomeBounds, Bundle extras);
// Introduced in NothingOS 2.5.5, needed in 2.6
void onAnimationStart(RecentsAnimationControllerCompat controller,
TransitionInfo transitionInfo, SurfaceControl.Transaction transaction,
RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
Rect homeContentInsets, Rect minimizedHomeBounds);
/**
* Called when the animation into Recents was canceled. This call is made on the binder thread.
@@ -43,5 +49,15 @@ public interface RecentsAnimationListener {
* Called when the task of an activity that has been started while the recents animation
* was running becomes ready for control.
*/
void onTasksAppeared(RemoteAnimationTarget[] app, @Nullable TransitionInfo transitionInfo);
void onTasksAppeared(RemoteAnimationTarget[] app);
/**
* Called to request that the current task tile be switched out for a screenshot (if not
* already). Once complete, onFinished should be called.
* @return true if this impl will call onFinished. No other onSwitchToScreenshot impls will
* be called afterwards (to avoid multiple calls to onFinished).
*/
default boolean onSwitchToScreenshot(Runnable onFinished) {
return false;
}
}
@@ -36,6 +36,8 @@ import com.android.systemui.shared.recents.model.ThumbnailData;
import java.util.ArrayList;
import java.util.List;
import app.lawnchair.compat.LawnchairQuickstepCompat;
/**
* Tracks all the task stack listeners
*/
@@ -167,9 +169,10 @@ public class TaskStackChangeListeners {
if (!mRegistered) {
// Register mTaskStackListener to IActivityManager only once if needed.
try {
if (!LawnchairQuickstepCompat.ATLEAST_V) return;
ActivityTaskManager.getService().registerTaskStackListener(this);
mRegistered = true;
} catch (Exception e) {
} catch (Throwable e) {
Log.w(TAG, "Failed to call registerTaskStackListener", e);
}
}
@@ -184,9 +187,10 @@ public class TaskStackChangeListeners {
if (isEmpty && mRegistered) {
// Unregister mTaskStackListener once we have no more listeners
try {
if (!LawnchairQuickstepCompat.ATLEAST_V) return;
ActivityTaskManager.getService().unregisterTaskStackListener(this);
mRegistered = false;
} catch (Exception e) {
} catch (Throwable e) {
Log.w(TAG, "Failed to call unregisterTaskStackListener", e);
}
}
@@ -28,7 +28,14 @@ class UncaughtExceptionPreHandlerManager @Inject constructor() {
* Verifies that the global handler is set in Thread. If not, sets is up.
*/
private fun checkGlobalHandlerSetup() {
// LC-Ignored
val currentHandler = Thread.getDefaultUncaughtExceptionHandler()
if (currentHandler != globalUncaughtExceptionPreHandler) {
if (currentHandler is GlobalUncaughtExceptionHandler) {
throw IllegalStateException("Two UncaughtExceptionPreHandlerManagers created")
}
currentHandler?.let { addHandler(it) }
Thread.setDefaultUncaughtExceptionHandler(globalUncaughtExceptionPreHandler)
}
}
/**
@@ -61,4 +68,4 @@ class UncaughtExceptionPreHandlerManager @Inject constructor() {
handleUncaughtException(thread, throwable)
}
}
}
}