fix: Conflict incoming changes from 15-dev

This commit is contained in:
Pun Butrach
2025-11-22 17:14:31 +07:00
parent 14e352c827
commit 2e76c99dad
1726 changed files with 146971 additions and 95028 deletions
@@ -33,11 +33,22 @@ class DisableSubpixelTextTransitionListener(private val rootView: ViewGroup?) :
override fun onTransitionStarted() {
isTransitionInProgress = true
getAllChildTextView(rootView, childrenTextViews)
childrenTextViews.forEach { child ->
val childTextView = child.get() ?: return@forEach
childTextView.paintFlags = childTextView.paintFlags or Paint.SUBPIXEL_TEXT_FLAG
}
}
override fun onTransitionFinished() {
if (!isTransitionInProgress) return
isTransitionInProgress = false
childrenTextViews.forEach { child ->
val childTextView = child.get() ?: return@forEach
childTextView.paintFlags =
childTextView.paintFlags and Paint.SUBPIXEL_TEXT_FLAG.inv()
}
childrenTextViews.clear()
}
/**
@@ -19,6 +19,7 @@ package com.android.systemui.shared.navigationbar;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.companion.virtualdevice.flags.Flags;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.CanvasProperty;
@@ -80,6 +81,7 @@ public class KeyButtonRipple extends Drawable {
private final Interpolator mInterpolator = new LogInterpolator();
private boolean mSupportHardware;
private final View mTargetView;
private final int mTapTimeoutMillis;
private final Handler mHandler = new Handler();
private final HashSet<Animator> mRunningAnimations = new HashSet<>();
@@ -101,6 +103,9 @@ public class KeyButtonRipple extends Drawable {
mMaxWidthResource = maxWidthResource;
mMaxWidth = ctx.getResources().getDimensionPixelSize(maxWidthResource);
mTargetView = targetView;
mTapTimeoutMillis = Flags.viewconfigurationApis()
? ViewConfiguration.get(mTargetView.getContext()).getTapTimeoutMillis()
: ViewConfiguration.getTapTimeout();
}
public void updateResources() {
@@ -338,7 +343,7 @@ public class KeyButtonRipple extends Drawable {
if (mDelayTouchFeedback) {
if (mRunningAnimations.isEmpty()) {
mHandler.removeCallbacksAndMessages(null);
mHandler.postDelayed(this::enterSoftware, ViewConfiguration.getTapTimeout());
mHandler.postDelayed(this::enterSoftware, mTapTimeoutMillis);
} else if (mVisible) {
enterSoftware();
}
@@ -383,7 +388,7 @@ public class KeyButtonRipple extends Drawable {
if (mDelayTouchFeedback) {
if (mRunningAnimations.isEmpty()) {
mHandler.removeCallbacksAndMessages(null);
mHandler.postDelayed(this::enterHardware, ViewConfiguration.getTapTimeout());
mHandler.postDelayed(this::enterHardware, mTapTimeoutMillis);
} else if (mVisible) {
enterHardware();
}
@@ -31,11 +31,13 @@ import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.net.Uri;
import android.util.ArraySet;
import android.util.Log;
import android.view.LayoutInflater;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.systemui.log.LogcatOnlyMessageBuffer;
import com.android.systemui.log.core.LogLevel;
import com.android.systemui.log.core.Logger;
import com.android.systemui.plugins.Plugin;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.PluginManager;
@@ -55,8 +57,6 @@ import java.util.concurrent.Executor;
*/
public class PluginActionManager<T extends Plugin> {
private static final boolean DEBUG = false;
private static final String TAG = "PluginActionManager";
public static final String PLUGIN_PERMISSION = "com.android.systemui.permission.PLUGIN";
@@ -76,6 +76,7 @@ public class PluginActionManager<T extends Plugin> {
private final Class<T> mPluginClass;
private final Executor mMainExecutor;
private final Executor mBgExecutor;
private final Logger mLogger;
private PluginActionManager(
Context context,
@@ -104,17 +105,21 @@ public class PluginActionManager<T extends Plugin> {
mPluginInstanceFactory = pluginInstanceFactory;
mPrivilegedPlugins.addAll(privilegedPlugins);
mIsDebuggable = debuggable;
String tag = String.format("%s[%s]", TAG, mAction);
mLogger = new Logger(mListener.getLogBuffer() != null ? mListener.getLogBuffer() :
new LogcatOnlyMessageBuffer(LogLevel.WARNING), tag);
}
/** Load all plugins matching this instance's action. */
public void loadAll() {
if (DEBUG) Log.d(TAG, "startListening");
mLogger.d("startListening");
mBgExecutor.execute(() -> queryAll());
}
/** Unload all plugins managed by this instance. */
public void destroy() {
if (DEBUG) Log.d(TAG, "stopListening");
mLogger.d("stopListening");
ArrayList<PluginInstance<T>> plugins = new ArrayList<>(mPluginInstances);
for (PluginInstance<T> plugInstance : plugins) {
mMainExecutor.execute(() -> onPluginDisconnected(plugInstance));
@@ -185,7 +190,7 @@ public class PluginActionManager<T extends Plugin> {
// Don't disable privileged plugins as they are a part of the OS.
return false;
}
Log.w(TAG, "Disabling plugin " + pluginComponent.flattenToShortString());
logFmt(LogLevel.WARNING, "Disabling plugin: %s", pluginComponent.flattenToShortString());
mPluginEnabler.setDisabled(pluginComponent, reason);
return true;
@@ -208,18 +213,18 @@ public class PluginActionManager<T extends Plugin> {
}
private void onPluginConnected(PluginInstance<T> pluginInstance) {
if (DEBUG) Log.d(TAG, "onPluginConnected");
mLogger.d("onPluginConnected");
PluginPrefs.setHasPlugins(mContext);
pluginInstance.onCreate();
}
private void onPluginDisconnected(PluginInstance<T> pluginInstance) {
if (DEBUG) Log.d(TAG, "onPluginDisconnected");
mLogger.d("onPluginDisconnected");
pluginInstance.onDestroy();
}
private void queryAll() {
if (DEBUG) Log.d(TAG, "queryAll " + mAction);
mLogger.d("queryAll");
for (int i = mPluginInstances.size() - 1; i >= 0; i--) {
PluginInstance<T> pluginInstance = mPluginInstances.get(i);
mMainExecutor.execute(() -> onPluginDisconnected(pluginInstance));
@@ -239,11 +244,11 @@ public class PluginActionManager<T extends Plugin> {
}
private void queryPkg(String pkg) {
if (DEBUG) Log.d(TAG, "queryPkg " + pkg + " " + mAction);
logFmt(LogLevel.DEBUG, "queryPkg(%s)", pkg);
if (mAllowMultiple || (mPluginInstances.size() == 0)) {
handleQueryPlugins(pkg);
} else {
if (DEBUG) Log.d(TAG, "Too many of " + mAction);
mLogger.d("Too many matching packages found");
}
}
@@ -255,23 +260,23 @@ public class PluginActionManager<T extends Plugin> {
intent.setPackage(pkgName);
}
List<ResolveInfo> result = mPm.queryIntentServices(intent, 0);
if (DEBUG) {
Log.d(TAG, "Found " + result.size() + " plugins");
for (ResolveInfo info : result) {
ComponentName name = new ComponentName(info.serviceInfo.packageName,
info.serviceInfo.name);
Log.d(TAG, " " + name);
}
}
StringBuilder logSb = new StringBuilder();
logSb.append("Found ");
logSb.append(result.size());
logSb.append(" plugins");
if (result.size() > 1 && !mAllowMultiple) {
// TODO: Show warning.
Log.w(TAG, "Multiple plugins found for " + mAction);
logSb.append(", but multiple plugins are disallowed.");
logFmt(LogLevel.WARNING, "%s", logSb);
return;
}
for (ResolveInfo info : result) {
ComponentName name = new ComponentName(info.serviceInfo.packageName,
info.serviceInfo.name);
logSb.append("\n " + name);
PluginInstance<T> pluginInstance = loadPluginComponent(name);
if (pluginInstance != null) {
// add plugin before sending PLUGIN_CONNECTED message
@@ -279,6 +284,7 @@ public class PluginActionManager<T extends Plugin> {
mMainExecutor.execute(() -> onPluginConnected(pluginInstance));
}
}
logFmt(LogLevel.DEBUG, "%s", logSb);
}
private PluginInstance<T> loadPluginComponent(ComponentName component) {
@@ -286,13 +292,11 @@ public class PluginActionManager<T extends Plugin> {
// use these on production builds.
if (!mIsDebuggable && !isPluginPrivileged(component)) {
// Never ever ever allow these on production builds, they are only for prototyping.
Log.w(TAG, "Plugin cannot be loaded on production build: " + component);
logFmt(LogLevel.ERROR, "Plugin cannot be loaded on production build: %s", component);
return null;
}
if (!mPluginEnabler.isEnabled(component)) {
if (DEBUG) {
Log.d(TAG, "Plugin is not enabled, aborting load: " + component);
}
logFmt(LogLevel.WARNING, "Plugin is not enabled, aborting load: %s", component);
return null;
}
String packageName = component.getPackageName();
@@ -300,16 +304,15 @@ public class PluginActionManager<T extends Plugin> {
// TODO: This probably isn't needed given that we don't have IGNORE_SECURITY on
if (mPm.checkPermission(PLUGIN_PERMISSION, packageName)
!= PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "Plugin doesn't have permission: " + packageName);
logFmt(LogLevel.ERROR, "Plugin doesn't have permission: %s", packageName);
return null;
}
ApplicationInfo appInfo = mPm.getApplicationInfo(packageName, 0);
// TODO: Only create the plugin before version check if we need it for
// legacy version check.
if (DEBUG) {
Log.d(TAG, "createPlugin: " + component);
}
// TODO: Only create the plugin before version
// check if we need it for legacy version check.
logFmt(LogLevel.DEBUG, "createPlugin: %s", component);
try {
return mPluginInstanceFactory.create(
mContext, appInfo, component,
@@ -318,7 +321,7 @@ public class PluginActionManager<T extends Plugin> {
reportInvalidVersion(component, component.getClassName(), e);
}
} catch (Throwable e) {
Log.w(TAG, "Couldn't load plugin: " + component, e);
logFmt(LogLevel.ERROR, "Couldn't load plugin: %s", component, e);
return null;
}
@@ -363,12 +366,31 @@ public class PluginActionManager<T extends Plugin> {
nb.addAction(new Action.Builder(null, "Disable plugin", pi).build());
mNotificationManager.notify(SystemMessage.NOTE_PLUGIN, nb.build());
// TODO: Warn user.
Log.w(TAG, "Error loading plugin; " + e.getMessage());
mLogger.e("Error loading plugin", e);
}
/**
* Construct a {@link PluginActionManager}
*/
/** Format a log message */
public void logFmt(LogLevel level, String formatStr, Object innerObj) {
logFmt(level, formatStr, innerObj, null);
}
/** Format a log message */
public void logFmt(LogLevel level, String formatStr, Object innerObj, Throwable ex) {
logFmt(mLogger, level, formatStr, innerObj, ex);
}
/** Format a log message */
public static void logFmt(Logger logger, LogLevel level,
String formatStr, Object innerObj, Throwable ex) {
logger.log(
level, msg -> String.format(formatStr, msg.getStr1()),
ex, msg -> {
msg.setStr1(innerObj.toString());
return kotlin.Unit.INSTANCE;
});
}
/** Construct a {@link PluginActionManager} */
public static class Factory {
private final Context mContext;
private final PackageManager mPackageManager;
@@ -403,7 +425,7 @@ public class PluginActionManager<T extends Plugin> {
}
}
/** */
/** Wrapper for PluginInstance contexts */
public static class PluginContextWrapper extends ContextWrapper {
private final ClassLoader mClassLoader;
private LayoutInflater mInflater;
@@ -28,6 +28,9 @@ import android.util.Log;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.log.LogcatOnlyMessageBuffer;
import com.android.systemui.log.core.LogLevel;
import com.android.systemui.log.core.Logger;
import com.android.systemui.plugins.Plugin;
import com.android.systemui.plugins.PluginFragment;
import com.android.systemui.plugins.PluginLifecycleManager;
@@ -61,13 +64,14 @@ public class PluginInstance<T extends Plugin>
private final ComponentName mComponentName;
private final PluginFactory<T> mPluginFactory;
private final String mTag;
private final Logger mLogger;
private boolean mHasError = false;
private BiConsumer<String, String> mLogConsumer = null;
private Context mPluginContext;
private T mPlugin;
/** */
/** Constructor */
public PluginInstance(
Context appContext,
PluginListener<T> listener,
@@ -79,8 +83,9 @@ public class PluginInstance<T extends Plugin>
mComponentName = componentName;
mPluginFactory = pluginFactory;
mPlugin = plugin;
mTag = TAG + "[" + mComponentName.getShortClassName() + "]"
+ '@' + Integer.toHexString(hashCode());
mTag = String.format("%s[%s]@%h", TAG, mComponentName.getShortClassName(), hashCode());
mLogger = new Logger(mListener.getLogBuffer() != null ? mListener.getLogBuffer() :
new LogcatOnlyMessageBuffer(LogLevel.WARNING), mTag);
if (mPlugin != null) {
mPluginContext = mPluginFactory.createPluginContext();
@@ -92,24 +97,16 @@ public class PluginInstance<T extends Plugin>
return mTag;
}
/** */
/** True if an error has been observed */
public boolean hasError() {
return mHasError;
}
public void setLogFunc(BiConsumer logConsumer) {
mLogConsumer = logConsumer;
}
private void log(String message) {
if (mLogConsumer != null) {
mLogConsumer.accept(mTag, message);
}
}
@Override
public synchronized boolean onFail(String className, String methodName, Throwable failure) {
Log.e(TAG, "Failure from " + mPlugin + ". Disabling Plugin.");
PluginActionManager.logFmt(mLogger, LogLevel.ERROR,
"Failure from %s. Disabling Plugin.", mPlugin.toString(), null);
mHasError = true;
unloadPlugin();
mListener.onPluginDetached(this);
@@ -119,31 +116,31 @@ public class PluginInstance<T extends Plugin>
/** Alerts listener and plugin that the plugin has been created. */
public synchronized void onCreate() {
if (mHasError) {
log("Previous Fatal Exception detected for plugin class");
mLogger.w("Previous Fatal Exception detected for plugin class");
return;
}
boolean loadPlugin = mListener.onPluginAttached(this);
if (!loadPlugin) {
if (mPlugin != null) {
log("onCreate: auto-unload");
mLogger.d("onCreate: auto-unload");
unloadPlugin();
}
return;
}
if (mPlugin == null) {
log("onCreate: auto-load");
mLogger.d("onCreate: auto-load");
loadPlugin();
return;
}
if (!checkVersion()) {
log("onCreate: version check failed");
mLogger.d("onCreate: version check failed");
return;
}
log("onCreate: load callbacks");
mLogger.i("onCreate: load callbacks");
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.
@@ -156,11 +153,11 @@ public class PluginInstance<T extends Plugin>
public synchronized void onDestroy() {
if (mHasError) {
// Detached in error handler
log("onDestroy - no-op");
mLogger.d("onDestroy - no-op");
return;
}
log("onDestroy");
mLogger.i("onDestroy");
unloadPlugin();
mListener.onPluginDetached(this);
}
@@ -171,17 +168,15 @@ public class PluginInstance<T extends Plugin>
return mHasError ? null : mPlugin;
}
/**
* Loads and creates the plugin if it does not exist.
*/
/** Loads and creates the plugin if it does not exist. */
public synchronized void loadPlugin() {
if (mHasError) {
log("Previous Fatal Exception detected for plugin class");
mLogger.w("Previous Fatal Exception detected for plugin class");
return;
}
if (mPlugin != null) {
log("Load request when already loaded");
mLogger.d("Load request when already loaded");
return;
}
@@ -189,16 +184,16 @@ public class PluginInstance<T extends Plugin>
mPlugin = mPluginFactory.createPlugin(this);
mPluginContext = mPluginFactory.createPluginContext();
if (mPlugin == null || mPluginContext == null) {
Log.e(mTag, "Requested load, but failed");
mLogger.e("Requested load, but failed");
return;
}
if (!checkVersion()) {
log("loadPlugin: version check failed");
mLogger.e("loadPlugin: version check failed");
return;
}
log("Loaded plugin; running callbacks");
mLogger.e("Loaded plugin; running callbacks");
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,9 +202,7 @@ public class PluginInstance<T extends Plugin>
mListener.onPluginLoaded(mPlugin, mPluginContext, this);
}
/**
* Checks the plugin version, and permanently destroys the plugin instance on a failure
*/
/** Checks the plugin version, and permanently destroys the plugin instance on a failure */
private synchronized boolean checkVersion() {
if (mHasError) {
return false;
@@ -237,11 +230,11 @@ public class PluginInstance<T extends Plugin>
*/
public synchronized void unloadPlugin() {
if (mPlugin == null) {
log("Unload request when already unloaded");
mLogger.d("Unload request when already unloaded");
return;
}
log("Unloading plugin, running callbacks");
mLogger.i("Unloading plugin, running callbacks");
mListener.onPluginUnloaded(mPlugin, this);
if (!(mPlugin instanceof PluginFragment)) {
// Only call onDestroy for plugins that aren't fragments, as fragments
@@ -397,7 +390,7 @@ public class PluginInstance<T extends Plugin>
}
/**
* Simple class to create a new instance. Useful for testing.
* Simple class to create a new instance. Useful for testing.
*
* @param <T> The type of plugin this create.
**/
@@ -101,7 +101,7 @@ oneway interface ILauncherProxy {
/**
* Sent when split keyboard shortcut is triggered to enter stage split.
*/
void enterStageSplitFromRunningApp(boolean leftOrTop) = 25;
void enterStageSplitFromRunningApp(int displayId, boolean leftOrTop) = 25;
/**
* Sent when the task bar stash state is toggled.
@@ -159,4 +159,10 @@ oneway interface ILauncherProxy {
* Sent when {@link TaskbarDelegate#onDisplayRemoveSystemDecorations} is called.
*/
void onDisplayRemoveSystemDecorations(int displayId) = 38;
/**
* Sent when active action corner is received in {@link ActionCornerInteractor}. Please refer to
* {@link ActionCornerConstants.Action} for all possible actions.
*/
void onActionCornerActivated(int action, int displayId) = 39;
}
@@ -178,5 +178,10 @@ interface ISystemUiProxy {
*/
oneway void updateContextualEduStats(boolean isTrackpadGesture, String gestureType) = 58;
// Next id = 59
/**
* Sent after layout is performed for the "recents" button and it is visible on screen.
*/
oneway void notifyRecentsButtonPositionChanged(in Rect position) = 59;
// Next id = 60
}
@@ -58,7 +58,7 @@ data class ThumbnailData(
"ThumbnailData",
"Unexpected snapshot without USAGE_GPU_SAMPLED_IMAGE: " +
"${snapshot.hardwareBuffer}",
ex
ex,
)
}
@@ -69,10 +69,15 @@ data class ThumbnailData(
}
@JvmStatic
fun wrap(taskIds: IntArray?, snapshots: Array<TaskSnapshot>?): HashMap<Int, ThumbnailData> {
fun wrap(
taskIds: IntArray?,
snapshots: Array<TaskSnapshot?>?,
): HashMap<Int, ThumbnailData> {
return hashMapOf<Int, ThumbnailData>().apply {
if (taskIds != null && snapshots != null && taskIds.size == snapshots.size) {
repeat(snapshots.size) { put(taskIds[it], fromSnapshot(snapshots[it])) }
repeat(snapshots.size) {
snapshots[it]?.let { snapshot -> put(taskIds[it], fromSnapshot(snapshot)) }
}
}
}
}
@@ -5,11 +5,18 @@ import static android.view.Surface.ROTATION_180;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_10_90;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
import androidx.annotation.Nullable;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.wm.shell.shared.split.SplitBounds;
/**
* Utility class to position the thumbnail in the TaskView
@@ -18,22 +25,20 @@ public class PreviewPositionHelper {
public static final float MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT = 0.1f;
/**
* Specifies that a stage is positioned at the top half of the screen if
* in portrait mode or at the left half of the screen if in landscape mode.
* TODO(b/254378592): Remove after consolidation
*/
public static final int STAGE_POSITION_TOP_OR_LEFT = 0;
/**
* Specifies that a stage is positioned at the bottom half of the screen if
* in portrait mode or at the right half of the screen if in landscape mode.
* TODO(b/254378592): Remove after consolidation
*/
public static final int STAGE_POSITION_BOTTOM_OR_RIGHT = 1;
private final Matrix mMatrix = new Matrix();
private boolean mIsOrientationChanged;
/**
* Only used when this helper is being used for an app in split screen. Refers to the position
* of the app in the pair.
* See {@link com.android.wm.shell.shared.split.SplitScreenConstants#@SplitPosition}
*/
private int mSplitPosition = SPLIT_POSITION_UNDEFINED;
/**
* Guarded by enableFlexibleTwoAppSplit() flag, but this class doesn't have access so the
* caller is responsible for checking. If the flag is disabled this will be null
*/
@Nullable
private SplitBounds mSplitBounds;
public Matrix getMatrix() {
return mMatrix;
@@ -160,6 +165,16 @@ public class PreviewPositionHelper {
}
thumbnailScale = targetW / (croppedWidth * scale);
if (mSplitBounds != null
&& mSplitBounds.snapPosition == SNAP_TO_2_10_90
&& mSplitPosition == SPLIT_POSITION_TOP_OR_LEFT) {
if (mSplitBounds.appsStackedVertically) {
thumbnailClipHint.top += availableHeight - croppedHeight;
} else {
thumbnailClipHint.left += availableWidth - croppedWidth;
}
}
}
if (!isRotated) {
@@ -209,6 +224,11 @@ public class PreviewPositionHelper {
mMatrix.postTranslate(translateX, translateY);
}
public void setSplitBounds(SplitBounds splitBounds, int stagePosition) {
mSplitBounds = splitBounds;
mSplitPosition = stagePosition;
}
/**
* 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
@@ -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" }" +
@@ -19,6 +19,7 @@ package com.android.systemui.shared.rotation;
import android.annotation.DimenRes;
import android.annotation.IdRes;
import android.annotation.LayoutRes;
import android.annotation.Nullable;
import android.annotation.StringRes;
import android.content.Context;
import android.content.pm.ActivityInfo;
@@ -79,8 +80,8 @@ public class FloatingRotationButton implements RotationButton {
private FloatingRotationButtonPositionCalculator mPositionCalculator;
private RotationButtonController mRotationButtonController;
private RotationButtonUpdatesCallback mUpdatesCallback;
@Nullable private RotationButtonController mRotationButtonController;
@Nullable private RotationButtonUpdatesCallback mUpdatesCallback;
private Position mPosition;
public FloatingRotationButton(Context context, @StringRes int contentDescriptionResource,
@@ -132,14 +133,17 @@ public class FloatingRotationButton implements RotationButton {
}
@Override
public void setRotationButtonController(RotationButtonController rotationButtonController) {
public void setRotationButtonController(
@Nullable RotationButtonController rotationButtonController) {
mRotationButtonController = rotationButtonController;
updateIcon(mRotationButtonController.getLightIconColor(),
mRotationButtonController.getDarkIconColor());
if (mRotationButtonController != null) {
updateIcon(mRotationButtonController.getLightIconColor(),
mRotationButtonController.getDarkIconColor());
}
}
@Override
public void setUpdatesCallback(RotationButtonUpdatesCallback updatesCallback) {
public void setUpdatesCallback(@Nullable RotationButtonUpdatesCallback updatesCallback) {
mUpdatesCallback = updatesCallback;
}
@@ -188,6 +192,28 @@ public class FloatingRotationButton implements RotationButton {
return true;
}
@Override
public void onDestroy() {
setRotationButtonController(null);
setOnClickListener(null);
setOnHoverListener(null);
setUpdatesCallback(null);
if (mKeyButtonContainer.isAttachedToWindow()) {
mWindowManager.removeViewImmediate(mKeyButtonContainer);
}
mKeyButtonView.animate().cancel();
mAnimatedDrawable.stop();
mKeyButtonView.setImageDrawable(null);
// Calling AnimatedDrawable#stop() and ImageView.setImageDrawable(null) above will not let
// RenderThread clear ref to view (via AnimatedVectorDrawable$VectorDrawableAnimatorRT)
// quick enough for LeakCanary to ignore the leak. To mute LeakCanary, a workaround is to
// clear the mKeyButtonView.mParent so that LeakCanary won't complain the leak on
// mKeyButtonContainer.
mKeyButtonContainer.removeView(mKeyButtonView);
}
@Override
public boolean isVisible() {
return mIsShowing;
@@ -195,9 +221,12 @@ public class FloatingRotationButton implements RotationButton {
@Override
public void updateIcon(int lightIconColor, int darkIconColor) {
mAnimatedDrawable = (AnimatedVectorDrawable) mKeyButtonView.getContext()
.getDrawable(mRotationButtonController.getIconResId());
mAnimatedDrawable.setBounds(0, 0, mKeyButtonView.getWidth(), mKeyButtonView.getHeight());
if (mRotationButtonController != null) {
mAnimatedDrawable = (AnimatedVectorDrawable) mKeyButtonView.getContext()
.getDrawable(mRotationButtonController.getIconResId());
mAnimatedDrawable.setBounds(
0, 0, mKeyButtonView.getWidth(), mKeyButtonView.getHeight());
}
mKeyButtonView.setImageDrawable(mAnimatedDrawable);
mKeyButtonView.setColors(lightIconColor, darkIconColor);
}
@@ -251,8 +280,10 @@ public class FloatingRotationButton implements RotationButton {
updateDimensionResources();
if (mIsShowing) {
updateIcon(mRotationButtonController.getLightIconColor(),
mRotationButtonController.getDarkIconColor());
if (mRotationButtonController != null) {
updateIcon(mRotationButtonController.getLightIconColor(),
mRotationButtonController.getDarkIconColor());
}
final LayoutParams layoutParams = adjustViewPositionAndCreateLayoutParams();
mWindowManager.updateViewLayout(mKeyButtonContainer, layoutParams);
if (mAnimatedDrawable != null) {
@@ -16,6 +16,7 @@
package com.android.systemui.shared.rotation;
import android.annotation.Nullable;
import android.graphics.drawable.Drawable;
import android.view.View;
@@ -25,14 +26,17 @@ import android.view.View;
* one in contextual for 3 button nav and a floating rotation button for gestural.
*/
public interface RotationButton {
default void setRotationButtonController(RotationButtonController rotationButtonController) { }
default void setUpdatesCallback(RotationButtonUpdatesCallback updatesCallback) { }
default void setRotationButtonController(
@Nullable RotationButtonController rotationButtonController) { }
default void setUpdatesCallback(
@Nullable RotationButtonUpdatesCallback updatesCallback) { }
default View getCurrentView() {
return null;
}
default boolean show() { return false; }
default boolean hide() { return false; }
default void onDestroy() {}
default boolean isVisible() {
return false;
}
@@ -17,6 +17,8 @@
package com.android.systemui.shared.rotation;
import static android.content.pm.PackageManager.FEATURE_PC;
import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY;
import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.internal.view.RotationPolicy.NATURAL_ROTATION;
@@ -36,6 +38,8 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.Drawable;
import android.hardware.devicestate.DeviceState;
import android.hardware.devicestate.DeviceStateManager;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
@@ -67,8 +71,10 @@ import com.android.systemui.shared.rotation.RotationButton.RotationButtonUpdates
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.window.flags2.Flags;
import java.io.PrintWriter;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
@@ -94,7 +100,7 @@ public class RotationButtonController {
private final UiEventLogger mUiEventLogger = new UiEventLoggerImpl();
private final ViewRippler mViewRippler = new ViewRippler();
private final Supplier<Integer> mWindowRotationProvider;
private RotationButton mRotationButton;
@Nullable private RotationButton mRotationButton;
private boolean mIsRecentsAnimationRunning;
private boolean mDocked;
@@ -201,6 +207,14 @@ public class RotationButtonController {
mRotationButton.setUpdatesCallback(updatesCallback);
}
private void clearRotationButton() {
if (mRotationButton == null) {
return;
}
mRotationButton.onDestroy();
mRotationButton = null;
}
public Context getContext() {
return mContext;
}
@@ -230,6 +244,7 @@ public class RotationButtonController {
*/
public void onDestroy() {
unregisterListeners();
clearRotationButton();
}
public void registerListeners(boolean registerRotationWatcher) {
@@ -286,12 +301,23 @@ public class RotationButtonController {
TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
}
public void setRotationLockedAtAngle(
/**
* Sets device rotation to {@code rotationSuggestion} if {@code isLocked} is true and
* {@link Flags#enableDeviceStateAutoRotateSettingRefactor()} is disabled.
* <p> When {@link Flags#enableDeviceStateAutoRotateSettingRefactor()} is enabled, the rotation
* change in system server is conditional on auto-rotate still being OFF.
*/
public void setRotationAtAngle(
@Nullable Boolean isLocked, int rotationSuggestion, String caller) {
if (isLocked == null) {
// Ignore if we can't read the setting for the current user
return;
}
if (isFoldable() && Flags.enableDeviceStateAutoRotateSettingRefactor()) {
RotationPolicy.setRotationAtAngleIfAllowed(rotationSuggestion, caller);
return;
}
RotationPolicy.setRotationLockAtAngle(mContext, /* enabled= */ isLocked,
/* rotation= */ rotationSuggestion, caller);
}
@@ -301,6 +327,8 @@ public class RotationButtonController {
}
void setRotateSuggestionButtonState(final boolean visible, final boolean force) {
if (mRotationButton == null) return;
// At any point the button can become invisible because an a11y service became active.
// Similarly, a call to make the button visible may be rejected because an a11y service is
// active. Must account for this.
@@ -361,7 +389,9 @@ public class RotationButtonController {
fadeOut.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mRotationButton.hide();
if (mRotationButton != null) {
mRotationButton.hide();
}
}
});
@@ -371,7 +401,9 @@ public class RotationButtonController {
}
public void setDarkIntensity(float darkIntensity) {
mRotationButton.setDarkIntensity(darkIntensity);
if (mRotationButton != null) {
mRotationButton.setDarkIntensity(darkIntensity);
}
}
public void setRecentsAnimationRunning(boolean running) {
@@ -400,6 +432,10 @@ public class RotationButtonController {
}
public void onRotationProposal(int rotation, boolean isValid) {
if (mRotationButton == null) {
return;
}
boolean isUserSetupComplete = Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.USER_SETUP_COMPLETE, 0) != 0;
if (!isUserSetupComplete && OEM_DISALLOW_ROTATION_IN_SUW) {
@@ -473,10 +509,10 @@ public class RotationButtonController {
}
// The isVisible check makes the rotation button disappear when we are not locked
// (e.g. for tabletop auto-rotate).
if (isRotationLocked || mRotationButton.isVisible()) {
if (isRotationLocked || (mRotationButton != null && mRotationButton.isVisible())) {
// Do not allow a change in rotation to set user rotation when docked.
if (shouldOverrideUserLockPrefs(rotation) && isRotationLocked && !mDocked) {
setRotationLockedAtAngle(true, rotation, /* caller= */
setRotationAtAngle(true, rotation, /* caller= */
"RotationButtonController#onRotationWatcherChanged");
}
setRotateSuggestionButtonState(false /* visible */, true /* forced */);
@@ -512,10 +548,9 @@ public class RotationButtonController {
public void onTaskbarStateChange(boolean visible, boolean stashed) {
mTaskBarVisible = visible;
if (getRotationButton() == null) {
return;
if (mRotationButton != null) {
mRotationButton.onTaskbarStateChanged(visible, stashed);
}
getRotationButton().onTaskbarStateChanged(visible, stashed);
}
private void showPendingRotationButtonIfNeeded() {
@@ -574,6 +609,7 @@ public class RotationButtonController {
"%s\tmDarkIconColor=0x%s", prefix, Integer.toHexString(mDarkIconColor)));
}
@Nullable
public RotationButton getRotationButton() {
return mRotationButton;
}
@@ -581,7 +617,7 @@ public class RotationButtonController {
private void onRotateSuggestionClick(View v) {
mUiEventLogger.log(RotationButtonEvent.ROTATION_SUGGESTION_ACCEPTED);
incrementNumAcceptedRotationSuggestionsIfNeeded();
setRotationLockedAtAngle(
setRotationAtAngle(
RotationPolicyUtil.isRotationLocked(mContext), mLastRotationSuggestion,
/* caller= */ "RotationButtonController#onRotateSuggestionClick");
Log.i(TAG, "onRotateSuggestionClick() mLastRotationSuggestion=" + mLastRotationSuggestion);
@@ -635,7 +671,7 @@ public class RotationButtonController {
// Don't reschedule if a hide animator is running
if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) return;
// Don't reschedule if not visible
if (!mRotationButton.isVisible()) return;
if (mRotationButton == null || !mRotationButton.isVisible()) return;
}
// Stop any pending removal
@@ -670,6 +706,27 @@ public class RotationButtonController {
}
}
private boolean isFoldable() {
if (android.hardware.devicestate.feature.flags.Flags.deviceStatePropertyMigration()) {
final DeviceStateManager deviceStateManager = mContext.getSystemService(
DeviceStateManager.class);
if (deviceStateManager == null) return false;
List<DeviceState> deviceStates = deviceStateManager.getSupportedDeviceStates();
for (int i = 0; i < deviceStates.size(); i++) {
DeviceState state = deviceStates.get(i);
if (state.hasProperty(PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY)
|| state.hasProperty(
PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY)) {
return true;
}
}
return false;
} else {
return mContext.getResources().getIntArray(
com.android.internal.R.array.config_foldedDeviceStates).length != 0;
}
}
private class TaskStackListenerImpl implements TaskStackChangeListener {
// Invalidate any rotation suggestion on task change or activity orientation change
// Note: all callbacks happen on main thread
@@ -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;
@@ -36,12 +33,8 @@ import android.app.WindowConfiguration;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.graphics.Rect;
import android.os.Build;
import android.os.Bundle;
import android.os.DeadSystemException;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -55,9 +48,6 @@ import com.android.internal.app.IVoiceInteractionManagerService;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
public class ActivityManagerWrapper {
@@ -83,17 +73,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();
}
}
@@ -149,24 +135,9 @@ public class ActivityManagerWrapper {
public @NonNull ThumbnailData getTaskThumbnail(int taskId, boolean isLowResolution) {
TaskSnapshot snapshot = null;
try {
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
Method getTaskSnapshotMethod = getService().getClass().getMethod(
"getTaskSnapshot",
int.class, // taskId
boolean.class, // isLowResolution
boolean.class // isTranslucent (added in Android 14)
);
snapshot = (TaskSnapshot) getTaskSnapshotMethod.invoke(
getService(), taskId, isLowResolution, false);
} else {
snapshot = getService().getTaskSnapshot(taskId, isLowResolution);
}
snapshot = getService().getTaskSnapshot(taskId, isLowResolution);
} catch (RemoteException e) {
Log.w(TAG, "Failed to retrieve task snapshot", e);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
Log.e(TAG, "Failed to invoke getTaskSnapshot", e);
}
if (snapshot != null) {
return ThumbnailData.fromSnapshot(snapshot);
@@ -337,18 +308,6 @@ public class ActivityManagerWrapper {
}
}
/**
* Returns true if the system supports freeform multi-window.
*/
public boolean supportsFreeformMultiWindow(Context context) {
final boolean freeformDevOption = Settings.Global.getInt(context.getContentResolver(),
Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT, 0) != 0;
return ActivityTaskManager.supportsMultiWindow(context)
&& (context.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT)
|| freeformDevOption);
}
/**
* Returns true if the running task represents the home task
*/
@@ -20,7 +20,6 @@ import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
@@ -33,8 +32,6 @@ import android.view.InputEvent;
import android.view.WindowManagerGlobal;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* Manages the input consumer that allows the SystemUI to directly receive input.
@@ -142,14 +139,7 @@ public class InputConsumerController {
if (mInputEventReceiver == null) {
final InputChannel inputChannel = new InputChannel();
try {
// Hook for Android P (9) to VANILLA_ICE_CREAM (15), where the method signature changed
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P
&& Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) {
hookDestroyInputConsumer(mWindowManager);
} else {
mWindowManager.destroyInputConsumer(mToken, DEFAULT_DISPLAY);
}
mWindowManager.destroyInputConsumer(mToken, DEFAULT_DISPLAY);
mWindowManager.createInputConsumer(mToken, mName, DEFAULT_DISPLAY, inputChannel);
} catch (RemoteException e) {
Log.e(TAG, "Failed to create input consumer", e);
@@ -162,36 +152,13 @@ public class InputConsumerController {
}
}
/**
* IWindowManager @destroyInputConsumer reflection
*/
private void hookDestroyInputConsumer(IWindowManager mWindowManager) throws RemoteException {
try {
Class<?> iWindowManagerClass = Class.forName("android.view.IWindowManager");
Method destroyInputConsumerMethod = iWindowManagerClass.getMethod("destroyInputConsumer", String.class, int.class);
destroyInputConsumerMethod.invoke(mWindowManager, mName, DEFAULT_DISPLAY);
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException |
InvocationTargetException e) {
Log.e(TAG, "Failed to invoke destroyInputConsumer", e);
mWindowManager.destroyInputConsumer(mToken, DEFAULT_DISPLAY);
}
}
/**
* Unregisters the input consumer.
*/
public void unregisterInputConsumer() {
if (mInputEventReceiver != null) {
try {
// Hook for Android P (9) to VANILLA_ICE_CREAM (15), where the method signature changed
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P
&& Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) {
hookDestroyInputConsumer(mWindowManager);
} else {
mWindowManager.destroyInputConsumer(mToken, DEFAULT_DISPLAY);
}
mWindowManager.destroyInputConsumer(mToken, DEFAULT_DISPLAY);
} catch (RemoteException e) {
Log.e(TAG, "Failed to destroy input consumer", e);
}
@@ -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 (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) 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 (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) 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 (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) 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 (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) 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 (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) 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 (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return false;
return InteractionJankMonitor.getInstance().isInstrumenting(cujType);
}
}
@@ -416,11 +416,13 @@ public class QuickStepContract {
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.
*
* @param context A display associated context.
*/
public static float getWindowCornerRadius(Context context) {
// LC-Wrapped
@@ -16,7 +16,6 @@
package com.android.systemui.shared.system;
import android.os.Build;
import android.os.RemoteException;
import android.util.Log;
import android.view.RemoteAnimationTarget;
@@ -27,8 +26,6 @@ import android.window.WindowAnimationState;
import com.android.internal.os.IResultReceiver;
import com.android.wm.shell.recents.IRecentsAnimationController;
import java.lang.reflect.Method;
public class RecentsAnimationControllerCompat {
private static final String TAG = RecentsAnimationControllerCompat.class.getSimpleName();
@@ -75,24 +72,7 @@ public class RecentsAnimationControllerCompat {
*/
public void finish(boolean toHome, boolean sendUserLeaveHint, IResultReceiver finishCb) {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
mAnimationController.finish(toHome, sendUserLeaveHint, finishCb);
} else {
try {
Method finishMethod = mAnimationController.getClass().getMethod(
"finish", boolean.class, boolean.class);
finishMethod.invoke(mAnimationController, toHome, sendUserLeaveHint);
if (finishCb != null) {
finishCb.send(0, null);
}
} catch (Exception e) {
Log.e(TAG, "Failed to finish recents animation via reflection", e);
if (finishCb != null) {
finishCb.send(0, null);
}
}
}
mAnimationController.finish(toHome, sendUserLeaveHint, finishCb);
} catch (RemoteException e) {
Log.e(TAG, "Failed to finish recents animation", e);
try {
@@ -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)
}
}
/**
@@ -18,6 +18,7 @@ import android.os.Handler
import android.os.HandlerThread
import android.os.Looper
import android.os.Process
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dagger.qualifiers.UiBackground
import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig
@@ -25,6 +26,7 @@ import com.android.systemui.unfold.config.UnfoldTransitionConfig
import com.android.systemui.unfold.dagger.UnfoldBg
import com.android.systemui.unfold.dagger.UnfoldMain
import com.android.systemui.unfold.dagger.UnfoldSingleThreadBg
import com.android.systemui.unfold.dagger.UnfoldTracking
import com.android.systemui.unfold.updates.FoldProvider
import com.android.systemui.unfold.util.CurrentActivityTypeProvider
import dagger.Binds
@@ -33,7 +35,10 @@ import dagger.Provides
import java.util.concurrent.Executor
import javax.inject.Singleton
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.android.asCoroutineDispatcher
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.plus
/**
* Dagger module with system-only dependencies for the unfold animation. The code that is used to
@@ -45,25 +50,20 @@ import kotlinx.coroutines.android.asCoroutineDispatcher
abstract class SystemUnfoldSharedModule {
@Binds
abstract fun activityTypeProvider(executor: ActivityManagerActivityTypeProvider):
CurrentActivityTypeProvider
abstract fun activityTypeProvider(
executor: ActivityManagerActivityTypeProvider
): CurrentActivityTypeProvider
@Binds
abstract fun config(config: ResourceUnfoldTransitionConfig): UnfoldTransitionConfig
@Binds abstract fun config(config: ResourceUnfoldTransitionConfig): UnfoldTransitionConfig
@Binds
abstract fun foldState(provider: DeviceStateManagerFoldProvider): FoldProvider
@Binds abstract fun foldState(provider: DeviceStateManagerFoldProvider): FoldProvider
@Binds
abstract fun deviceStateRepository(provider: DeviceStateRepositoryImpl): DeviceStateRepository
@Binds
@UnfoldMain
abstract fun mainExecutor(@Main executor: Executor): Executor
@Binds @UnfoldMain abstract fun mainExecutor(@Main executor: Executor): Executor
@Binds
@UnfoldMain
abstract fun mainHandler(@Main handler: Handler): Handler
@Binds @UnfoldMain abstract fun mainHandler(@Main handler: Handler): Handler
@Binds
@UnfoldSingleThreadBg
@@ -92,5 +92,18 @@ abstract class SystemUnfoldSharedModule {
.apply { start() }
.looper
}
@Provides
@UnfoldTracking
@Singleton
fun unfoldTrackingContext(
@UnfoldSingleThreadBg singleThreadBgExecutor: Executor,
@Application applicationScope: CoroutineScope,
): CoroutineScope {
// tracking depends on being executed on a single thread so when changing it, ensure all
// consumers are not accessing shared state
val backgroundDispatcher = singleThreadBgExecutor.asCoroutineDispatcher()
return applicationScope + backgroundDispatcher
}
}
}