diff --git a/Android.bp b/Android.bp
index 0d41864e2c1d7..ab3aeecadcc61 100644
--- a/Android.bp
+++ b/Android.bp
@@ -142,6 +142,7 @@ filegroup {
":deviceproductinfoconstants_aidl",
":adbrootservice_aidl",
+ ":lmofreeform_aidl",
// For the generated R.java and Manifest.java
":framework-res{.aapt.srcjar}",
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 2174513c83e56..b149d03f351ad 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -24,17 +24,21 @@
import android.hardware.SensorManager;
import android.hardware.input.HostUsiVersion;
import android.os.Handler;
+import android.os.IBinder;
import android.os.PowerManager;
import android.util.IntArray;
import android.util.SparseArray;
import android.view.Display;
import android.view.DisplayInfo;
+import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceControl.RefreshRateRange;
import android.view.SurfaceControl.Transaction;
import android.window.DisplayWindowPolicyController;
import android.window.ScreenCaptureInternal;
+import com.libremobileos.freeform.ILMOFreeformDisplayCallback;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
@@ -501,6 +505,17 @@ public abstract RefreshRateRange getRefreshRateForDisplayAndSensor(
*/
public abstract IntArray getDisplayIds();
+ // LMOFreeform
+ public abstract void createFreeformLocked(String name, ILMOFreeformDisplayCallback callback,
+ int width, int height, int densityDpi, boolean secure, boolean ownContentOnly,
+ boolean shouldShowSystemDecorations, Surface surface, float refreshRate,
+ long presentationDeadlineNanos);
+
+ public abstract void resizeFreeform(IBinder appToken, int width, int height,
+ int densityDpi);
+
+ public abstract void releaseFreeform(IBinder appToken);
+
/**
* Get group id for given display id
*/
diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java
index dbb68372ab985..ac3a59d5472a0 100644
--- a/core/java/android/view/animation/AnimationUtils.java
+++ b/core/java/android/view/animation/AnimationUtils.java
@@ -30,13 +30,17 @@
import android.content.res.Resources.Theme;
import android.content.res.XmlResourceParser;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.ravenwood.annotation.RavenwoodIgnore;
import android.ravenwood.annotation.RavenwoodKeepPartialClass;
import android.util.AttributeSet;
+import android.util.DisplayMetrics;
import android.util.TimeUtils;
import android.util.Xml;
import android.view.InflateException;
+import com.android.internal.R;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -224,6 +228,20 @@ public static long getExpectedPresentationTimeMillis() {
public static Animation loadAnimation(Context context, @AnimRes int id)
throws NotFoundException {
+ if (SystemProperties.getBoolean("persist.sys.activity_anim_perf_override", false)) {
+ ActivityAnimations.maybeInit(context);
+ switch (id) {
+ case R.anim.activity_open_enter:
+ return ActivityAnimations.getOpenEnter();
+ case R.anim.activity_open_exit:
+ return ActivityAnimations.getOpenExit();
+ case R.anim.activity_close_enter:
+ return ActivityAnimations.getCloseEnter();
+ case R.anim.activity_close_exit:
+ return ActivityAnimations.getCloseExit();
+ }
+ }
+
XmlResourceParser parser = null;
try {
parser = context.getResources().getAnimation(id);
@@ -504,4 +522,100 @@ private static Interpolator createInterpolatorFromXml(
}
return interpolator;
}
+
+ /** @hide */
+ public final class ActivityAnimations {
+
+ private static Animation sOpenEnter;
+ private static Animation sOpenExit;
+ private static Animation sCloseEnter;
+ private static Animation sCloseExit;
+
+ private static Interpolator sFastOutExtraSlowInInterpolator;
+
+ private static final float DISTANCE = 0.1f;
+
+ private ActivityAnimations() {}
+
+ /** @hide */
+ public static void maybeInit(Context context) {
+ if (sFastOutExtraSlowInInterpolator == null) {
+ sFastOutExtraSlowInInterpolator = AnimationUtils.loadInterpolator(
+ context, R.interpolator.fast_out_extra_slow_in);
+ }
+ }
+
+ private static class ActivityAnimFactory {
+ private float fromX = 0f, toX = 0f;
+ private long duration = 200L;
+
+ public ActivityAnimFactory fromX(float ratio) {
+ this.fromX = ratio;
+ return this;
+ }
+
+ public ActivityAnimFactory toX(float ratio) {
+ this.toX = ratio;
+ return this;
+ }
+
+ public Animation build() {
+ AnimationSet animationSet = new AnimationSet(false);
+ TranslateAnimation slide = new TranslateAnimation(
+ Animation.RELATIVE_TO_SELF, fromX,
+ Animation.RELATIVE_TO_SELF, toX,
+ Animation.RELATIVE_TO_SELF, 0f,
+ Animation.RELATIVE_TO_SELF, 0f
+ );
+ slide.setDuration(duration);
+ slide.setInterpolator(sFastOutExtraSlowInInterpolator);
+ animationSet.addAnimation(slide);
+ return animationSet;
+ }
+ }
+
+ /** @hide */
+ public static Animation getOpenEnter() {
+ if (sOpenEnter == null) {
+ sOpenEnter = new ActivityAnimFactory()
+ .fromX(1.0f)
+ .toX(0.0f)
+ .build();
+ }
+ return sOpenEnter;
+ }
+
+ /** @hide */
+ public static Animation getOpenExit() {
+ if (sOpenExit == null) {
+ sOpenExit = new ActivityAnimFactory()
+ .fromX(0.0f)
+ .toX(-DISTANCE)
+ .build();
+ }
+ return sOpenExit;
+ }
+
+ /** @hide */
+ public static Animation getCloseEnter() {
+ if (sCloseEnter == null) {
+ sCloseEnter = new ActivityAnimFactory()
+ .fromX(-DISTANCE)
+ .toX(0.0f)
+ .build();
+ }
+ return sCloseEnter;
+ }
+
+ /** @hide */
+ public static Animation getCloseExit() {
+ if (sCloseExit == null) {
+ sCloseExit = new ActivityAnimFactory()
+ .fromX(0.0f)
+ .toX(1.0f)
+ .build();
+ }
+ return sCloseExit;
+ }
+ }
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 2aa28894b3ba5..bb1f7e3b801ee 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -941,6 +941,10 @@
android:knownCerts="@array/config_setContactsDefaultAccountKnownSigners"
android:featureFlag="android.provider.new_default_account_api_enabled"/>
+
+
+
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 127ca3c989840..61283953dc577 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -32,6 +32,7 @@
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
+import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.Insets;
import android.graphics.Point;
@@ -54,6 +55,7 @@
import android.window.DesktopModeFlags;
import android.window.WindowContainerTransaction;
+import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
@@ -220,7 +222,7 @@ static void updateRelayoutParams(
boolean shouldSetTaskVisibilityPositionAndCrop,
boolean isStatusBarVisible,
boolean isKeyguardVisibleAndOccluded,
- InsetsState displayInsetsState,
+ DisplayController displayController,
boolean hasGlobalFocus,
@NonNull Region globalExclusionRegion,
boolean shouldSetBackground,
@@ -246,6 +248,8 @@ static void updateRelayoutParams(
|| (isStatusBarVisible && !isKeyguardVisibleAndOccluded);
relayoutParams.mDisplayExclusionRegion.set(globalExclusionRegion);
relayoutParams.mInSyncWithTransition = inSyncWithTransition;
+ relayoutParams.mCornerRadius =
+ getCornerRadius(context, displayController.getDisplay(taskInfo.displayId));
if (TaskInfoKt.isTransparentCaptionBarAppearance(taskInfo)) {
// If the app is requesting to customize the caption bar, allow input to fall
@@ -265,7 +269,8 @@ static void updateRelayoutParams(
);
relayoutParams.mCaptionTopPadding = getTopPadding(relayoutParams,
- taskInfo.getConfiguration().windowConfiguration.getBounds(), displayInsetsState);
+ taskInfo.getConfiguration().windowConfiguration.getBounds(),
+ displayController.getInsetsState(taskInfo.displayId));
// Set opaque background for all freeform tasks to prevent freeform tasks below
// from being visible if freeform task window above is translucent.
// Otherwise if fluid resize is enabled, add a background to freeform tasks.
@@ -290,7 +295,7 @@ void relayout(RunningTaskInfo taskInfo,
updateRelayoutParams(mRelayoutParams, mContext, taskInfo, applyStartTransactionOnDraw,
shouldSetTaskVisibilityPositionAndCrop, mIsStatusBarVisible,
mIsKeyguardVisibleAndOccluded,
- mDisplayController.getInsetsState(taskInfo.displayId), hasGlobalFocus,
+ mDisplayController, hasGlobalFocus,
globalExclusionRegion, mDesktopConfig.shouldSetBackground(taskInfo),
inSyncWithTransition);
@@ -350,6 +355,19 @@ void relayout(RunningTaskInfo taskInfo,
});
}
+ private static int getCornerRadius(Context context, Display display) {
+ // Show rounded corners only on the internal display as we can't get rounded corners for
+ // external displays.
+ if (display.getType() != Display.TYPE_INTERNAL) {
+ return 0;
+ }
+ final TypedArray ta = context.obtainStyledAttributes(
+ new int[]{android.R.attr.dialogCornerRadius});
+ final int cornerRadius = ta.getDimensionPixelSize(0, 0);
+ ta.recycle();
+ return cornerRadius;
+ }
+
/**
* Sets up listeners when a new root view is created.
*/
diff --git a/services/Android.bp b/services/Android.bp
index 6f978252e57c4..d22024fd7e5e6 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -133,6 +133,7 @@ filegroup {
":services.coverage-sources",
":services.credentials-sources",
":services.devicepolicy-sources",
+ ":services.freeform-sources",
":services.midi-sources",
":services.musicsearch-sources",
":services.net-sources",
@@ -339,6 +340,7 @@ java_library {
"services.credentials",
"services.devicepolicy",
"services.flags",
+ "services.freeform",
"services.midi",
"services.musicsearch",
"services.net",
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 71091f230ad1f..77abaa02e1334 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -134,6 +134,7 @@ java_library_static {
":display-layout-config",
":display-topology",
":device-state-config",
+ ":lmofreeform-display-adapter-java",
"java/com/android/server/EventLogTags.logtags",
"java/com/android/server/am/EventLogTags.logtags",
"java/com/android/server/wm/EventLogTags.logtags",
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index e032b4e332007..f38425c326fe2 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -208,6 +208,8 @@
import com.android.server.wm.SurfaceAnimationThread;
import com.android.server.wm.WindowManagerInternal;
+import com.libremobileos.freeform.ILMOFreeformDisplayCallback;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -563,6 +565,8 @@ public synchronized void requestDisplayState(int displayId, int state, float bri
// only be used for the devices in projected mode.
private boolean mIncludeDefaultDisplayInTopology;
+ private LMOFreeformDisplayAdapter mFreeformDisplayAdapter;
+
private final BroadcastReceiver mIdleModeReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -2386,6 +2390,7 @@ private void registerAdditionalDisplayAdapters() {
if (shouldRegisterNonEssentialDisplayAdaptersLocked()) {
registerOverlayDisplayAdapterLocked();
registerWifiDisplayAdapterLocked();
+ registerFreeformDisplayAdapterLocked();
}
}
}
@@ -2406,6 +2411,13 @@ private void registerWifiDisplayAdapterLocked() {
}
}
+ private void registerFreeformDisplayAdapterLocked() {
+ mFreeformDisplayAdapter = new LMOFreeformDisplayAdapter(
+ mSyncRoot, mContext, mHandler, mDisplayDeviceRepo, mLogicalDisplayMapper,
+ mUiHandler, mFlags);
+ registerDisplayAdapterLocked(mFreeformDisplayAdapter);
+ }
+
private boolean shouldRegisterNonEssentialDisplayAdaptersLocked() {
// In safe mode, we disable non-essential display adapters to give the user
// an opportunity to fix broken settings or other problems that might affect
@@ -6431,6 +6443,23 @@ public void reloadTopologies(final int userId) {
scheduleTopologiesReload(mCurrentUserId, /*isUserSwitching=*/ false);
}
}
+
+ public void createFreeformLocked(String name, ILMOFreeformDisplayCallback callback,
+ int width, int height, int densityDpi, boolean secure, boolean ownContentOnly,
+ boolean shouldShowSystemDecorations, Surface surface, float refreshRate,
+ long presentationDeadlineNanos) {
+ mFreeformDisplayAdapter.createFreeformLocked(name, callback, width, height, densityDpi,
+ secure, ownContentOnly, shouldShowSystemDecorations, surface, refreshRate,
+ presentationDeadlineNanos);
+ }
+
+ public void resizeFreeform(IBinder appToken, int width, int height, int densityDpi) {
+ mFreeformDisplayAdapter.resizeFreeform(appToken, width, height, densityDpi);
+ }
+
+ public void releaseFreeform(IBinder appToken) {
+ mFreeformDisplayAdapter.releaseFreeform(appToken);
+ }
}
class DesiredDisplayModeSpecsObserver
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index ac8b92854e68b..8c458ae6f6a42 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -3070,6 +3070,12 @@ int startActivityFromRecents(int callingPid, int callingUid, int taskId,
mService.continueWindowLayout();
}
}
+ if (activityOptions != null) {
+ final int windowingMode = activityOptions.getLaunchWindowingMode();
+ if (windowingMode == WINDOWING_MODE_FREEFORM) {
+ task.setBounds(activityOptions.getLaunchBounds());
+ }
+ }
taskCallingUid = task.mCallingUid;
callingPackage = task.mCallingPackage;
callingFeatureId = task.mCallingFeatureId;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 27427724f0c3e..3b79e0650ee93 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -130,6 +130,7 @@
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_SLEEP_TOKEN;
import static com.android.internal.protolog.WmProtoLogGroups.WM_SHOW_TRANSACTIONS;
import static com.android.internal.util.LatencyTracker.ACTION_ROTATE_SCREEN;
+import static com.android.server.display.LMOFreeformDisplayAdapter.UNIQUE_ID_PREFIX;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
@@ -563,6 +564,8 @@ public void onIdMatch(InsetsSource source1, InsetsSource source2) {
// TODO(multi-display): remove some of the usages.
boolean isDefaultDisplay;
+ private boolean isFreeformDisplay;
+
/** Save allocating when calculating rects */
private final Rect mTmpRect = new Rect();
private final Region mTmpRegion = new Region();
@@ -796,6 +799,8 @@ public void onIdMatch(InsetsSource source1, InsetsSource source2) {
/** Last window to hold the screen locked. */
private WindowState mLastWakeLockHoldingWindow;
+ private boolean mHasSecureContent;
+
/**
* Whether display is allowed to ignore all activity size restrictions.
* @see #isDisplayIgnoreActivitySizeRestrictions
@@ -1169,6 +1174,7 @@ public void onLowMemory() {
mSystemGestureExclusionLimit = mWmService.mConstants.mSystemGestureExclusionLimitDp
* mDisplayMetrics.densityDpi / DENSITY_DEFAULT;
isDefaultDisplay = mDisplayId == DEFAULT_DISPLAY;
+ isFreeformDisplay = mDisplayInfo.uniqueId.startsWith(UNIQUE_ID_PREFIX);
mInsetsStateController = new InsetsStateController(this);
initializeDisplayBaseInfo();
mDisplayFrames = new DisplayFrames(mInsetsStateController.getRawInsetsState(),
@@ -2147,7 +2153,9 @@ void continueUpdateOrientationForDiffOrienLaunchingApp() {
return;
}
// The orientation of display is not changed.
- clearFixedRotationLaunchingApp();
+ if (!mTransitionController.isCollecting(this)) {
+ clearFixedRotationLaunchingApp();
+ }
}
/**
@@ -4362,6 +4370,11 @@ InsetsControlTarget getImeControlTarget() {
return mWmService.mDisplayWindowSettings.getImePolicyLocked(this);
}
+ boolean forceDesktopMode() {
+ return ("VNC".equals(mDisplay.getName()) || mWmService.mForceDesktopModeOnExternalDisplays)
+ && !isDefaultDisplay && !isFreeformDisplay && !isPrivate();
+ }
+
/** @see WindowManagerInternal#onToggleImeRequested */
void onShowImeRequested() {
if (mInputMethodWindow == null) {
@@ -5151,6 +5164,11 @@ boolean isInputMethodClientFocus(int uid, int pid) {
return imeLayeringTarget.mSession.mUid == uid && imeLayeringTarget.mSession.mPid == pid;
}
+ boolean hasSecureWindowOnScreen() {
+ final WindowState win = getWindow(w -> w.isOnScreen() && w.isSecureLocked());
+ return win != null;
+ }
+
// TODO: Super unexpected long method that should be broken down...
void applySurfaceChangesTransaction() {
final WindowSurfacePlacer surfacePlacer = mWmService.mWindowPlacerLocked;
@@ -5286,6 +5304,13 @@ private void performLayoutNoTrace(boolean initial, boolean updateInputWindows) {
if (updateInputWindows) {
mInputMonitor.updateInputWindowsLw(false /*force*/);
}
+
+ // Notify if display added or removed a secure window
+ final boolean hasSecureContent = hasSecureWindowOnScreen();
+ if (hasSecureContent != mHasSecureContent) {
+ mHasSecureContent = hasSecureContent;
+ mWmService.notifyDisplaySecureContentChange(mDisplayId, hasSecureContent);
+ }
}
/**
@@ -5780,7 +5805,7 @@ && isPublicSecondaryDisplayWithDesktopModeForceEnabled()) {
* also check {@link #isSystemDecorationsSupported()} to avoid breaking any security policy.
*/
boolean isPublicSecondaryDisplayWithDesktopModeForceEnabled() {
- if (!mWmService.mForceDesktopModeOnExternalDisplays || isDefaultDisplay || isPrivate()) {
+ if (!mWmService.mForceDesktopModeOnExternalDisplays || isDefaultDisplay || isPrivate() && !isFreeformDisplay) {
return false;
}
if (!isWindowingModeSupported(WINDOWING_MODE_FREEFORM)) {
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index bfbdef1a92cef..9d24cba7b9ba1 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -259,16 +259,13 @@ public int getPointerLayer() {
@Override
public int getPointerDisplayId() {
synchronized (mService.mGlobalLock) {
- // If desktop mode is not enabled, show on the default display.
- if (!mService.mForceDesktopModeOnExternalDisplays) {
- return DEFAULT_DISPLAY;
- }
-
// Look for the topmost freeform display.
int firstExternalDisplayId = DEFAULT_DISPLAY;
for (int i = mService.mRoot.mChildren.size() - 1; i >= 0; --i) {
final DisplayContent displayContent = mService.mRoot.mChildren.get(i);
- if (displayContent.getDisplayInfo().state == Display.STATE_OFF) {
+ if (displayContent.getDisplayInfo().state == Display.STATE_OFF
+ || !displayContent.forceDesktopMode()) {
+ // If desktop mode is not enabled, show on the default display.
continue;
}
// Heuristic solution here. Currently when "Freeform windows" developer option is
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 8931af7c0474c..4216ada8dd65c 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -439,6 +439,14 @@ default void dragRecipientEntered(IWindow window) {}
default void dragRecipientExited(IWindow window) {}
}
+ /**
+ * Listener interface for secure content showing up on the display.
+ */
+ public interface DisplaySecureContentListener {
+ public void onDisplayHasSecureWindowOnScreenChanged(
+ int displayId, boolean hasSecureWindowOnScreen);
+ }
+
/**
* Request the interface to access features implemented by AccessibilityController.
*/
@@ -1249,4 +1257,12 @@ public abstract void requestAssistScreenshot(IAssistDataReceiver receiver,
* @throws RuntimeException if the payload cannot be written to the settings file.
*/
public abstract void restoreDisplayWindowSettings(int userId, byte[] payload);
+
+ /**
+ * Register/unregister callbacks for secure content showing up on the display.
+ */
+ public abstract void registerDisplaySecureContentListener(
+ DisplaySecureContentListener listener);
+ public abstract void unregisterDisplaySecureContentListener(
+ DisplaySecureContentListener listener);
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index dd4c992494b0a..20a67fa901604 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -797,6 +797,9 @@ public void binderDied() {
WindowManagerInternal.OnHardKeyboardStatusChangeListener mHardKeyboardStatusChangeListener;
WindowManagerInternal.OnImeRequestedChangedListener mOnImeRequestedChangedListener;
+ private ArraySet
+ mDisplaySecureContentListeners = new ArraySet<>();
+
SettingsObserver mSettingsObserver;
final EmbeddedWindowController mEmbeddedWindowController;
final AnrController mAnrController;
@@ -5817,6 +5820,15 @@ void notifyHardKeyboardStatusChange() {
}
}
+ void notifyDisplaySecureContentChange(int displayId, boolean hasSecureWindowOnScreen) {
+ synchronized (mGlobalLock) {
+ mDisplaySecureContentListeners.forEach((listener) -> {
+ listener.onDisplayHasSecureWindowOnScreenChanged(
+ displayId, hasSecureWindowOnScreen);
+ });
+ }
+ }
+
// -------------------------------------------------------------
// Input Events and Focus Management
// -------------------------------------------------------------
@@ -9214,6 +9226,20 @@ public void requestAssistScreenshot(IAssistDataReceiver receiver, IBinder activi
}
WindowManagerService.this.requestAssistScreenshotInternal(receiver, displayId);
}
+
+ @Override
+ public void registerDisplaySecureContentListener(DisplaySecureContentListener listener) {
+ synchronized (mGlobalLock) {
+ mDisplaySecureContentListeners.add(listener);
+ }
+ }
+
+ @Override
+ public void unregisterDisplaySecureContentListener(DisplaySecureContentListener listener) {
+ synchronized (mGlobalLock) {
+ mDisplaySecureContentListeners.remove(listener);
+ }
+ }
}
/** Called to inform window manager if non-Vr UI shoul be disabled or not. */
diff --git a/services/core/java/com/android/server/wm/WindowOrientationListener.java b/services/core/java/com/android/server/wm/WindowOrientationListener.java
index d26616edfb44c..0cdd058a09994 100644
--- a/services/core/java/com/android/server/wm/WindowOrientationListener.java
+++ b/services/core/java/com/android/server/wm/WindowOrientationListener.java
@@ -1177,6 +1177,10 @@ public void onSensorChanged(SensorEvent event) {
return;
}
}
+ if (mRotationResolverService == null) {
+ // Bail out because RotationResolverManagerService wasn't started
+ return;
+ }
String packageName = null;
if (mActivityTaskManagerInternal != null) {
diff --git a/services/freeform/Android.bp b/services/freeform/Android.bp
new file mode 100644
index 0000000000000..3920451176ca5
--- /dev/null
+++ b/services/freeform/Android.bp
@@ -0,0 +1,27 @@
+//
+// SPDX-FileCopyrightText: 2023 The LibreMobileOS Foundation
+// SPDX-FileCopyrightText: 2024-2025 crDroid Android Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+filegroup {
+ name: "services.freeform-sources",
+ srcs: [
+ "java/**/*.java",
+ "java/**/*.kt"
+ ],
+ path: "java",
+ visibility: ["//frameworks/base/services"],
+}
+
+java_library_static {
+ name: "services.freeform",
+ defaults: ["platform_service_defaults"],
+ srcs: [":services.freeform-sources"],
+ libs: ["services.core"],
+ static_libs: [
+ "kotlinx_coroutines",
+ "lmofreeform-server",
+ ],
+ jarjar_rules: "jarjar-rules.txt",
+}
diff --git a/services/freeform/jarjar-rules.txt b/services/freeform/jarjar-rules.txt
new file mode 100644
index 0000000000000..a50af24f3dddc
--- /dev/null
+++ b/services/freeform/jarjar-rules.txt
@@ -0,0 +1,2 @@
+rule kotlin.** com.libremobileos.server.jarjar.@0
+rule kotlinx.** com.libremobileos.server.jarjar.@0
diff --git a/services/freeform/java/com/android/server/display/FreeformService.java b/services/freeform/java/com/android/server/display/FreeformService.java
new file mode 100644
index 0000000000000..af5c3a5484ecc
--- /dev/null
+++ b/services/freeform/java/com/android/server/display/FreeformService.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023-2024 LibreMobileOS Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import android.content.Context;
+import android.hardware.display.DisplayManagerInternal;
+import android.util.Slog;
+
+import com.android.server.SystemService;
+
+import com.libremobileos.freeform.server.LMOFreeformService;
+import com.libremobileos.freeform.server.LMOFreeformServiceHolder;
+import com.libremobileos.freeform.server.LMOFreeformUIService;
+
+public class FreeformService extends SystemService {
+
+ private static final String TAG = "FreeformService";
+
+ public FreeformService(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {
+ // noop
+ }
+
+ @Override
+ public void onBootPhase(@BootPhase int phase) {
+ if (phase != PHASE_ACTIVITY_MANAGER_READY || isSafeMode()) return;
+
+ Slog.d(TAG, "PHASE_ACTIVITY_MANAGER_READY, going to init!");
+
+ DisplayManagerInternal displayManager = getLocalService(DisplayManagerInternal.class);
+ if (displayManager == null) {
+ Slog.e(TAG, "Cannot init: DisplayManagerInternal is null!");
+ return;
+ }
+
+ LMOFreeformService service = new LMOFreeformService(displayManager);
+ LMOFreeformUIService uiService =
+ new LMOFreeformUIService(getContext(), displayManager, service);
+ LMOFreeformServiceHolder.init(uiService, service);
+ }
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index c1bfbb330b96f..cb69348f6cbed 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -175,6 +175,7 @@
import com.android.server.devicepolicy.DevicePolicyManagerService;
import com.android.server.devicestate.DeviceStateManagerService;
import com.android.server.display.DisplayManagerService;
+import com.android.server.display.FreeformService;
import com.android.server.display.color.ColorDisplayService;
import com.android.server.dreams.DreamManagerService;
import com.android.server.emergency.EmergencyAffordanceService;
@@ -2817,6 +2818,10 @@ private void startOtherServices(@NonNull TimingsTraceAndSlog t) {
t.traceEnd();
}
+ t.traceBegin("FreeformService");
+ mSystemServiceManager.startService(FreeformService.class);
+ t.traceEnd();
+
if (!isWatch) {
// We don't run this on watches as there are no plans to use the data logged
// on watch devices.