Merge "[Audiosharing] Add util to check if BluetoothDevice has connected source" into main
diff --git a/core/api/current.txt b/core/api/current.txt
index 3c5a31c..354e26b 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -43729,6 +43729,7 @@
field public static final String KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY = "carrier_nr_availabilities_int_array";
field public static final String KEY_CARRIER_PROVISIONS_WIFI_MERGED_NETWORKS_BOOL = "carrier_provisions_wifi_merged_networks_bool";
field public static final String KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL = "carrier_rcs_provisioning_required_bool";
+ field @FlaggedApi("com.android.internal.telephony.flags.carrier_roaming_nb_iot_ntn") public static final String KEY_CARRIER_ROAMING_NTN_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_INT = "carrier_roaming_ntn_emergency_call_to_satellite_handover_type_int";
field public static final String KEY_CARRIER_SERVICE_NAME_STRING_ARRAY = "carrier_service_name_array";
field public static final String KEY_CARRIER_SERVICE_NUMBER_STRING_ARRAY = "carrier_service_number_array";
field public static final String KEY_CARRIER_SETTINGS_ACTIVITY_COMPONENT_NAME_STRING = "carrier_settings_activity_component_name_string";
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 633067d..5a24198 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -30,7 +30,6 @@
import static android.view.MotionEvent.ACTION_HOVER_ENTER;
import static android.view.MotionEvent.ACTION_HOVER_EXIT;
import static android.view.MotionEvent.ACTION_MOVE;
-import static android.view.MotionEvent.ACTION_OUTSIDE;
import static android.view.MotionEvent.ACTION_UP;
import static android.view.WindowInsets.Type.statusBars;
@@ -118,6 +117,8 @@
import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder;
+import kotlin.Unit;
+
import java.io.PrintWriter;
import java.util.Optional;
import java.util.function.Supplier;
@@ -418,13 +419,13 @@
mWindowDecorByTaskId.remove(taskInfo.taskId);
}
- private void onMaximizeOrRestore(int taskId, String tag) {
+ private void onMaximizeOrRestore(int taskId, String source) {
final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
if (decoration == null) {
return;
}
mInteractionJankMonitor.begin(
- decoration.mTaskSurface, mContext, Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW, tag);
+ decoration.mTaskSurface, mContext, Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW, source);
mDesktopTasksController.toggleDesktopTaskSize(decoration.mTaskInfo);
decoration.closeHandleMenu();
decoration.closeMaximizeMenu();
@@ -441,10 +442,14 @@
decoration.closeMaximizeMenu();
}
- private void onOpenInBrowser(@NonNull DesktopModeWindowDecoration decor, @NonNull Uri uri) {
+ private void onOpenInBrowser(int taskId, @NonNull Uri uri) {
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
+ if (decoration == null) {
+ return;
+ }
openInBrowser(uri);
- decor.closeHandleMenu();
- decor.closeMaximizeMenu();
+ decoration.closeHandleMenu();
+ decoration.closeMaximizeMenu();
}
private void openInBrowser(Uri uri) {
@@ -454,6 +459,57 @@
mContext.startActivity(intent);
}
+ private void onToDesktop(int taskId, DesktopModeTransitionSource source) {
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
+ if (decoration == null) {
+ return;
+ }
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ mInteractionJankMonitor.begin(decoration.mTaskSurface, mContext,
+ CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU);
+ // App sometimes draws before the insets from WindowDecoration#relayout have
+ // been added, so they must be added here
+ decoration.addCaptionInset(wct);
+ mDesktopTasksController.moveTaskToDesktop(taskId, wct, source);
+ decoration.closeHandleMenu();
+ }
+
+ private void onToFullscreen(int taskId) {
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
+ if (decoration == null) {
+ return;
+ }
+ decoration.closeHandleMenu();
+ if (isTaskInSplitScreen(taskId)) {
+ mSplitScreenController.moveTaskToFullscreen(taskId,
+ SplitScreenController.EXIT_REASON_DESKTOP_MODE);
+ } else {
+ mDesktopTasksController.moveToFullscreen(taskId,
+ DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON);
+ }
+ }
+
+ private void onToSplitScreen(int taskId) {
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
+ if (decoration == null) {
+ return;
+ }
+ decoration.closeHandleMenu();
+ // When the app enters split-select, the handle will no longer be visible, meaning
+ // we shouldn't receive input for it any longer.
+ decoration.disposeStatusBarInputLayer();
+ mDesktopTasksController.requestSplit(decoration.mTaskInfo, false /* leftOrTop */);
+ }
+
+ private void onNewWindow(int taskId) {
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
+ if (decoration == null) {
+ return;
+ }
+ decoration.closeHandleMenu();
+ mDesktopTasksController.openNewWindow(decoration.mTaskInfo);
+ }
+
private class DesktopModeTouchEventListener extends GestureDetector.SimpleOnGestureListener
implements View.OnClickListener, View.OnTouchListener, View.OnLongClickListener,
View.OnGenericMotionListener, DragDetector.MotionEventHandler {
@@ -511,40 +567,6 @@
moveTaskToFront(decoration.mTaskInfo);
decoration.createHandleMenu(mSplitScreenController);
}
- } else if (id == R.id.desktop_button) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- // App sometimes draws before the insets from WindowDecoration#relayout have
- // been added, so they must be added here
- mInteractionJankMonitor.begin(decoration.mTaskSurface, mContext,
- CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU);
- mWindowDecorByTaskId.get(mTaskId).addCaptionInset(wct);
- mDesktopTasksController.moveTaskToDesktop(mTaskId, wct,
- DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON);
- decoration.closeHandleMenu();
- } else if (id == R.id.fullscreen_button) {
- decoration.closeHandleMenu();
- if (isTaskInSplitScreen(mTaskId)) {
- mSplitScreenController.moveTaskToFullscreen(mTaskId,
- SplitScreenController.EXIT_REASON_DESKTOP_MODE);
- } else {
- mDesktopTasksController.moveToFullscreen(mTaskId,
- DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON);
- }
- } else if (id == R.id.split_screen_button) {
- decoration.closeHandleMenu();
- // When the app enters split-select, the handle will no longer be visible, meaning
- // we shouldn't receive input for it any longer.
- decoration.disposeStatusBarInputLayer();
- mDesktopTasksController.requestSplit(decoration.mTaskInfo);
- } else if (id == R.id.open_in_browser_button) {
- // TODO(b/346441962): let the decoration handle the click gesture and only call back
- // to the ViewModel via #setOpenInBrowserClickListener
- decoration.onOpenInBrowserClick();
- } else if (id == R.id.collapse_menu_button) {
- decoration.closeHandleMenu();
- } else if (id == R.id.new_window_button) {
- decoration.closeHandleMenu();
- mDesktopTasksController.openNewWindow(decoration.mTaskInfo);
} else if (id == R.id.maximize_window) {
// TODO(b/346441962): move click detection logic into the decor's
// {@link AppHeaderViewHolder}. Let it encapsulate the that and have it report
@@ -559,16 +581,6 @@
public boolean onTouch(View v, MotionEvent e) {
final int id = v.getId();
final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
- if (e.getActionMasked() == ACTION_OUTSIDE) {
- if (id == R.id.handle_menu) {
- // Close handle menu on outside touch if menu is directly touchable; if not,
- // it will be handled by handleEventOutsideCaption.
- if (decoration.mTaskInfo.isFreeform()
- || Flags.enableAdditionalWindowsAboveStatusBar()) {
- decoration.closeHandleMenu();
- }
- }
- }
if ((e.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN) {
mTouchscreenInUse = e.getActionMasked() != ACTION_UP
&& e.getActionMasked() != ACTION_CANCEL;
@@ -1191,14 +1203,36 @@
final DesktopModeTouchEventListener touchEventListener =
new DesktopModeTouchEventListener(taskInfo, dragPositioningCallback);
- windowDecoration.setOnMaximizeOrRestoreClickListener(this::onMaximizeOrRestore);
- windowDecoration.setOnLeftSnapClickListener((taskId, tag) -> {
- onSnapResize(taskId, true /* isLeft */);
+ windowDecoration.setOnMaximizeOrRestoreClickListener(() -> {
+ onMaximizeOrRestore(taskInfo.taskId, "maximize_menu");
+ return Unit.INSTANCE;
});
- windowDecoration.setOnRightSnapClickListener((taskId, tag) -> {
- onSnapResize(taskId, false /* isLeft */);
+ windowDecoration.setOnLeftSnapClickListener(() -> {
+ onSnapResize(taskInfo.taskId, true /* isLeft */);
+ return Unit.INSTANCE;
});
- windowDecoration.setOpenInBrowserClickListener(this::onOpenInBrowser);
+ windowDecoration.setOnRightSnapClickListener(() -> {
+ onSnapResize(taskInfo.taskId, false /* isLeft */);
+ return Unit.INSTANCE;
+ });
+ windowDecoration.setOnToDesktopClickListener(desktopModeTransitionSource -> {
+ onToDesktop(taskInfo.taskId, desktopModeTransitionSource);
+ });
+ windowDecoration.setOnToFullscreenClickListener(() -> {
+ onToFullscreen(taskInfo.taskId);
+ return Unit.INSTANCE;
+ });
+ windowDecoration.setOnToSplitScreenClickListener(() -> {
+ onToSplitScreen(taskInfo.taskId);
+ return Unit.INSTANCE;
+ });
+ windowDecoration.setOpenInBrowserClickListener((uri) -> {
+ onOpenInBrowser(taskInfo.taskId, uri);
+ });
+ windowDecoration.setOnNewWindowClickListener(() -> {
+ onNewWindow(taskInfo.taskId);
+ return Unit.INSTANCE;
+ });
windowDecoration.setCaptionListeners(
touchEventListener, touchEventListener, touchEventListener, touchEventListener);
windowDecoration.setExclusionRegionListener(mExclusionRegionListener);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index b888344..24fb971 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -26,6 +26,7 @@
import static android.view.MotionEvent.ACTION_UP;
import static com.android.launcher3.icons.BaseIconFactory.MODE_DEFAULT;
+import static com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getFineResizeCornerSize;
import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getLargeResizeCornerSize;
@@ -77,18 +78,20 @@
import com.android.wm.shell.common.MultiInstanceHelper;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
import com.android.wm.shell.shared.desktopmode.DesktopModeFlags;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.splitscreen.SplitScreenController;
-import com.android.wm.shell.windowdecor.common.OnTaskActionClickListener;
import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
import com.android.wm.shell.windowdecor.viewholder.AppHandleViewHolder;
import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder;
import com.android.wm.shell.windowdecor.viewholder.WindowDecorationViewHolder;
import kotlin.Unit;
+import kotlin.jvm.functions.Function0;
+import java.util.function.Consumer;
import java.util.function.Supplier;
/**
@@ -115,9 +118,13 @@
private View.OnTouchListener mOnCaptionTouchListener;
private View.OnLongClickListener mOnCaptionLongClickListener;
private View.OnGenericMotionListener mOnCaptionGenericMotionListener;
- private OnTaskActionClickListener mOnMaximizeOrRestoreClickListener;
- private OnTaskActionClickListener mOnLeftSnapClickListener;
- private OnTaskActionClickListener mOnRightSnapClickListener;
+ private Function0<Unit> mOnMaximizeOrRestoreClickListener;
+ private Function0<Unit> mOnLeftSnapClickListener;
+ private Function0<Unit> mOnRightSnapClickListener;
+ private Consumer<DesktopModeTransitionSource> mOnToDesktopClickListener;
+ private Function0<Unit> mOnToFullscreenClickListener;
+ private Function0<Unit> mOnToSplitscreenClickListener;
+ private Function0<Unit> mOnNewWindowClickListener;
private DragPositioningCallback mDragPositioningCallback;
private DragResizeInputListener mDragResizeListener;
private DragDetector mDragDetector;
@@ -140,7 +147,7 @@
private CharSequence mAppName;
private CapturedLink mCapturedLink;
private Uri mGenericLink;
- private OpenInBrowserClickListener mOpenInBrowserClickListener;
+ private Consumer<Uri> mOpenInBrowserClickListener;
private ExclusionRegionListener mExclusionRegionListener;
@@ -228,24 +235,42 @@
* TODO(b/346441962): hook this up to double-tap and the header's maximize button, instead of
* having the ViewModel deal with parsing motion events.
*/
- void setOnMaximizeOrRestoreClickListener(OnTaskActionClickListener listener) {
+ void setOnMaximizeOrRestoreClickListener(Function0<Unit> listener) {
mOnMaximizeOrRestoreClickListener = listener;
}
- /**
- * Register a listener to be called back when one of the tasks snap-left action is triggered.
- */
- void setOnLeftSnapClickListener(OnTaskActionClickListener listener) {
+ /** Registers a listener to be called when the decoration's snap-left action is triggered.*/
+ void setOnLeftSnapClickListener(Function0<Unit> listener) {
mOnLeftSnapClickListener = listener;
}
- /**
- * Register a listener to be called back when one of the tasks' snap-right action is triggered.
- */
- void setOnRightSnapClickListener(OnTaskActionClickListener listener) {
+ /** Registers a listener to be called when the decoration's snap-right action is triggered. */
+ void setOnRightSnapClickListener(Function0<Unit> listener) {
mOnRightSnapClickListener = listener;
}
+ /** Registers a listener to be called when the decoration's to-desktop action is triggered. */
+ void setOnToDesktopClickListener(Consumer<DesktopModeTransitionSource> listener) {
+ mOnToDesktopClickListener = listener;
+ }
+
+ /**
+ * Registers a listener to be called when the decoration's to-fullscreen action is triggered.
+ */
+ void setOnToFullscreenClickListener(Function0<Unit> listener) {
+ mOnToFullscreenClickListener = listener;
+ }
+
+ /** Registers a listener to be called when the decoration's to-split action is triggered. */
+ void setOnToSplitScreenClickListener(Function0<Unit> listener) {
+ mOnToSplitscreenClickListener = listener;
+ }
+
+ /** Registers a listener to be called when the decoration's new window action is triggered. */
+ void setOnNewWindowClickListener(Function0<Unit> listener) {
+ mOnNewWindowClickListener = listener;
+ }
+
void setCaptionListeners(
View.OnClickListener onCaptionButtonClickListener,
View.OnTouchListener onCaptionTouchListener,
@@ -270,7 +295,7 @@
mDragDetector.setTouchSlop(ViewConfiguration.get(mContext).getScaledTouchSlop());
}
- void setOpenInBrowserClickListener(OpenInBrowserClickListener listener) {
+ void setOpenInBrowserClickListener(Consumer<Uri> listener) {
mOpenInBrowserClickListener = listener;
}
@@ -445,14 +470,6 @@
}
}
- void onOpenInBrowserClick() {
- if (mOpenInBrowserClickListener == null || mHandleMenu == null) {
- return;
- }
- mOpenInBrowserClickListener.onClick(this, mHandleMenu.getOpenInBrowserLink());
- onCapturedLinkExpired();
- }
-
@Nullable
private Uri getBrowserLink() {
// If the captured link is available and has not expired, return the captured link.
@@ -965,11 +982,8 @@
mHandleMenu = mHandleMenuFactory.create(
this,
mRelayoutParams.mLayoutResId,
- mOnCaptionButtonClickListener,
- mOnCaptionTouchListener,
mAppIconBitmap,
mAppName,
- mDisplayController,
splitScreenController,
DesktopModeStatus.canEnterDesktopMode(mContext),
Flags.enableDesktopWindowingMultiInstanceFeatures()
@@ -981,7 +995,28 @@
mResult.mCaptionX
);
mWindowDecorViewHolder.onHandleMenuOpened();
- mHandleMenu.show();
+ mHandleMenu.show(
+ /* onToDesktopClickListener= */ () -> {
+ mOnToDesktopClickListener.accept(APP_HANDLE_MENU_BUTTON);
+ return Unit.INSTANCE;
+ },
+ /* onToFullscreenClickListener= */ mOnToFullscreenClickListener,
+ /* onToSplitScreenClickListener= */ mOnToSplitscreenClickListener,
+ /* onNewWindowClickListener= */ mOnNewWindowClickListener,
+ /* openInBrowserClickListener= */ (uri) -> {
+ mOpenInBrowserClickListener.accept(uri);
+ onCapturedLinkExpired();
+ return Unit.INSTANCE;
+ },
+ /* onCloseMenuClickListener= */ () -> {
+ closeHandleMenu();
+ return Unit.INSTANCE;
+ },
+ /* onOutsideTouchListener= */ () -> {
+ closeHandleMenu();
+ return Unit.INSTANCE;
+ }
+ );
}
private void updateGenericLink() {
@@ -1320,14 +1355,6 @@
}
}
-
- /** Listener for the handle menu's "Open in browser" button */
- interface OpenInBrowserClickListener {
-
- /** Inform the implementing class that the "Open in browser" button has been clicked */
- void onClick(DesktopModeWindowDecoration decoration, Uri uri);
- }
-
interface ExclusionRegionListener {
/** Inform the implementing class of this task's change in region resize handles */
void onExclusionRegionChanged(int taskId, Region region);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
index 74a6407..b348d65 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
@@ -17,7 +17,8 @@
import android.annotation.ColorInt
import android.annotation.DimenRes
-import android.app.ActivityManager
+import android.annotation.SuppressLint
+import android.app.ActivityManager.RunningTaskInfo
import android.content.Context
import android.content.res.ColorStateList
import android.content.res.Resources
@@ -28,7 +29,9 @@
import android.graphics.PointF
import android.graphics.Rect
import android.net.Uri
+import android.view.LayoutInflater
import android.view.MotionEvent
+import android.view.MotionEvent.ACTION_OUTSIDE
import android.view.SurfaceControl
import android.view.View
import android.view.WindowManager
@@ -42,7 +45,6 @@
import androidx.core.view.isGone
import com.android.window.flags.Flags
import com.android.wm.shell.R
-import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.split.SplitScreenConstants
import com.android.wm.shell.splitscreen.SplitScreenController
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
@@ -63,22 +65,18 @@
class HandleMenu(
private val parentDecor: DesktopModeWindowDecoration,
private val layoutResId: Int,
- private val onClickListener: View.OnClickListener?,
- private val onTouchListener: View.OnTouchListener?,
private val appIconBitmap: Bitmap?,
private val appName: CharSequence?,
- private val displayController: DisplayController,
private val splitScreenController: SplitScreenController,
private val shouldShowWindowingPill: Boolean,
private val shouldShowNewWindowButton: Boolean,
- val openInBrowserLink: Uri?,
+ private val openInBrowserLink: Uri?,
private val captionWidth: Int,
private val captionHeight: Int,
captionX: Int
) {
private val context: Context = parentDecor.mDecorWindowContext
- private val taskInfo: ActivityManager.RunningTaskInfo = parentDecor.mTaskInfo
- private val decorThemeUtil = DecorThemeUtil(context)
+ private val taskInfo: RunningTaskInfo = parentDecor.mTaskInfo
private val isViewAboveStatusBar: Boolean
get() = (Flags.enableAdditionalWindowsAboveStatusBar() && !taskInfo.isFreeform)
@@ -94,10 +92,9 @@
private val marginMenuStart = loadDimensionPixelSize(
R.dimen.desktop_mode_handle_menu_margin_start)
- private var handleMenuAnimator: HandleMenuAnimator? = null
-
@VisibleForTesting
var handleMenuViewContainer: AdditionalViewContainer? = null
+ private var handleMenuView: HandleMenuView? = null
// Position of the handle menu used for laying out the handle view.
@VisibleForTesting
@@ -116,168 +113,88 @@
updateHandleMenuPillPositions(captionX)
}
- fun show() {
+ fun show(
+ onToDesktopClickListener: () -> Unit,
+ onToFullscreenClickListener: () -> Unit,
+ onToSplitScreenClickListener: () -> Unit,
+ onNewWindowClickListener: () -> Unit,
+ openInBrowserClickListener: (Uri) -> Unit,
+ onCloseMenuClickListener: () -> Unit,
+ onOutsideTouchListener: () -> Unit,
+ ) {
val ssg = SurfaceSyncGroup(TAG)
val t = SurfaceControl.Transaction()
- createHandleMenuViewContainer(t, ssg)
+ createHandleMenu(
+ t = t,
+ ssg = ssg,
+ onToDesktopClickListener = onToDesktopClickListener,
+ onToFullscreenClickListener = onToFullscreenClickListener,
+ onToSplitScreenClickListener = onToSplitScreenClickListener,
+ onNewWindowClickListener = onNewWindowClickListener,
+ openInBrowserClickListener = openInBrowserClickListener,
+ onCloseMenuClickListener = onCloseMenuClickListener,
+ onOutsideTouchListener = onOutsideTouchListener,
+ )
ssg.addTransaction(t)
ssg.markSyncReady()
- setupHandleMenu()
- animateHandleMenu()
+
+ handleMenuView?.animateOpenMenu()
}
- private fun createHandleMenuViewContainer(
+ private fun createHandleMenu(
t: SurfaceControl.Transaction,
- ssg: SurfaceSyncGroup
+ ssg: SurfaceSyncGroup,
+ onToDesktopClickListener: () -> Unit,
+ onToFullscreenClickListener: () -> Unit,
+ onToSplitScreenClickListener: () -> Unit,
+ onNewWindowClickListener: () -> Unit,
+ openInBrowserClickListener: (Uri) -> Unit,
+ onCloseMenuClickListener: () -> Unit,
+ onOutsideTouchListener: () -> Unit
) {
+ val handleMenuView = HandleMenuView(
+ context = context,
+ menuWidth = menuWidth,
+ captionHeight = captionHeight,
+ shouldShowWindowingPill = shouldShowWindowingPill,
+ shouldShowBrowserPill = shouldShowBrowserPill,
+ shouldShowNewWindowButton = shouldShowNewWindowButton
+ ).apply {
+ bind(taskInfo, appIconBitmap, appName)
+ this.onToDesktopClickListener = onToDesktopClickListener
+ this.onToFullscreenClickListener = onToFullscreenClickListener
+ this.onToSplitScreenClickListener = onToSplitScreenClickListener
+ this.onNewWindowClickListener = onNewWindowClickListener
+ this.onOpenInBrowserClickListener = {
+ openInBrowserClickListener.invoke(openInBrowserLink!!)
+ }
+ this.onCloseMenuClickListener = onCloseMenuClickListener
+ this.onOutsideTouchListener = onOutsideTouchListener
+ }
+
val x = handleMenuPosition.x.toInt()
val y = handleMenuPosition.y.toInt()
handleMenuViewContainer =
if (!taskInfo.isFreeform && Flags.enableAdditionalWindowsAboveStatusBar()) {
AdditionalSystemViewContainer(
context = context,
- layoutId = R.layout.desktop_mode_window_decor_handle_menu,
taskId = taskInfo.taskId,
x = x,
y = y,
width = menuWidth,
height = menuHeight,
flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
- WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+ WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
+ view = handleMenuView.rootView
)
} else {
parentDecor.addWindow(
- R.layout.desktop_mode_window_decor_handle_menu, "Handle Menu",
- t, ssg, x, y, menuWidth, menuHeight
+ handleMenuView.rootView, "Handle Menu", t, ssg, x, y, menuWidth, menuHeight
)
}
- handleMenuViewContainer?.view?.let { view ->
- handleMenuAnimator =
- HandleMenuAnimator(view, menuWidth, captionHeight.toFloat())
- }
- }
- /**
- * Animates the appearance of the handle menu and its three pills.
- */
- private fun animateHandleMenu() {
- when {
- taskInfo.isFullscreen || taskInfo.isMultiWindow -> {
- handleMenuAnimator?.animateCaptionHandleExpandToOpen()
- }
- else -> {
- handleMenuAnimator?.animateOpen()
- }
- }
- }
-
- /**
- * Set up all three pills of the handle menu: app info pill, windowing pill, & more actions
- * pill.
- */
- private fun setupHandleMenu() {
- val handleMenu = handleMenuViewContainer?.view ?: return
- handleMenu.setOnTouchListener(onTouchListener)
-
- val style = calculateMenuStyle()
- setupAppInfoPill(handleMenu, style)
- if (shouldShowWindowingPill) {
- setupWindowingPill(handleMenu, style)
- }
- setupMoreActionsPill(handleMenu, style)
- setupOpenInBrowserPill(handleMenu, style)
- }
-
- /**
- * Set up interactive elements of handle menu's app info pill.
- */
- private fun setupAppInfoPill(handleMenu: View, style: MenuStyle) {
- val pill = handleMenu.requireViewById<View>(R.id.app_info_pill).apply {
- background.colorFilter = BlendModeColorFilter(style.backgroundColor, BlendMode.MULTIPLY)
- }
-
- pill.requireViewById<HandleMenuImageButton>(R.id.collapse_menu_button)
- .let { collapseBtn ->
- collapseBtn.imageTintList = ColorStateList.valueOf(style.textColor)
- collapseBtn.setOnClickListener(onClickListener)
- collapseBtn.taskInfo = taskInfo
- }
- pill.requireViewById<ImageView>(R.id.application_icon).let { appIcon ->
- appIcon.setImageBitmap(appIconBitmap)
- }
- pill.requireViewById<TextView>(R.id.application_name).let { appNameView ->
- appNameView.text = appName
- appNameView.setTextColor(style.textColor)
- }
- }
-
- /**
- * Set up interactive elements and color of handle menu's windowing pill.
- */
- private fun setupWindowingPill(handleMenu: View, style: MenuStyle) {
- val pill = handleMenu.requireViewById<View>(R.id.windowing_pill).apply {
- background.colorFilter = BlendModeColorFilter(style.backgroundColor, BlendMode.MULTIPLY)
- }
-
- val fullscreenBtn = pill.requireViewById<ImageButton>(R.id.fullscreen_button)
- val splitscreenBtn = pill.requireViewById<ImageButton>(R.id.split_screen_button)
- val floatingBtn = pill.requireViewById<ImageButton>(R.id.floating_button)
- // TODO: Remove once implemented.
- floatingBtn.visibility = View.GONE
- val desktopBtn = handleMenu.requireViewById<ImageButton>(R.id.desktop_button)
-
- fullscreenBtn.setOnClickListener(onClickListener)
- splitscreenBtn.setOnClickListener(onClickListener)
- floatingBtn.setOnClickListener(onClickListener)
- desktopBtn.setOnClickListener(onClickListener)
-
- fullscreenBtn.isSelected = taskInfo.isFullscreen
- fullscreenBtn.imageTintList = style.windowingButtonColor
- splitscreenBtn.isSelected = taskInfo.isMultiWindow
- splitscreenBtn.imageTintList = style.windowingButtonColor
- floatingBtn.isSelected = taskInfo.isPinned
- floatingBtn.imageTintList = style.windowingButtonColor
- desktopBtn.isSelected = taskInfo.isFreeform
- desktopBtn.imageTintList = style.windowingButtonColor
- }
-
- /**
- * Set up interactive elements & height of handle menu's more actions pill
- */
- private fun setupMoreActionsPill(handleMenu: View, style: MenuStyle) {
- val moreActionsPill = handleMenu.findViewById<View>(R.id.more_actions_pill)
- moreActionsPill.isGone = !shouldShowNewWindowButton && !SHOULD_SHOW_SCREENSHOT_BUTTON
- if (!moreActionsPill.isGone) {
- handleMenu.requireViewById<Button>(R.id.screenshot_button).apply {
- isGone = !SHOULD_SHOW_SCREENSHOT_BUTTON
- background.colorFilter =
- BlendModeColorFilter(style.backgroundColor, BlendMode.MULTIPLY)
- setTextColor(style.textColor)
- compoundDrawableTintList = ColorStateList.valueOf(style.textColor)
- }
- handleMenu.findViewById<Button>(R.id.new_window_button).apply {
- isGone = !shouldShowNewWindowButton
- background.colorFilter =
- BlendModeColorFilter(style.backgroundColor, BlendMode.MULTIPLY)
- setTextColor(style.textColor)
- compoundDrawableTintList = ColorStateList.valueOf(style.textColor)
- if (!isGone) setOnClickListener(onClickListener)
- }
- }
- }
-
- private fun setupOpenInBrowserPill(handleMenu: View, style: MenuStyle) {
- val pill = handleMenu.requireViewById<View>(R.id.open_in_browser_pill).apply {
- isGone = !shouldShowBrowserPill
- background.colorFilter = BlendModeColorFilter(style.backgroundColor, BlendMode.MULTIPLY)
- }
-
- pill.requireViewById<Button>(R.id.open_in_browser_button).let { browserButton ->
- browserButton.setOnClickListener(onClickListener)
- browserButton.setTextColor(style.textColor)
- browserButton.compoundDrawableTintList = ColorStateList.valueOf(style.textColor)
- }
+ this.handleMenuView = handleMenuView
}
/**
@@ -371,16 +288,8 @@
fun checkMotionEvent(ev: MotionEvent) {
// If the menu view is above status bar, we can let the views handle input directly.
if (isViewAboveStatusBar) return
- val handleMenu = handleMenuViewContainer?.view ?: return
- val collapse = handleMenu.findViewById<HandleMenuImageButton>(R.id.collapse_menu_button)
val inputPoint = translateInputToLocalSpace(ev)
- val inputInCollapseButton = pointInView(collapse, inputPoint.x, inputPoint.y)
- val action = ev.actionMasked
- collapse.isHovered = inputInCollapseButton && action != MotionEvent.ACTION_UP
- collapse.isPressed = inputInCollapseButton && action == MotionEvent.ACTION_DOWN
- if (action == MotionEvent.ACTION_UP && inputInCollapseButton) {
- collapse.performClick()
- }
+ handleMenuView?.checkMotionEvent(ev, inputPoint)
}
// Translate the input point from display coordinates to the same space as the handle menu.
@@ -480,45 +389,241 @@
}
fun close() {
- val after = {
+ handleMenuView?.animateCloseMenu {
handleMenuViewContainer?.releaseView()
handleMenuViewContainer = null
}
- if (taskInfo.isFullscreen || taskInfo.isMultiWindow) {
- handleMenuAnimator?.animateCollapseIntoHandleClose(after)
- } else {
- handleMenuAnimator?.animateClose(after)
- }
}
- private fun calculateMenuStyle(): MenuStyle {
- val colorScheme = decorThemeUtil.getColorScheme(taskInfo)
- return MenuStyle(
- backgroundColor = colorScheme.surfaceBright.toArgb(),
- textColor = colorScheme.onSurface.toArgb(),
- windowingButtonColor = ColorStateList(
- arrayOf(
- intArrayOf(android.R.attr.state_pressed),
- intArrayOf(android.R.attr.state_focused),
- intArrayOf(android.R.attr.state_selected),
- intArrayOf(),
+ /** The view within the Handle Menu, with options to change the windowing mode and more. */
+ @SuppressLint("ClickableViewAccessibility")
+ class HandleMenuView(
+ context: Context,
+ menuWidth: Int,
+ captionHeight: Int,
+ private val shouldShowWindowingPill: Boolean,
+ private val shouldShowBrowserPill: Boolean,
+ private val shouldShowNewWindowButton: Boolean
+ ) {
+ val rootView = LayoutInflater.from(context)
+ .inflate(R.layout.desktop_mode_window_decor_handle_menu, null /* root */) as View
+
+ // App Info Pill.
+ private val appInfoPill = rootView.requireViewById<View>(R.id.app_info_pill)
+ private val collapseMenuButton = appInfoPill.requireViewById<HandleMenuImageButton>(
+ R.id.collapse_menu_button)
+ private val appIconView = appInfoPill.requireViewById<ImageView>(R.id.application_icon)
+ private val appNameView = appInfoPill.requireViewById<TextView>(R.id.application_name)
+
+ // Windowing Pill.
+ private val windowingPill = rootView.requireViewById<View>(R.id.windowing_pill)
+ private val fullscreenBtn = windowingPill.requireViewById<ImageButton>(
+ R.id.fullscreen_button)
+ private val splitscreenBtn = windowingPill.requireViewById<ImageButton>(
+ R.id.split_screen_button)
+ private val floatingBtn = windowingPill.requireViewById<ImageButton>(R.id.floating_button)
+ private val desktopBtn = windowingPill.requireViewById<ImageButton>(R.id.desktop_button)
+
+ // More Actions Pill.
+ private val moreActionsPill = rootView.requireViewById<View>(R.id.more_actions_pill)
+ private val screenshotBtn = moreActionsPill.requireViewById<Button>(R.id.screenshot_button)
+ private val newWindowBtn = moreActionsPill.requireViewById<Button>(R.id.new_window_button)
+
+ // Open in Browser Pill.
+ private val openInBrowserPill = rootView.requireViewById<View>(R.id.open_in_browser_pill)
+ private val browserBtn = openInBrowserPill.requireViewById<Button>(
+ R.id.open_in_browser_button)
+
+ private val decorThemeUtil = DecorThemeUtil(context)
+ private val animator = HandleMenuAnimator(rootView, menuWidth, captionHeight.toFloat())
+
+ private lateinit var taskInfo: RunningTaskInfo
+ private lateinit var style: MenuStyle
+
+ var onToDesktopClickListener: (() -> Unit)? = null
+ var onToFullscreenClickListener: (() -> Unit)? = null
+ var onToSplitScreenClickListener: (() -> Unit)? = null
+ var onNewWindowClickListener: (() -> Unit)? = null
+ var onOpenInBrowserClickListener: (() -> Unit)? = null
+ var onCloseMenuClickListener: (() -> Unit)? = null
+ var onOutsideTouchListener: (() -> Unit)? = null
+
+ init {
+ fullscreenBtn.setOnClickListener { onToFullscreenClickListener?.invoke() }
+ splitscreenBtn.setOnClickListener { onToSplitScreenClickListener?.invoke() }
+ desktopBtn.setOnClickListener { onToDesktopClickListener?.invoke() }
+ browserBtn.setOnClickListener { onOpenInBrowserClickListener?.invoke() }
+ collapseMenuButton.setOnClickListener { onCloseMenuClickListener?.invoke() }
+ newWindowBtn.setOnClickListener { onNewWindowClickListener?.invoke() }
+
+ rootView.setOnTouchListener { _, event ->
+ if (event.actionMasked == ACTION_OUTSIDE) {
+ onOutsideTouchListener?.invoke()
+ return@setOnTouchListener false
+ }
+ return@setOnTouchListener true
+ }
+ }
+
+ /** Binds the menu views to the new data. */
+ fun bind(taskInfo: RunningTaskInfo, appIconBitmap: Bitmap?, appName: CharSequence?) {
+ this.taskInfo = taskInfo
+ this.style = calculateMenuStyle(taskInfo)
+
+ bindAppInfoPill(style, appIconBitmap, appName)
+ if (shouldShowWindowingPill) {
+ bindWindowingPill(style)
+ }
+ bindMoreActionsPill(style)
+ bindOpenInBrowserPill(style)
+ }
+
+ /** Animates the menu opening. */
+ fun animateOpenMenu() {
+ if (taskInfo.isFullscreen || taskInfo.isMultiWindow) {
+ animator.animateCaptionHandleExpandToOpen()
+ } else {
+ animator.animateOpen()
+ }
+ }
+
+ /** Animates the menu closing. */
+ fun animateCloseMenu(onAnimFinish: () -> Unit) {
+ if (taskInfo.isFullscreen || taskInfo.isMultiWindow) {
+ animator.animateCollapseIntoHandleClose(onAnimFinish)
+ } else {
+ animator.animateClose(onAnimFinish)
+ }
+ }
+
+ /**
+ * Checks whether a motion event falls inside this menu, and invokes a click of the
+ * collapse button if needed.
+ * Note: should only be called when regular click detection doesn't work because input is
+ * detected through the status bar layer with a global input monitor.
+ */
+ fun checkMotionEvent(ev: MotionEvent, inputPointLocal: PointF) {
+ val inputInCollapseButton = pointInView(
+ collapseMenuButton,
+ inputPointLocal.x,
+ inputPointLocal.y
+ )
+ val action = ev.actionMasked
+ collapseMenuButton.isHovered = inputInCollapseButton
+ && action != MotionEvent.ACTION_UP
+ collapseMenuButton.isPressed = inputInCollapseButton
+ && action == MotionEvent.ACTION_DOWN
+ if (action == MotionEvent.ACTION_UP && inputInCollapseButton) {
+ collapseMenuButton.performClick()
+ }
+ }
+
+ private fun pointInView(v: View?, x: Float, y: Float): Boolean {
+ return v != null && v.left <= x && v.right >= x && v.top <= y && v.bottom >= y
+ }
+
+ private fun calculateMenuStyle(taskInfo: RunningTaskInfo): MenuStyle {
+ val colorScheme = decorThemeUtil.getColorScheme(taskInfo)
+ return MenuStyle(
+ backgroundColor = colorScheme.surfaceBright.toArgb(),
+ textColor = colorScheme.onSurface.toArgb(),
+ windowingButtonColor = ColorStateList(
+ arrayOf(
+ intArrayOf(android.R.attr.state_pressed),
+ intArrayOf(android.R.attr.state_focused),
+ intArrayOf(android.R.attr.state_selected),
+ intArrayOf(),
+ ),
+ intArrayOf(
+ colorScheme.onSurface.toArgb(),
+ colorScheme.onSurface.toArgb(),
+ colorScheme.primary.toArgb(),
+ colorScheme.onSurface.toArgb(),
+ )
),
- intArrayOf(
- colorScheme.onSurface.toArgb(),
- colorScheme.onSurface.toArgb(),
- colorScheme.primary.toArgb(),
- colorScheme.onSurface.toArgb(),
+ )
+ }
+
+ private fun bindAppInfoPill(
+ style: MenuStyle,
+ appIconBitmap: Bitmap?,
+ appName: CharSequence?
+ ) {
+ appInfoPill.background.colorFilter = BlendModeColorFilter(
+ style.backgroundColor, BlendMode.MULTIPLY
+ )
+
+ collapseMenuButton.apply {
+ imageTintList = ColorStateList.valueOf(style.textColor)
+ this.taskInfo = [email protected]
+ }
+ appIconView.setImageBitmap(appIconBitmap)
+ appNameView.apply {
+ text = appName
+ setTextColor(style.textColor)
+ }
+ }
+
+ private fun bindWindowingPill(style: MenuStyle) {
+ windowingPill.background.colorFilter = BlendModeColorFilter(
+ style.backgroundColor, BlendMode.MULTIPLY
+ )
+
+ // TODO: Remove once implemented.
+ floatingBtn.visibility = View.GONE
+
+ fullscreenBtn.isSelected = taskInfo.isFullscreen
+ fullscreenBtn.imageTintList = style.windowingButtonColor
+ splitscreenBtn.isSelected = taskInfo.isMultiWindow
+ splitscreenBtn.imageTintList = style.windowingButtonColor
+ floatingBtn.isSelected = taskInfo.isPinned
+ floatingBtn.imageTintList = style.windowingButtonColor
+ desktopBtn.isSelected = taskInfo.isFreeform
+ desktopBtn.imageTintList = style.windowingButtonColor
+ }
+
+ private fun bindMoreActionsPill(style: MenuStyle) {
+ moreActionsPill.apply {
+ isGone = !shouldShowNewWindowButton && !SHOULD_SHOW_SCREENSHOT_BUTTON
+ }
+ screenshotBtn.apply {
+ isGone = !SHOULD_SHOW_SCREENSHOT_BUTTON
+ background.colorFilter =
+ BlendModeColorFilter(style.backgroundColor, BlendMode.MULTIPLY
)
- ),
+ setTextColor(style.textColor)
+ compoundDrawableTintList = ColorStateList.valueOf(style.textColor)
+ }
+ newWindowBtn.apply {
+ isGone = !shouldShowNewWindowButton
+ background.colorFilter =
+ BlendModeColorFilter(style.backgroundColor, BlendMode.MULTIPLY)
+ setTextColor(style.textColor)
+ compoundDrawableTintList = ColorStateList.valueOf(style.textColor)
+ }
+ }
+
+ private fun bindOpenInBrowserPill(style: MenuStyle) {
+ openInBrowserPill.apply {
+ isGone = !shouldShowBrowserPill
+ background.colorFilter = BlendModeColorFilter(
+ style.backgroundColor, BlendMode.MULTIPLY
+ )
+ }
+
+ browserBtn.apply {
+ setTextColor(style.textColor)
+ compoundDrawableTintList = ColorStateList.valueOf(style.textColor)
+ }
+ }
+
+ private data class MenuStyle(
+ @ColorInt val backgroundColor: Int,
+ @ColorInt val textColor: Int,
+ val windowingButtonColor: ColorStateList,
)
}
- private data class MenuStyle(
- @ColorInt val backgroundColor: Int,
- @ColorInt val textColor: Int,
- val windowingButtonColor: ColorStateList,
- )
-
companion object {
private const val TAG = "HandleMenu"
private const val SHOULD_SHOW_SCREENSHOT_BUTTON = false
@@ -530,11 +635,8 @@
fun create(
parentDecor: DesktopModeWindowDecoration,
layoutResId: Int,
- onClickListener: View.OnClickListener?,
- onTouchListener: View.OnTouchListener?,
appIconBitmap: Bitmap?,
appName: CharSequence?,
- displayController: DisplayController,
splitScreenController: SplitScreenController,
shouldShowWindowingPill: Boolean,
shouldShowNewWindowButton: Boolean,
@@ -550,11 +652,8 @@
override fun create(
parentDecor: DesktopModeWindowDecoration,
layoutResId: Int,
- onClickListener: View.OnClickListener?,
- onTouchListener: View.OnTouchListener?,
appIconBitmap: Bitmap?,
appName: CharSequence?,
- displayController: DisplayController,
splitScreenController: SplitScreenController,
shouldShowWindowingPill: Boolean,
shouldShowNewWindowButton: Boolean,
@@ -566,11 +665,8 @@
return HandleMenu(
parentDecor,
layoutResId,
- onClickListener,
- onTouchListener,
appIconBitmap,
appName,
- displayController,
splitScreenController,
shouldShowWindowingPill,
shouldShowNewWindowButton,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
index aa2ce0f..013f506 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
@@ -66,7 +66,6 @@
import com.android.wm.shell.windowdecor.common.DecorThemeUtil
import com.android.wm.shell.windowdecor.common.OPACITY_12
import com.android.wm.shell.windowdecor.common.OPACITY_40
-import com.android.wm.shell.windowdecor.common.OnTaskActionClickListener
import com.android.wm.shell.windowdecor.common.withAlpha
import java.util.function.Supplier
@@ -102,15 +101,15 @@
/** Creates and shows the maximize window. */
fun show(
- onMaximizeClickListener: OnTaskActionClickListener,
- onLeftSnapClickListener: OnTaskActionClickListener,
- onRightSnapClickListener: OnTaskActionClickListener,
+ onMaximizeOrRestoreClickListener: () -> Unit,
+ onLeftSnapClickListener: () -> Unit,
+ onRightSnapClickListener: () -> Unit,
onHoverListener: (Boolean) -> Unit,
onOutsideTouchListener: () -> Unit,
) {
if (maximizeMenu != null) return
createMaximizeMenu(
- onMaximizeClickListener = onMaximizeClickListener,
+ onMaximizeClickListener = onMaximizeOrRestoreClickListener,
onLeftSnapClickListener = onLeftSnapClickListener,
onRightSnapClickListener = onRightSnapClickListener,
onHoverListener = onHoverListener,
@@ -129,9 +128,9 @@
/** Create a maximize menu that is attached to the display area. */
private fun createMaximizeMenu(
- onMaximizeClickListener: OnTaskActionClickListener,
- onLeftSnapClickListener: OnTaskActionClickListener,
- onRightSnapClickListener: OnTaskActionClickListener,
+ onMaximizeClickListener: () -> Unit,
+ onLeftSnapClickListener: () -> Unit,
+ onRightSnapClickListener: () -> Unit,
onHoverListener: (Boolean) -> Unit,
onOutsideTouchListener: () -> Unit
) {
@@ -165,17 +164,10 @@
menuHeight = menuHeight,
menuPadding = menuPadding,
).also { menuView ->
- val taskId = taskInfo.taskId
menuView.bind(taskInfo)
- menuView.onMaximizeClickListener = {
- onMaximizeClickListener.onClick(taskId, "maximize_menu_option")
- }
- menuView.onLeftSnapClickListener = {
- onLeftSnapClickListener.onClick(taskId, "left_snap_option")
- }
- menuView.onRightSnapClickListener = {
- onRightSnapClickListener.onClick(taskId, "right_snap_option")
- }
+ menuView.onMaximizeClickListener = onMaximizeClickListener
+ menuView.onLeftSnapClickListener = onLeftSnapClickListener
+ menuView.onRightSnapClickListener = onRightSnapClickListener
menuView.onMenuHoverListener = onHoverListener
menuView.onOutsideTouchListener = onOutsideTouchListener
viewHost.setView(menuView.rootView, lp)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 6828560..4cab6e4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -613,7 +613,7 @@
* Create a window associated with this WindowDecoration.
* Note that subclass must dispose of this when the task is hidden/closed.
*
- * @param layoutId layout to make the window from
+ * @param v View to attach to the window
* @param t the transaction to apply
* @param xPos x position of new window
* @param yPos y position of new window
@@ -621,9 +621,9 @@
* @param height height of new window
* @return the {@link AdditionalViewHostViewContainer} that was added.
*/
- AdditionalViewHostViewContainer addWindow(int layoutId, String namePrefix,
- SurfaceControl.Transaction t, SurfaceSyncGroup ssg, int xPos, int yPos,
- int width, int height) {
+ AdditionalViewHostViewContainer addWindow(@NonNull View v, @NonNull String namePrefix,
+ @NonNull SurfaceControl.Transaction t, @NonNull SurfaceSyncGroup ssg,
+ int xPos, int yPos, int width, int height) {
final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get();
SurfaceControl windowSurfaceControl = builder
.setName(namePrefix + " of Task=" + mTaskInfo.taskId)
@@ -631,8 +631,6 @@
.setParent(mDecorationContainerSurface)
.setCallsite("WindowDecoration.addWindow")
.build();
- View v = LayoutInflater.from(mDecorWindowContext).inflate(layoutId, null);
-
t.setPosition(windowSurfaceControl, xPos, yPos)
.setWindowCrop(windowSurfaceControl, width, height)
.show(windowSurfaceControl);
@@ -653,6 +651,25 @@
}
/**
+ * Create a window associated with this WindowDecoration.
+ * Note that subclass must dispose of this when the task is hidden/closed.
+ *
+ * @param layoutId layout to make the window from
+ * @param t the transaction to apply
+ * @param xPos x position of new window
+ * @param yPos y position of new window
+ * @param width width of new window
+ * @param height height of new window
+ * @return the {@link AdditionalViewHostViewContainer} that was added.
+ */
+ AdditionalViewHostViewContainer addWindow(int layoutId, String namePrefix,
+ SurfaceControl.Transaction t, SurfaceSyncGroup ssg, int xPos, int yPos,
+ int width, int height) {
+ final View v = LayoutInflater.from(mDecorWindowContext).inflate(layoutId, null);
+ return addWindow(v, namePrefix, t, ssg, xPos, yPos, width, height);
+ }
+
+ /**
* Adds caption inset source to a WCT
*/
public void addCaptionInset(WindowContainerTransaction wct) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt
index f1370bb..cadd80e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt
@@ -16,6 +16,7 @@
package com.android.wm.shell.windowdecor.additionalviewcontainer
+import android.annotation.LayoutRes
import android.content.Context
import android.graphics.PixelFormat
import android.view.Gravity
@@ -29,24 +30,52 @@
* for view containers that should be above the status bar layer.
*/
class AdditionalSystemViewContainer(
- private val context: Context,
+ context: Context,
taskId: Int,
x: Int,
y: Int,
width: Int,
height: Int,
flags: Int,
- layoutId: Int? = null
-) : AdditionalViewContainer() {
override val view: View
+) : AdditionalViewContainer() {
+
+ constructor(
+ context: Context,
+ taskId: Int,
+ x: Int,
+ y: Int,
+ width: Int,
+ height: Int,
+ flags: Int,
+ @LayoutRes layoutId: Int
+ ) : this(
+ context = context,
+ taskId = taskId,
+ x = x,
+ y = y,
+ width = width,
+ height = height,
+ flags = flags,
+ view = LayoutInflater.from(context).inflate(layoutId, null /* parent */)
+ )
+
+ constructor(
+ context: Context, taskId: Int, x: Int, y: Int, width: Int, height: Int, flags: Int
+ ) : this(
+ context = context,
+ taskId = taskId,
+ x = x,
+ y = y,
+ width = width,
+ height = height,
+ flags = flags,
+ view = View(context)
+ )
+
val windowManager: WindowManager? = context.getSystemService(WindowManager::class.java)
init {
- if (layoutId != null) {
- view = LayoutInflater.from(context).inflate(layoutId, null)
- } else {
- view = View(context)
- }
val lp = WindowManager.LayoutParams(
width, height, x, y,
WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/OnTaskActionClickListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/OnTaskActionClickListener.kt
deleted file mode 100644
index 14b9e7f..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/OnTaskActionClickListener.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.wm.shell.windowdecor.common
-
-/** A callback to be invoked when a Task's window decor element is clicked. */
-fun interface OnTaskActionClickListener {
- /**
- * Called when a task's decor element has been clicked.
- *
- * @param taskId the id of the task.
- * @param tag a readable identifier for the element.
- */
- fun onClick(taskId: Int, tag: String)
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index c3a0bd9..5e6d01b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -16,19 +16,22 @@
package com.android.wm.shell.windowdecor
import android.app.ActivityManager.RunningTaskInfo
-import android.app.WindowConfiguration
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
+import android.app.WindowConfiguration.WindowingMode
import android.content.ComponentName
import android.content.Context
+import android.content.Intent
import android.content.pm.ActivityInfo
import android.graphics.Rect
import android.hardware.display.DisplayManager
import android.hardware.display.VirtualDisplay
import android.hardware.input.InputManager
+import android.net.Uri
import android.os.Handler
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
@@ -37,6 +40,7 @@
import android.platform.test.flag.junit.DeviceFlagsValueProvider
import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
+import android.testing.TestableContext
import android.testing.TestableLooper.RunWithLooper
import android.util.SparseArray
import android.view.Choreographer
@@ -71,6 +75,7 @@
import com.android.wm.shell.common.MultiInstanceHelper
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource
import com.android.wm.shell.desktopmode.DesktopTasksController
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter
@@ -82,13 +87,17 @@
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener
-import com.android.wm.shell.windowdecor.common.OnTaskActionClickListener
+import java.util.Optional
+import java.util.function.Consumer
+import java.util.function.Supplier
+import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentCaptor.forClass
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.anyInt
@@ -97,13 +106,13 @@
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.kotlin.any
+import org.mockito.kotlin.argThat
import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.doNothing
import org.mockito.kotlin.eq
import org.mockito.kotlin.spy
import org.mockito.kotlin.whenever
import org.mockito.quality.Strictness
-import java.util.Optional
-import java.util.function.Supplier
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
@@ -147,22 +156,33 @@
@Mock private lateinit var mockGenericLinksParser: AppToWebGenericLinksParser
private val bgExecutor = TestShellExecutor()
@Mock private lateinit var mockMultiInstanceHelper: MultiInstanceHelper
+ private lateinit var spyContext: TestableContext
private val transactionFactory = Supplier<SurfaceControl.Transaction> {
SurfaceControl.Transaction()
}
private val windowDecorByTaskIdSpy = spy(SparseArray<DesktopModeWindowDecoration>())
+ private lateinit var mockitoSession: StaticMockitoSession
private lateinit var shellInit: ShellInit
private lateinit var desktopModeOnInsetsChangedListener: DesktopModeOnInsetsChangedListener
private lateinit var desktopModeWindowDecorViewModel: DesktopModeWindowDecorViewModel
@Before
fun setUp() {
+ mockitoSession =
+ mockitoSession()
+ .strictness(Strictness.LENIENT)
+ .spyStatic(DesktopModeStatus::class.java)
+ .startMocking()
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(Mockito.any()) }
+
+ spyContext = spy(mContext)
+ doNothing().`when`(spyContext).startActivity(any())
shellInit = ShellInit(mockShellExecutor)
windowDecorByTaskIdSpy.clear()
desktopModeWindowDecorViewModel = DesktopModeWindowDecorViewModel(
- mContext,
+ spyContext,
mockShellExecutor,
mockMainHandler,
mockMainChoreographer,
@@ -204,6 +224,11 @@
desktopModeOnInsetsChangedListener = listenerCaptor.firstValue
}
+ @After
+ fun tearDown() {
+ mockitoSession.finishMocking()
+ }
+
@Test
fun testDeleteCaptionOnChangeTransitionWhenNecessary() {
val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
@@ -299,7 +324,7 @@
whenever(view.id).thenReturn(R.id.back_button)
val inputManager = mock(InputManager::class.java)
- mContext.addMockSystemService(InputManager::class.java, inputManager)
+ spyContext.addMockSystemService(InputManager::class.java, inputManager)
val freeformTaskTransitionStarter = mock(FreeformTaskTransitionStarter::class.java)
desktopModeWindowDecorViewModel
@@ -337,24 +362,16 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
fun testDecorationIsCreatedForTopTranslucentActivitiesWithStyleFloating() {
- val mockitoSession: StaticMockitoSession = mockitoSession()
- .strictness(Strictness.LENIENT)
- .spyStatic(DesktopModeStatus::class.java)
- .startMocking()
- try {
- val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true).apply {
- isTopActivityTransparent = true
- isTopActivityStyleFloating = true
- numActivities = 1
- }
- doReturn(true).`when` { DesktopModeStatus.canEnterDesktopMode(any()) }
- setUpMockDecorationsForTasks(task)
-
- onTaskOpening(task)
- assertTrue(windowDecorByTaskIdSpy.contains(task.taskId))
- } finally {
- mockitoSession.finishMocking()
+ val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true).apply {
+ isTopActivityTransparent = true
+ isTopActivityStyleFloating = true
+ numActivities = 1
}
+ doReturn(true).`when` { DesktopModeStatus.canEnterDesktopMode(any()) }
+ setUpMockDecorationsForTasks(task)
+
+ onTaskOpening(task)
+ assertTrue(windowDecorByTaskIdSpy.contains(task.taskId))
}
@Test
@@ -466,96 +483,70 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
fun testWindowDecor_desktopModeUnsupportedOnDevice_decorNotCreated() {
- val mockitoSession: StaticMockitoSession = mockitoSession()
- .strictness(Strictness.LENIENT)
- .spyStatic(DesktopModeStatus::class.java)
- .startMocking()
- try {
- // Simulate default enforce device restrictions system property
- whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(true)
+ // Simulate default enforce device restrictions system property
+ whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(true)
- val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true)
- // Simulate device that doesn't support desktop mode
- doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true)
+ // Simulate device that doesn't support desktop mode
+ doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
- onTaskOpening(task)
- assertFalse(windowDecorByTaskIdSpy.contains(task.taskId))
- } finally {
- mockitoSession.finishMocking()
- }
+ onTaskOpening(task)
+ assertFalse(windowDecorByTaskIdSpy.contains(task.taskId))
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
fun testWindowDecor_desktopModeUnsupportedOnDevice_deviceRestrictionsOverridden_decorCreated() {
- val mockitoSession: StaticMockitoSession = mockitoSession()
- .strictness(Strictness.LENIENT)
- .spyStatic(DesktopModeStatus::class.java)
- .startMocking()
- try {
- // Simulate enforce device restrictions system property overridden to false
- whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(false)
- // Simulate device that doesn't support desktop mode
- doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ // Simulate enforce device restrictions system property overridden to false
+ whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(false)
+ // Simulate device that doesn't support desktop mode
+ doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
- val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true)
- setUpMockDecorationsForTasks(task)
+ val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true)
+ setUpMockDecorationsForTasks(task)
- onTaskOpening(task)
- assertTrue(windowDecorByTaskIdSpy.contains(task.taskId))
- } finally {
- mockitoSession.finishMocking()
- }
+ onTaskOpening(task)
+ assertTrue(windowDecorByTaskIdSpy.contains(task.taskId))
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
fun testWindowDecor_deviceSupportsDesktopMode_decorCreated() {
- val mockitoSession: StaticMockitoSession = mockitoSession()
- .strictness(Strictness.LENIENT)
- .spyStatic(DesktopModeStatus::class.java)
- .startMocking()
- try {
- // Simulate default enforce device restrictions system property
- whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(true)
+ // Simulate default enforce device restrictions system property
+ whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(true)
- val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true)
- doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
- setUpMockDecorationsForTasks(task)
+ val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true)
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ setUpMockDecorationsForTasks(task)
- onTaskOpening(task)
- assertTrue(windowDecorByTaskIdSpy.contains(task.taskId))
- } finally {
- mockitoSession.finishMocking()
- }
+ onTaskOpening(task)
+ assertTrue(windowDecorByTaskIdSpy.contains(task.taskId))
}
@Test
fun testOnDecorMaximizedOrRestored_togglesTaskSize() {
- val decor = setUpMockDecorationForTask(createTask(windowingMode = WINDOWING_MODE_FREEFORM))
- onTaskOpening(decor.mTaskInfo)
- val maxOrRestoreListener = ArgumentCaptor.forClass(OnTaskActionClickListener::class.java)
- .let { captor ->
- verify(decor).setOnMaximizeOrRestoreClickListener(captor.capture())
- return@let captor.value
- }
+ val maxOrRestoreListenerCaptor = forClass(Function0::class.java)
+ as ArgumentCaptor<Function0<Unit>>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FREEFORM,
+ onMaxOrRestoreListenerCaptor = maxOrRestoreListenerCaptor
+ )
- maxOrRestoreListener.onClick(decor.mTaskInfo.taskId, "test")
+ maxOrRestoreListenerCaptor.value.invoke()
verify(mockDesktopTasksController).toggleDesktopTaskSize(decor.mTaskInfo)
}
@Test
fun testOnDecorMaximizedOrRestored_closesMenus() {
- val decor = setUpMockDecorationForTask(createTask(windowingMode = WINDOWING_MODE_FREEFORM))
- onTaskOpening(decor.mTaskInfo)
- val maxOrRestoreListener = ArgumentCaptor.forClass(OnTaskActionClickListener::class.java)
- .let { captor ->
- verify(decor).setOnMaximizeOrRestoreClickListener(captor.capture())
- return@let captor.value
- }
+ val maxOrRestoreListenerCaptor = forClass(Function0::class.java)
+ as ArgumentCaptor<Function0<Unit>>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FREEFORM,
+ onMaxOrRestoreListenerCaptor = maxOrRestoreListenerCaptor
+ )
- maxOrRestoreListener.onClick(decor.mTaskInfo.taskId, "test")
+ maxOrRestoreListenerCaptor.value.invoke()
verify(decor).closeHandleMenu()
verify(decor).closeMaximizeMenu()
@@ -563,30 +554,28 @@
@Test
fun testOnDecorSnappedLeft_snapResizes() {
- val decor = setUpMockDecorationForTask(createTask(windowingMode = WINDOWING_MODE_FREEFORM))
- onTaskOpening(decor.mTaskInfo)
- val snapLeftListener = ArgumentCaptor.forClass(OnTaskActionClickListener::class.java)
- .let { captor ->
- verify(decor).setOnLeftSnapClickListener(captor.capture())
- return@let captor.value
- }
+ val onLeftSnapClickListenerCaptor = forClass(Function0::class.java)
+ as ArgumentCaptor<Function0<Unit>>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FREEFORM,
+ onLeftSnapClickListenerCaptor = onLeftSnapClickListenerCaptor
+ )
- snapLeftListener.onClick(decor.mTaskInfo.taskId, "test")
+ onLeftSnapClickListenerCaptor.value.invoke()
verify(mockDesktopTasksController).snapToHalfScreen(decor.mTaskInfo, SnapPosition.LEFT)
}
@Test
fun testOnDecorSnappedLeft_closeMenus() {
- val decor = setUpMockDecorationForTask(createTask(windowingMode = WINDOWING_MODE_FREEFORM))
- onTaskOpening(decor.mTaskInfo)
- val snapLeftListener = ArgumentCaptor.forClass(OnTaskActionClickListener::class.java)
- .let { captor ->
- verify(decor).setOnLeftSnapClickListener(captor.capture())
- return@let captor.value
- }
+ val onLeftSnapClickListenerCaptor = forClass(Function0::class.java)
+ as ArgumentCaptor<Function0<Unit>>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FREEFORM,
+ onLeftSnapClickListenerCaptor = onLeftSnapClickListenerCaptor
+ )
- snapLeftListener.onClick(decor.mTaskInfo.taskId, "test")
+ onLeftSnapClickListenerCaptor.value.invoke()
verify(decor).closeHandleMenu()
verify(decor).closeMaximizeMenu()
@@ -594,35 +583,234 @@
@Test
fun testOnDecorSnappedRight_snapResizes() {
- val decor = setUpMockDecorationForTask(createTask(windowingMode = WINDOWING_MODE_FREEFORM))
- onTaskOpening(decor.mTaskInfo)
- val snapLeftListener = ArgumentCaptor.forClass(OnTaskActionClickListener::class.java)
- .let { captor ->
- verify(decor).setOnRightSnapClickListener(captor.capture())
- return@let captor.value
- }
+ val onRightSnapClickListenerCaptor = forClass(Function0::class.java)
+ as ArgumentCaptor<Function0<Unit>>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FREEFORM,
+ onRightSnapClickListenerCaptor = onRightSnapClickListenerCaptor
+ )
- snapLeftListener.onClick(decor.mTaskInfo.taskId, "test")
+ onRightSnapClickListenerCaptor.value.invoke()
verify(mockDesktopTasksController).snapToHalfScreen(decor.mTaskInfo, SnapPosition.RIGHT)
}
@Test
fun testOnDecorSnappedRight_closeMenus() {
- val decor = setUpMockDecorationForTask(createTask(windowingMode = WINDOWING_MODE_FREEFORM))
- onTaskOpening(decor.mTaskInfo)
- val snapLeftListener = ArgumentCaptor.forClass(OnTaskActionClickListener::class.java)
- .let { captor ->
- verify(decor).setOnRightSnapClickListener(captor.capture())
- return@let captor.value
- }
+ val onRightSnapClickListenerCaptor = forClass(Function0::class.java)
+ as ArgumentCaptor<Function0<Unit>>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FREEFORM,
+ onRightSnapClickListenerCaptor = onRightSnapClickListenerCaptor
+ )
- snapLeftListener.onClick(decor.mTaskInfo.taskId, "test")
+ onRightSnapClickListenerCaptor.value.invoke()
verify(decor).closeHandleMenu()
verify(decor).closeMaximizeMenu()
}
+ @Test
+ fun testDecor_onClickToDesktop_movesToDesktopWithSource() {
+ val toDesktopListenerCaptor = forClass(Consumer::class.java)
+ as ArgumentCaptor<Consumer<DesktopModeTransitionSource>>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FULLSCREEN,
+ onToDesktopClickListenerCaptor = toDesktopListenerCaptor
+ )
+
+ toDesktopListenerCaptor.value.accept(DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON)
+
+ verify(mockDesktopTasksController).moveTaskToDesktop(
+ eq(decor.mTaskInfo.taskId),
+ any(),
+ eq(DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON)
+ )
+ }
+
+ @Test
+ fun testDecor_onClickToDesktop_addsCaptionInsets() {
+ val toDesktopListenerCaptor = forClass(Consumer::class.java)
+ as ArgumentCaptor<Consumer<DesktopModeTransitionSource>>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FULLSCREEN,
+ onToDesktopClickListenerCaptor = toDesktopListenerCaptor
+ )
+
+ toDesktopListenerCaptor.value.accept(DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON)
+
+ verify(decor).addCaptionInset(any())
+ }
+
+ @Test
+ fun testDecor_onClickToDesktop_closesHandleMenu() {
+ val toDesktopListenerCaptor = forClass(Consumer::class.java)
+ as ArgumentCaptor<Consumer<DesktopModeTransitionSource>>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FULLSCREEN,
+ onToDesktopClickListenerCaptor = toDesktopListenerCaptor
+ )
+
+ toDesktopListenerCaptor.value.accept(DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON)
+
+ verify(decor).closeHandleMenu()
+ }
+
+ @Test
+ fun testDecor_onClickToFullscreen_closesHandleMenu() {
+ val toFullscreenListenerCaptor = forClass(Function0::class.java)
+ as ArgumentCaptor<Function0<Unit>>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FREEFORM,
+ onToFullscreenClickListenerCaptor = toFullscreenListenerCaptor
+ )
+
+ toFullscreenListenerCaptor.value.invoke()
+
+ verify(decor).closeHandleMenu()
+ }
+
+ @Test
+ fun testDecor_onClickToFullscreen_isFreeform_movesToFullscreen() {
+ val toFullscreenListenerCaptor = forClass(Function0::class.java)
+ as ArgumentCaptor<Function0<Unit>>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FREEFORM,
+ onToFullscreenClickListenerCaptor = toFullscreenListenerCaptor
+ )
+
+ toFullscreenListenerCaptor.value.invoke()
+
+ verify(mockDesktopTasksController).moveToFullscreen(
+ decor.mTaskInfo.taskId,
+ DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON
+ )
+ }
+
+ @Test
+ fun testDecor_onClickToFullscreen_isSplit_movesToFullscreen() {
+ val toFullscreenListenerCaptor = forClass(Function0::class.java)
+ as ArgumentCaptor<Function0<Unit>>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_MULTI_WINDOW,
+ onToFullscreenClickListenerCaptor = toFullscreenListenerCaptor
+ )
+
+ toFullscreenListenerCaptor.value.invoke()
+
+ verify(mockSplitScreenController).moveTaskToFullscreen(
+ decor.mTaskInfo.taskId,
+ SplitScreenController.EXIT_REASON_DESKTOP_MODE
+ )
+ }
+
+ @Test
+ fun testDecor_onClickToSplitScreen_closesHandleMenu() {
+ val toSplitScreenListenerCaptor = forClass(Function0::class.java)
+ as ArgumentCaptor<Function0<Unit>>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_MULTI_WINDOW,
+ onToSplitScreenClickListenerCaptor = toSplitScreenListenerCaptor
+ )
+
+ toSplitScreenListenerCaptor.value.invoke()
+
+ verify(decor).closeHandleMenu()
+ }
+
+ @Test
+ fun testDecor_onClickToSplitScreen_requestsSplit() {
+ val toSplitScreenListenerCaptor = forClass(Function0::class.java)
+ as ArgumentCaptor<Function0<Unit>>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_MULTI_WINDOW,
+ onToSplitScreenClickListenerCaptor = toSplitScreenListenerCaptor
+ )
+
+ toSplitScreenListenerCaptor.value.invoke()
+
+ verify(mockDesktopTasksController).requestSplit(decor.mTaskInfo, leftOrTop = false)
+ }
+
+ @Test
+ fun testDecor_onClickToSplitScreen_disposesStatusBarInputLayer() {
+ val toSplitScreenListenerCaptor = forClass(Function0::class.java)
+ as ArgumentCaptor<Function0<Unit>>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_MULTI_WINDOW,
+ onToSplitScreenClickListenerCaptor = toSplitScreenListenerCaptor
+ )
+
+ toSplitScreenListenerCaptor.value.invoke()
+
+ verify(decor).disposeStatusBarInputLayer()
+ }
+
+ @Test
+ fun testDecor_onClickToOpenBrowser_closeMenus() {
+ val openInBrowserListenerCaptor = forClass(Consumer::class.java)
+ as ArgumentCaptor<Consumer<Uri>>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FULLSCREEN,
+ onOpenInBrowserClickListener = openInBrowserListenerCaptor
+ )
+
+ openInBrowserListenerCaptor.value.accept(Uri.EMPTY)
+
+ verify(decor).closeHandleMenu()
+ verify(decor).closeMaximizeMenu()
+ }
+
+ @Test
+ fun testDecor_onClickToOpenBrowser_opensBrowser() {
+ doNothing().whenever(spyContext).startActivity(any())
+ val uri = Uri.parse("https://www.google.com")
+ val openInBrowserListenerCaptor = forClass(Consumer::class.java)
+ as ArgumentCaptor<Consumer<Uri>>
+ createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FULLSCREEN,
+ onOpenInBrowserClickListener = openInBrowserListenerCaptor
+ )
+
+ openInBrowserListenerCaptor.value.accept(uri)
+
+ verify(spyContext).startActivity(argThat { intent ->
+ intent.data == uri
+ && ((intent.flags and Intent.FLAG_ACTIVITY_NEW_TASK) != 0)
+ && intent.categories.contains(Intent.CATEGORY_LAUNCHER)
+ && intent.action == Intent.ACTION_MAIN
+ })
+ }
+
+ private fun createOpenTaskDecoration(
+ @WindowingMode windowingMode: Int,
+ onMaxOrRestoreListenerCaptor: ArgumentCaptor<Function0<Unit>> =
+ forClass(Function0::class.java) as ArgumentCaptor<Function0<Unit>>,
+ onLeftSnapClickListenerCaptor: ArgumentCaptor<Function0<Unit>> =
+ forClass(Function0::class.java) as ArgumentCaptor<Function0<Unit>>,
+ onRightSnapClickListenerCaptor: ArgumentCaptor<Function0<Unit>> =
+ forClass(Function0::class.java) as ArgumentCaptor<Function0<Unit>>,
+ onToDesktopClickListenerCaptor: ArgumentCaptor<Consumer<DesktopModeTransitionSource>> =
+ forClass(Consumer::class.java) as ArgumentCaptor<Consumer<DesktopModeTransitionSource>>,
+ onToFullscreenClickListenerCaptor: ArgumentCaptor<Function0<Unit>> =
+ forClass(Function0::class.java) as ArgumentCaptor<Function0<Unit>>,
+ onToSplitScreenClickListenerCaptor: ArgumentCaptor<Function0<Unit>> =
+ forClass(Function0::class.java) as ArgumentCaptor<Function0<Unit>>,
+ onOpenInBrowserClickListener: ArgumentCaptor<Consumer<Uri>> =
+ forClass(Consumer::class.java) as ArgumentCaptor<Consumer<Uri>>,
+ ): DesktopModeWindowDecoration {
+ val decor = setUpMockDecorationForTask(createTask(windowingMode = windowingMode))
+ onTaskOpening(decor.mTaskInfo)
+ verify(decor).setOnMaximizeOrRestoreClickListener(onMaxOrRestoreListenerCaptor.capture())
+ verify(decor).setOnLeftSnapClickListener(onLeftSnapClickListenerCaptor.capture())
+ verify(decor).setOnRightSnapClickListener(onRightSnapClickListenerCaptor.capture())
+ verify(decor).setOnToDesktopClickListener(onToDesktopClickListenerCaptor.capture())
+ verify(decor).setOnToFullscreenClickListener(onToFullscreenClickListenerCaptor.capture())
+ verify(decor).setOnToSplitScreenClickListener(onToSplitScreenClickListenerCaptor.capture())
+ verify(decor).setOpenInBrowserClickListener(onOpenInBrowserClickListener.capture())
+ return decor
+ }
+
private fun onTaskOpening(task: RunningTaskInfo, leash: SurfaceControl = SurfaceControl()) {
desktopModeWindowDecorViewModel.onTaskOpening(
task,
@@ -643,7 +831,7 @@
private fun createTask(
displayId: Int = DEFAULT_DISPLAY,
- @WindowConfiguration.WindowingMode windowingMode: Int,
+ @WindowingMode windowingMode: Int,
activityType: Int = ACTIVITY_TYPE_STANDARD,
focused: Boolean = true,
activityInfo: ActivityInfo = ActivityInfo(),
@@ -668,6 +856,10 @@
).thenReturn(decoration)
decoration.mTaskInfo = task
whenever(decoration.isFocused).thenReturn(task.isFocused)
+ if (task.windowingMode == WINDOWING_MODE_MULTI_WINDOW) {
+ whenever(mockSplitScreenController.isTaskInSplitScreen(task.taskId))
+ .thenReturn(true)
+ }
return decoration
}
@@ -688,7 +880,7 @@
)
}
- private fun RunningTaskInfo.setWindowingMode(@WindowConfiguration.WindowingMode mode: Int) {
+ private fun RunningTaskInfo.setWindowingMode(@WindowingMode mode: Int) {
configuration.windowConfiguration.windowingMode = mode
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index e60eb77..596adfb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -95,9 +95,9 @@
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams;
-import com.android.wm.shell.windowdecor.common.OnTaskActionClickListener;
import kotlin.Unit;
+import kotlin.jvm.functions.Function0;
import kotlin.jvm.functions.Function1;
import org.junit.After;
@@ -111,6 +111,7 @@
import org.mockito.Mock;
import org.mockito.quality.Strictness;
+import java.util.function.Consumer;
import java.util.function.Supplier;
/**
@@ -168,7 +169,7 @@
@Mock
private Handler mMockHandler;
@Mock
- private DesktopModeWindowDecoration.OpenInBrowserClickListener mMockOpenInBrowserClickListener;
+ private Consumer<Uri> mMockOpenInBrowserClickListener;
@Mock
private AppToWebGenericLinksParser mMockGenericLinksParser;
@Mock
@@ -219,9 +220,9 @@
final Display defaultDisplay = mock(Display.class);
doReturn(defaultDisplay).when(mMockDisplayController).getDisplay(Display.DEFAULT_DISPLAY);
doReturn(mInsetsState).when(mMockDisplayController).getInsetsState(anyInt());
- doReturn(mMockHandleMenu).when(mMockHandleMenuFactory).create(any(), anyInt(), any(), any(),
- any(), any(), any(), any(), anyBoolean(), anyBoolean(), any(), anyInt(), anyInt(),
- anyInt());
+ when(mMockHandleMenuFactory.create(any(), anyInt(), any(), any(),
+ any(), anyBoolean(), anyBoolean(), any(), anyInt(), anyInt(), anyInt()))
+ .thenReturn(mMockHandleMenu);
}
@After
@@ -742,9 +743,21 @@
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
final DesktopModeWindowDecoration decor = createWindowDecoration(
taskInfo, TEST_URI1 /* captured link */, null /* generic link */);
+ final ArgumentCaptor<Function1<Uri, Unit>> openInBrowserCaptor =
+ ArgumentCaptor.forClass(Function1.class);
+
// Simulate menu opening and clicking open in browser button
decor.createHandleMenu(mMockSplitScreenController);
- decor.onOpenInBrowserClick();
+ verify(mMockHandleMenu).show(
+ any(),
+ any(),
+ any(),
+ any(),
+ openInBrowserCaptor.capture(),
+ any(),
+ any()
+ );
+ openInBrowserCaptor.getValue().invoke(TEST_URI1);
// Verify handle menu's browser link not set to captured link since link not valid after
// open in browser clicked
@@ -758,10 +771,22 @@
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
final DesktopModeWindowDecoration decor = createWindowDecoration(
taskInfo, TEST_URI1 /* captured link */, null /* generic link */);
+ final ArgumentCaptor<Function1<Uri, Unit>> openInBrowserCaptor =
+ ArgumentCaptor.forClass(Function1.class);
decor.createHandleMenu(mMockSplitScreenController);
- decor.onOpenInBrowserClick();
+ verify(mMockHandleMenu).show(
+ any(),
+ any(),
+ any(),
+ any(),
+ openInBrowserCaptor.capture(),
+ any(),
+ any()
+ );
- verify(mMockOpenInBrowserClickListener).onClick(any(), any());
+ openInBrowserCaptor.getValue().invoke(TEST_URI1);
+
+ verify(mMockOpenInBrowserClickListener).accept(TEST_URI1);
}
@Test
@@ -776,13 +801,37 @@
verifyHandleMenuCreated(TEST_URI2);
}
+ @Test
+ public void handleMenu_onCloseMenuClick_closesMenu() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ final DesktopModeWindowDecoration decoration = createWindowDecoration(taskInfo,
+ true /* relayout */);
+ final ArgumentCaptor<Function0<Unit>> closeClickListener =
+ ArgumentCaptor.forClass(Function0.class);
+ decoration.createHandleMenu(mMockSplitScreenController);
+ verify(mMockHandleMenu).show(
+ any(),
+ any(),
+ any(),
+ any(),
+ any(),
+ closeClickListener.capture(),
+ any()
+ );
+
+ closeClickListener.getValue().invoke();
+
+ verify(mMockHandleMenu).close();
+ assertFalse(decoration.isHandleMenuActive());
+ }
+
private void verifyHandleMenuCreated(@Nullable Uri uri) {
- verify(mMockHandleMenuFactory).create(any(), anyInt(), any(), any(), any(), any(), any(),
+ verify(mMockHandleMenuFactory).create(any(), anyInt(), any(), any(),
any(), anyBoolean(), anyBoolean(), eq(uri), anyInt(), anyInt(), anyInt());
}
private void createMaximizeMenu(DesktopModeWindowDecoration decoration, MaximizeMenu menu) {
- final OnTaskActionClickListener l = (taskId, tag) -> {};
+ final Function0<Unit> l = () -> Unit.INSTANCE;
decoration.setOnMaximizeOrRestoreClickListener(l);
decoration.setOnLeftSnapClickListener(l);
decoration.setOnRightSnapClickListener(l);
@@ -814,20 +863,31 @@
taskInfo.capturedLinkTimestamp = System.currentTimeMillis();
final String genericLinkString = genericLink == null ? null : genericLink.toString();
doReturn(genericLinkString).when(mMockGenericLinksParser).getGenericLink(any());
- final DesktopModeWindowDecoration decor = createWindowDecoration(taskInfo);
// Relayout to set captured link
- decor.relayout(taskInfo);
- return decor;
+ return createWindowDecoration(taskInfo, new FakeMaximizeMenuFactory(), true /* relayout */);
}
private DesktopModeWindowDecoration createWindowDecoration(
ActivityManager.RunningTaskInfo taskInfo) {
- return createWindowDecoration(taskInfo, new FakeMaximizeMenuFactory());
+ return createWindowDecoration(taskInfo, new FakeMaximizeMenuFactory(),
+ false /* relayout */);
+ }
+
+ private DesktopModeWindowDecoration createWindowDecoration(
+ ActivityManager.RunningTaskInfo taskInfo, boolean relayout) {
+ return createWindowDecoration(taskInfo, new FakeMaximizeMenuFactory(), relayout);
}
private DesktopModeWindowDecoration createWindowDecoration(
ActivityManager.RunningTaskInfo taskInfo,
MaximizeMenuFactory maximizeMenuFactory) {
+ return createWindowDecoration(taskInfo, maximizeMenuFactory, false /* relayout */);
+ }
+
+ private DesktopModeWindowDecoration createWindowDecoration(
+ ActivityManager.RunningTaskInfo taskInfo,
+ MaximizeMenuFactory maximizeMenuFactory,
+ boolean relayout) {
final DesktopModeWindowDecoration windowDecor = new DesktopModeWindowDecoration(mContext,
mContext, mMockDisplayController, mMockSplitScreenController,
mMockShellTaskOrganizer, taskInfo, mMockSurfaceControl, mMockHandler, mBgExecutor,
@@ -841,6 +901,9 @@
windowDecor.setExclusionRegionListener(mMockExclusionRegionListener);
windowDecor.setOpenInBrowserClickListener(mMockOpenInBrowserClickListener);
windowDecor.mDecorWindowContext = mContext;
+ if (relayout) {
+ windowDecor.relayout(taskInfo);
+ }
return windowDecor;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
index cca83dd..a1c7947 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
@@ -57,6 +57,7 @@
import org.mockito.Mock
import org.mockito.Mockito.mock
import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
/**
@@ -109,6 +110,9 @@
whenever(mockDesktopWindowDecoration.addWindow(
anyInt(), any(), any(), any(), anyInt(), anyInt(), anyInt(), anyInt())
).thenReturn(mockAdditionalViewHostViewContainer)
+ whenever(mockDesktopWindowDecoration.addWindow(
+ any<View>(), any(), any(), any(), anyInt(), anyInt(), anyInt(), anyInt())
+ ).thenReturn(mockAdditionalViewHostViewContainer)
whenever(mockAdditionalViewHostViewContainer.view).thenReturn(menuView)
whenever(displayController.getDisplayLayout(anyInt())).thenReturn(displayLayout)
whenever(displayLayout.width()).thenReturn(DISPLAY_BOUNDS.width())
@@ -226,13 +230,13 @@
}
else -> error("Invalid windowing mode")
}
- val handleMenu = HandleMenu(mockDesktopWindowDecoration, layoutId,
- onClickListener, onTouchListener, appIcon, appName, displayController,
+ val handleMenu = HandleMenu(mockDesktopWindowDecoration, layoutId, appIcon, appName,
splitScreenController, shouldShowWindowingPill = true,
shouldShowNewWindowButton = true,
- openInBrowserLink = null, captionWidth = HANDLE_WIDTH, captionHeight = 50,
- captionX = captionX)
- handleMenu.show()
+ null /* openInBrowserLink */, captionWidth = HANDLE_WIDTH, captionHeight = 50,
+ captionX = captionX
+ )
+ handleMenu.show(mock(), mock(), mock(), mock(), mock(), mock(), mock())
return handleMenu
}
diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig
index 83a986b1..754d9423 100644
--- a/packages/SettingsLib/aconfig/settingslib.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib.aconfig
@@ -9,16 +9,6 @@
}
flag {
- name: "enable_cached_bluetooth_device_dedup"
- namespace: "bluetooth"
- description: "Enable dedup in CachedBluetoothDevice"
- bug: "319197962"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "bluetooth_qs_tile_dialog_auto_on_toggle"
namespace: "bluetooth"
description: "Displays the auto on toggle in the bluetooth QS tile dialog"
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
index e926b16..0dc772a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
@@ -16,8 +16,6 @@
package com.android.settingslib.bluetooth;
-import static com.android.settingslib.flags.Flags.enableCachedBluetoothDeviceDedup;
-
import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothCsipSetCoordinator;
@@ -403,7 +401,7 @@
cachedDevice = mDeviceManager.addDevice(device);
}
- if (enableCachedBluetoothDeviceDedup() && bondState == BluetoothDevice.BOND_BONDED) {
+ if (bondState == BluetoothDevice.BOND_BONDED) {
mDeviceManager.removeDuplicateInstanceForIdentityAddress(device);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/satellite/SatelliteDialogUtils.kt b/packages/SettingsLib/src/com/android/settingslib/satellite/SatelliteDialogUtils.kt
index d69c87b..4b6bcf9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/satellite/SatelliteDialogUtils.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/satellite/SatelliteDialogUtils.kt
@@ -21,6 +21,7 @@
import android.content.Intent
import android.os.OutcomeReceiver
import android.telephony.satellite.SatelliteManager
+import android.telephony.satellite.SatelliteModemStateCallback
import android.util.Log
import android.view.WindowManager
import androidx.lifecycle.LifecycleOwner
@@ -31,12 +32,19 @@
import kotlinx.coroutines.Dispatchers.Default
import kotlinx.coroutines.Job
import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
import java.util.concurrent.ExecutionException
import java.util.concurrent.TimeoutException
import kotlin.coroutines.resume
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flowOn
/** A util for Satellite dialog */
object SatelliteDialogUtils {
@@ -70,7 +78,7 @@
coroutineScope.launch {
var isSatelliteModeOn = false
try {
- isSatelliteModeOn = requestIsEnabled(context)
+ isSatelliteModeOn = requestIsSessionStarted(context)
} catch (e: InterruptedException) {
Log.w(TAG, "Error to get satellite status : $e")
} catch (e: ExecutionException) {
@@ -118,19 +126,107 @@
}
suspendCancellableCoroutine {continuation ->
- satelliteManager?.requestIsEnabled(Default.asExecutor(),
- object : OutcomeReceiver<Boolean, SatelliteManager.SatelliteException> {
- override fun onResult(result: Boolean) {
- Log.i(TAG, "Satellite modem enabled status: $result")
- continuation.resume(result)
- }
+ try {
+ satelliteManager?.requestIsEnabled(Default.asExecutor(),
+ object : OutcomeReceiver<Boolean, SatelliteManager.SatelliteException> {
+ override fun onResult(result: Boolean) {
+ Log.i(TAG, "Satellite modem enabled status: $result")
+ continuation.resume(result)
+ }
- override fun onError(error: SatelliteManager.SatelliteException) {
- super.onError(error)
- Log.w(TAG, "Can't get satellite modem enabled status", error)
- continuation.resume(false)
- }
- })
+ override fun onError(error: SatelliteManager.SatelliteException) {
+ super.onError(error)
+ Log.w(TAG, "Can't get satellite modem enabled status", error)
+ continuation.resume(false)
+ }
+ })
+ } catch (e: IllegalStateException) {
+ Log.w(TAG, "IllegalStateException: $e")
+ continuation.resume(false)
+ }
+ }
+ }
+
+ private suspend fun requestIsSessionStarted(
+ context: Context
+ ): Boolean = withContext(Default) {
+ val satelliteManager: SatelliteManager? =
+ context.getSystemService(SatelliteManager::class.java)
+ if (satelliteManager == null) {
+ Log.w(TAG, "SatelliteManager is null")
+ return@withContext false
+ }
+
+ getIsSessionStartedFlow(context).conflate().first()
+ }
+
+ /**
+ * Provides a Flow that emits the session state of the satellite modem. Updates are triggered
+ * when the modem state changes.
+ *
+ * @param defaultDispatcher The CoroutineDispatcher to use (Defaults to `Dispatchers.Default`).
+ * @return A Flow emitting `true` when the session is started and `false` otherwise.
+ */
+ private fun getIsSessionStartedFlow(
+ context: Context
+ ): Flow<Boolean> {
+ val satelliteManager: SatelliteManager? =
+ context.getSystemService(SatelliteManager::class.java)
+ if (satelliteManager == null) {
+ Log.w(TAG, "SatelliteManager is null")
+ return flowOf(false)
+ }
+
+ return callbackFlow {
+ val callback = SatelliteModemStateCallback { state ->
+ val isSessionStarted = isSatelliteSessionStarted(state)
+ Log.i(TAG, "Satellite modem state changed: state=$state"
+ + ", isSessionStarted=$isSessionStarted")
+ trySend(isSessionStarted)
+ }
+
+ var registerResult = SatelliteManager.SATELLITE_MODEM_STATE_UNKNOWN
+ try {
+ registerResult = satelliteManager.registerForModemStateChanged(
+ Default.asExecutor(),
+ callback
+ )
+ } catch (e: IllegalStateException) {
+ Log.w(TAG, "IllegalStateException: $e")
+ }
+
+
+ if (registerResult != SatelliteManager.SATELLITE_RESULT_SUCCESS) {
+ // If the registration failed (e.g., device doesn't support satellite),
+ // SatelliteManager will not emit the current state by callback.
+ // We send `false` value by ourself to make sure the flow has initial value.
+ Log.w(TAG, "Failed to register for satellite modem state change: $registerResult")
+ trySend(false)
+ }
+
+ awaitClose {
+ try {
+ satelliteManager.unregisterForModemStateChanged(callback)
+ } catch (e: IllegalStateException) {
+ Log.w(TAG, "IllegalStateException: $e")
+ }
+ }
+ }.flowOn(Default)
+ }
+
+
+ /**
+ * Check if the modem is in a satellite session.
+ *
+ * @param state The SatelliteModemState provided by the SatelliteManager.
+ * @return `true` if the modem is in a satellite session, `false` otherwise.
+ */
+ fun isSatelliteSessionStarted(@SatelliteManager.SatelliteModemState state: Int): Boolean {
+ return when (state) {
+ SatelliteManager.SATELLITE_MODEM_STATE_OFF,
+ SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE,
+ SatelliteManager.SATELLITE_MODEM_STATE_UNKNOWN -> false
+ else -> true
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/satellite/SatelliteDialogUtilsTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/satellite/SatelliteDialogUtilsTest.kt
index aeda1ed6..2078b36 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/satellite/SatelliteDialogUtilsTest.kt
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/satellite/SatelliteDialogUtilsTest.kt
@@ -17,11 +17,12 @@
package com.android.settingslib.satellite
import android.content.Context
-import android.content.Intent
-import android.os.OutcomeReceiver
import android.platform.test.annotations.RequiresFlagsEnabled
import android.telephony.satellite.SatelliteManager
-import android.telephony.satellite.SatelliteManager.SatelliteException
+import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_ENABLING_SATELLITE
+import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_OFF
+import android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_MODEM_ERROR
+import android.telephony.satellite.SatelliteModemStateCallback
import android.util.AndroidRuntimeException
import androidx.test.core.app.ApplicationProvider
import com.android.internal.telephony.flags.Flags
@@ -67,26 +68,19 @@
@Test
@RequiresFlagsEnabled(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
fun mayStartSatelliteWarningDialog_satelliteIsOn_showWarningDialog() = runBlocking {
- `when`(
- satelliteManager.requestIsEnabled(
- any(), any<OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>()
- )
- )
+ `when`(satelliteManager.registerForModemStateChanged(any(), any()))
.thenAnswer { invocation ->
- val receiver = invocation
- .getArgument<
- OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>(
- 1
- )
- receiver.onResult(true)
+ val callback = invocation
+ .getArgument<SatelliteModemStateCallback>(1)
+ callback.onSatelliteModemStateChanged(SATELLITE_MODEM_STATE_ENABLING_SATELLITE)
null
}
try {
SatelliteDialogUtils.mayStartSatelliteWarningDialog(
context, coroutineScope, TYPE_IS_WIFI, allowClick = {
- assertTrue(it)
- })
+ assertTrue(it)
+ })
} catch (e: AndroidRuntimeException) {
// Catch exception of starting activity .
}
@@ -95,68 +89,61 @@
@Test
@RequiresFlagsEnabled(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
fun mayStartSatelliteWarningDialog_satelliteIsOff_notShowWarningDialog() = runBlocking {
- `when`(
- satelliteManager.requestIsEnabled(
- any(), any<OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>()
- )
- )
+ `when`(satelliteManager.registerForModemStateChanged(any(), any()))
.thenAnswer { invocation ->
- val receiver = invocation
- .getArgument<
- OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>(
- 1
- )
- receiver.onResult(false)
+ val callback = invocation
+ .getArgument<SatelliteModemStateCallback>(1)
+ callback.onSatelliteModemStateChanged(SATELLITE_MODEM_STATE_OFF)
null
}
SatelliteDialogUtils.mayStartSatelliteWarningDialog(
- context, coroutineScope, TYPE_IS_WIFI, allowClick = {
- assertFalse(it)
- })
+ context, coroutineScope, TYPE_IS_WIFI, allowClick = {
+ assertFalse(it)
+ })
- verify(context, Times(0)).startActivity(any<Intent>())
+ verify(context, Times(0)).startActivity(any())
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
fun mayStartSatelliteWarningDialog_noSatelliteManager_notShowWarningDialog() = runBlocking {
- `when`(context.getSystemService(SatelliteManager::class.java))
- .thenReturn(null)
+ `when`(context.getSystemService(SatelliteManager::class.java)).thenReturn(null)
SatelliteDialogUtils.mayStartSatelliteWarningDialog(
- context, coroutineScope, TYPE_IS_WIFI, allowClick = {
- assertFalse(it)
- })
+ context, coroutineScope, TYPE_IS_WIFI, allowClick = {
+ assertFalse(it)
+ })
- verify(context, Times(0)).startActivity(any<Intent>())
+ verify(context, Times(0)).startActivity(any())
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
fun mayStartSatelliteWarningDialog_satelliteErrorResult_notShowWarningDialog() = runBlocking {
- `when`(
- satelliteManager.requestIsEnabled(
- any(), any<OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>()
- )
- )
- .thenAnswer { invocation ->
- val receiver = invocation
- .getArgument<
- OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>(
- 1
- )
- receiver.onError(SatelliteException(SatelliteManager.SATELLITE_RESULT_ERROR))
- null
- }
-
+ `when`(satelliteManager.registerForModemStateChanged(any(), any()))
+ .thenReturn(SATELLITE_RESULT_MODEM_ERROR)
SatelliteDialogUtils.mayStartSatelliteWarningDialog(
- context, coroutineScope, TYPE_IS_WIFI, allowClick = {
- assertFalse(it)
- })
+ context, coroutineScope, TYPE_IS_WIFI, allowClick = {
+ assertFalse(it)
+ })
- verify(context, Times(0)).startActivity(any<Intent>())
+ verify(context, Times(0)).startActivity(any())
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ fun mayStartSatelliteWarningDialog_phoneCrash_notShowWarningDialog() = runBlocking {
+ `when`(satelliteManager.registerForModemStateChanged(any(), any()))
+ .thenThrow(IllegalStateException("Telephony is null!!!"))
+
+ SatelliteDialogUtils.mayStartSatelliteWarningDialog(
+ context, coroutineScope, TYPE_IS_WIFI, allowClick = {
+ assertFalse(it)
+ })
+
+ verify(context, Times(0)).startActivity(any())
}
}
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index ee7033e..415f78a 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -65,7 +65,6 @@
import com.android.server.LocalServices;
import com.android.server.PackageWatchdog;
import com.android.server.pm.UserManagerInternal;
-import com.android.server.pm.UserManagerService;
import com.android.server.usage.AppStandbyInternal;
import com.android.server.wm.WindowProcessController;
@@ -1027,7 +1026,8 @@
isBackground &= (userId != profileId);
}
int visibleUserId = getVisibleUserId(userId);
- boolean isVisibleUser = isVisibleBackgroundUser(visibleUserId);
+ boolean isVisibleUser = LocalServices.getService(UserManagerInternal.class)
+ .isVisibleBackgroundFullUser(visibleUserId);
boolean showBackground = Settings.Secure.getIntForUser(mContext.getContentResolver(),
Settings.Secure.ANR_SHOW_BACKGROUND, 0, visibleUserId) != 0;
if (isBackground && !showBackground && !isVisibleUser) {
@@ -1050,7 +1050,7 @@
mContext.getContentResolver(),
Settings.Secure.SHOW_FIRST_CRASH_DIALOG_DEV_OPTION,
0,
- mService.mUserController.getCurrentUserId()) != 0;
+ visibleUserId) != 0;
final String packageName = proc.info.packageName;
final boolean crashSilenced = mAppsNotReportingCrashes != null
&& mAppsNotReportingCrashes.contains(proc.info.packageName);
@@ -1183,26 +1183,6 @@
}
/**
- * Checks if the given user is a visible background user, which is a full, background user
- * assigned to secondary displays on the devices that have
- * {@link UserManager#isVisibleBackgroundUsersEnabled()
- * config_multiuserVisibleBackgroundUsers enabled} (for example, passenger users on
- * automotive builds, using the display associated with their seats).
- *
- * @see UserManager#isUserVisible()
- */
- private boolean isVisibleBackgroundUser(int userId) {
- if (!UserManager.isVisibleBackgroundUsersEnabled()) {
- return false;
- }
- boolean isForeground = mService.mUserController.getCurrentUserId() == userId;
- boolean isProfile = UserManagerService.getInstance().isProfile(userId);
- boolean isVisible = LocalServices.getService(UserManagerInternal.class)
- .isUserVisible(userId);
- return isVisible && !isForeground && !isProfile;
- }
-
- /**
* Information about a process that is currently marked as bad.
*/
static final class BadProcessInfo {
diff --git a/services/core/java/com/android/server/biometrics/biometrics.aconfig b/services/core/java/com/android/server/biometrics/biometrics.aconfig
index 15c8850..b2e95aa 100644
--- a/services/core/java/com/android/server/biometrics/biometrics.aconfig
+++ b/services/core/java/com/android/server/biometrics/biometrics.aconfig
@@ -16,8 +16,11 @@
}
flag {
- name: "notify_fingerprint_loe"
+ name: "notify_fingerprints_loe"
namespace: "biometrics_framework"
description: "This flag controls whether a notification should be sent to notify user when loss of enrollment happens"
bug: "351036558"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
index 3b6aeef..77e27ba 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
@@ -108,7 +108,7 @@
}
if (mBiometricUtils.hasValidBiometricUserState(getContext(), getTargetUserId())
- && Flags.notifyFingerprintLoe()) {
+ && Flags.notifyFingerprintsLoe()) {
handleInvalidBiometricState();
}
}
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index 0e7ce2e..14b0fc8 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -497,6 +497,17 @@
public abstract boolean isUserVisible(@UserIdInt int userId, int displayId);
/**
+ * Checks if the given user is a visible background full user, which is a full background user
+ * assigned to secondary displays on the devices that have
+ * {@link UserManager#isVisibleBackgroundUsersEnabled()
+ * config_multiuserVisibleBackgroundUsers enabled} (for example, passenger users on
+ * automotive builds, using the display associated with their seats).
+ *
+ * @see UserManager#isUserVisible()
+ */
+ public abstract boolean isVisibleBackgroundFullUser(@UserIdInt int userId);
+
+ /**
* Returns the main display id assigned to the user, or {@code Display.INVALID_DISPLAY} if the
* user is not assigned to any main display.
*
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index dde9943..c902fb2 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -7927,6 +7927,17 @@
}
@Override
+ public boolean isVisibleBackgroundFullUser(@UserIdInt int userId) {
+ if (!UserManager.isVisibleBackgroundUsersEnabled()) {
+ return false;
+ }
+ boolean isForeground = userId == getCurrentUserId();
+ boolean isProfile = isProfileUnchecked(userId);
+ boolean isVisible = isUserVisible(userId);
+ return isVisible && !isForeground && !isProfile;
+ }
+
+ @Override
public int getMainDisplayAssignedToUser(@UserIdInt int userId) {
return mUserVisibilityMediator.getMainDisplayAssignedToUser(userId);
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 1c14c5d..9f3bbd1 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -7175,25 +7175,6 @@
}
/**
- * Checks if the given user is a visible background user, which is a full, background user
- * assigned to secondary displays on the devices that have
- * {@link UserManager#isVisibleBackgroundUsersEnabled()
- * config_multiuserVisibleBackgroundUsers enabled} (for example, passenger users on
- * automotive builds, using the display associated with their seats).
- *
- * @see UserManager#isUserVisible()
- */
- private boolean isVisibleBackgroundUser(int userId) {
- if (!UserManager.isVisibleBackgroundUsersEnabled()) {
- return false;
- }
- boolean isForeground = getCurrentUserId() == userId;
- boolean isProfile = getUserManager().isProfile(userId);
- boolean isVisible = mWindowManager.mUmInternal.isUserVisible(userId);
- return isVisible && !isForeground && !isProfile;
- }
-
- /**
* In a car environment, {@link ActivityTaskManagerService#mShowDialogs} is always set to
* {@code false} from {@link ActivityTaskManagerService#updateShouldShowDialogsLocked}
* because its UI mode is {@link Configuration#UI_MODE_TYPE_CAR}. Thus, error dialogs are
@@ -7208,7 +7189,7 @@
* @see ActivityTaskManagerService#updateShouldShowDialogsLocked
*/
private boolean shouldShowDialogsForVisibleBackgroundUserLocked(int userId) {
- if (!isVisibleBackgroundUser(userId)) {
+ if (!mWindowManager.mUmInternal.isVisibleBackgroundFullUser(userId)) {
return false;
}
final int displayId = mWindowManager.mUmInternal.getMainDisplayAssignedToUser(userId);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java
index a34e796..242880c 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java
@@ -178,7 +178,7 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_NOTIFY_FINGERPRINT_LOE)
+ @RequiresFlagsEnabled(Flags.FLAG_NOTIFY_FINGERPRINTS_LOE)
public void invalidBiometricUserState() throws Exception {
mClient = createClient();
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index e57a95e..c6959ae 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -51,6 +51,7 @@
import android.telephony.ims.RcsUceAdapter;
import android.telephony.ims.feature.MmTelFeature;
import android.telephony.ims.feature.RcsFeature;
+import android.telephony.satellite.SatelliteManager;
import com.android.internal.telephony.ICarrierConfigLoader;
import com.android.internal.telephony.flags.Flags;
@@ -10022,6 +10023,22 @@
"carrier_roaming_ntn_connect_type_int";
/**
+ * Indicates carrier roaming non-terrestrial network emergency call handover type that the
+ * device will use to perform a handover between ESOS or T911.
+ * If this key is set to {@link SatelliteManager#EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_SOS}
+ * then the handover will be made to ESOS. If this key is set to
+ * {@link SatelliteManager#EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911} then the handover
+ * will be made to T911.
+ *
+ * The default value is {@link SatelliteManager#EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911}.
+ *
+ */
+ @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+ public static final String
+ KEY_CARRIER_ROAMING_NTN_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_INT =
+ "carrier_roaming_ntn_emergency_call_to_satellite_handover_type_int";
+
+ /**
* The carrier roaming non-terrestrial network hysteresis time in seconds.
*
* If the device supports P2P satellite messaging which is defined by
@@ -11209,6 +11226,8 @@
(int) TimeUnit.SECONDS.toMillis(30));
sDefaults.putBoolean(KEY_SATELLITE_ESOS_SUPPORTED_BOOL, false);
sDefaults.putInt(KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT, 0);
+ sDefaults.putInt(KEY_CARRIER_ROAMING_NTN_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_INT,
+ SatelliteManager.EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911);
sDefaults.putInt(KEY_CARRIER_SUPPORTED_SATELLITE_NOTIFICATION_HYSTERESIS_SEC_INT, 180);
sDefaults.putInt(KEY_SATELLITE_SCREEN_OFF_INACTIVITY_TIMEOUT_SEC_INT, 30);
sDefaults.putString(KEY_DEFAULT_PREFERRED_APN_NAME_STRING, "");