diff options
66 files changed, 1304 insertions, 810 deletions
diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 47e12aef9926..5413c6606bcb 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -3694,9 +3694,11 @@ package android.companion.virtual.sensor { method public int getMinDelay(); method @NonNull public String getName(); method public float getPower(); + method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") public int getReportingMode(); method public float getResolution(); method public int getType(); method @Nullable public String getVendor(); + method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") public boolean isWakeUpSensor(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.sensor.VirtualSensorConfig> CREATOR; } @@ -3710,8 +3712,10 @@ package android.companion.virtual.sensor { method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setMaximumRange(float); method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setMinDelay(int); method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setPower(float); + method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setReportingMode(int); method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setResolution(float); method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setVendor(@Nullable String); + method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setWakeUpSensor(boolean); } public interface VirtualSensorDirectChannelCallback { diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig index c3c3f0ef32e1..b4c36e1bc513 100644 --- a/core/java/android/companion/virtual/flags/flags.aconfig +++ b/core/java/android/companion/virtual/flags/flags.aconfig @@ -103,3 +103,10 @@ flag { description: "Expose multiple surface for the virtual camera owner for different stream resolution" bug: "341083465" } + +flag { + namespace: "virtual_devices" + name: "device_aware_display_power" + description: "Device awareness in power and display APIs" + bug: "285020111" +} diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java index 21ad914bbc29..82f183fd1d62 100644 --- a/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java +++ b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java @@ -17,12 +17,14 @@ package android.companion.virtual.sensor; +import android.annotation.FlaggedApi; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.TestApi; +import android.companion.virtualdevice.flags.Flags; import android.hardware.Sensor; import android.hardware.SensorDirectChannel; import android.os.Parcel; @@ -42,6 +44,13 @@ import java.util.Objects; public final class VirtualSensorConfig implements Parcelable { private static final String TAG = "VirtualSensorConfig"; + // Defined in sensors.h + private static final int FLAG_WAKE_UP_SENSOR = 1; + + // Mask for the reporting mode, bit 2, 3, 4. + private static final int REPORTING_MODE_MASK = 0xE; + private static final int REPORTING_MODE_SHIFT = 1; + // Mask for direct mode highest rate level, bit 7, 8, 9. private static final int DIRECT_REPORT_MASK = 0x380; private static final int DIRECT_REPORT_SHIFT = 7; @@ -193,8 +202,7 @@ public final class VirtualSensorConfig implements Parcelable { @SensorDirectChannel.RateLevel public int getHighestDirectReportRateLevel() { int rateLevel = ((mFlags & DIRECT_REPORT_MASK) >> DIRECT_REPORT_SHIFT); - return rateLevel <= SensorDirectChannel.RATE_VERY_FAST - ? rateLevel : SensorDirectChannel.RATE_VERY_FAST; + return Math.min(rateLevel, SensorDirectChannel.RATE_VERY_FAST); } /** @@ -215,6 +223,28 @@ public final class VirtualSensorConfig implements Parcelable { } /** + * Returns whether the sensor is a wake-up sensor. + * + * @see Builder#setWakeUpSensor(boolean) + * @see Sensor#isWakeUpSensor() + */ + @FlaggedApi(Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER) + public boolean isWakeUpSensor() { + return (mFlags & FLAG_WAKE_UP_SENSOR) > 0; + } + + /** + * Returns the reporting mode of this sensor. + * + * @see Builder#setReportingMode(int) + * @see Sensor#getReportingMode() + */ + @FlaggedApi(Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER) + public int getReportingMode() { + return ((mFlags & REPORTING_MODE_MASK) >> REPORTING_MODE_SHIFT); + } + + /** * Returns the sensor flags. * * @hide @@ -383,6 +413,45 @@ public final class VirtualSensorConfig implements Parcelable { } return this; } + + /** + * Sets whether this sensor is a wake up sensor. + * + * @see Sensor#isWakeUpSensor() + */ + @FlaggedApi(Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER) + @NonNull + public VirtualSensorConfig.Builder setWakeUpSensor(boolean wakeUpSensor) { + if (wakeUpSensor) { + mFlags |= FLAG_WAKE_UP_SENSOR; + } else { + mFlags &= ~FLAG_WAKE_UP_SENSOR; + } + return this; + } + + /** + * Sets the reporting mode of this sensor. + * + * @throws IllegalArgumentException if the reporting mode is not one of + * {@link Sensor#REPORTING_MODE_CONTINUOUS}, {@link Sensor#REPORTING_MODE_ON_CHANGE}, + * {@link Sensor#REPORTING_MODE_ONE_SHOT}, or + * {@link Sensor#REPORTING_MODE_SPECIAL_TRIGGER}. + * + * @see Sensor#getReportingMode() + */ + @FlaggedApi(Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER) + @NonNull + public VirtualSensorConfig.Builder setReportingMode(int reportingMode) { + if (reportingMode != Sensor.REPORTING_MODE_CONTINUOUS + && reportingMode != Sensor.REPORTING_MODE_ON_CHANGE + && reportingMode != Sensor.REPORTING_MODE_ONE_SHOT + && reportingMode != Sensor.REPORTING_MODE_SPECIAL_TRIGGER) { + throw new IllegalArgumentException("Invalid reporting mode: " + reportingMode); + } + mFlags |= reportingMode << REPORTING_MODE_SHIFT; + return this; + } } @NonNull diff --git a/core/java/android/service/dreams/DreamOverlayService.java b/core/java/android/service/dreams/DreamOverlayService.java index 013ec5f35761..244257cb61c8 100644 --- a/core/java/android/service/dreams/DreamOverlayService.java +++ b/core/java/android/service/dreams/DreamOverlayService.java @@ -51,6 +51,8 @@ public abstract class DreamOverlayService extends Service { */ private Executor mExecutor; + private boolean mCurrentRedirectToWake; + // An {@link IDreamOverlayClient} implementation that identifies itself when forwarding // requests to the {@link DreamOverlayService} private static class OverlayClient extends IDreamOverlayClient.Stub { @@ -132,6 +134,10 @@ public abstract class DreamOverlayService extends Service { mExecutor.execute(() -> { endDreamInternal(mCurrentClient); mCurrentClient = client; + if (Flags.dreamWakeRedirect()) { + mCurrentClient.redirectWake(mCurrentRedirectToWake); + } + onStartDream(params); }); } @@ -282,8 +288,10 @@ public abstract class DreamOverlayService extends Service { return; } + mCurrentRedirectToWake = redirect; + if (mCurrentClient == null) { - throw new IllegalStateException("redirected wake with no dream present"); + return; } mCurrentClient.redirectWake(redirect); diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 6f8838619808..3b5286a04b3d 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -3093,74 +3093,74 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager */ private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { - final boolean handled; - - // Canceling motions is a special case. We don't need to perform any transformations - // or filtering. The important part is the action, not the contents. final int oldAction = event.getAction(); - if (cancel || oldAction == MotionEvent.ACTION_CANCEL) { - event.setAction(MotionEvent.ACTION_CANCEL); - if (child == null) { - handled = super.dispatchTouchEvent(event); - } else { - handled = child.dispatchTouchEvent(event); + try { + final boolean handled; + if (cancel) { + event.setAction(MotionEvent.ACTION_CANCEL); } - event.setAction(oldAction); - return handled; - } - // Calculate the number of pointers to deliver. - final int oldPointerIdBits = event.getPointerIdBits(); - final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits; - - // If for some reason we ended up in an inconsistent state where it looks like we - // might produce a motion event with no pointers in it, then drop the event. - if (newPointerIdBits == 0) { - return false; - } + // Calculate the number of pointers to deliver. + final int oldPointerIdBits = event.getPointerIdBits(); + int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits; - // If the number of pointers is the same and we don't need to perform any fancy - // irreversible transformations, then we can reuse the motion event for this - // dispatch as long as we are careful to revert any changes we make. - // Otherwise we need to make a copy. - final MotionEvent transformedEvent; - if (newPointerIdBits == oldPointerIdBits) { - if (child == null || child.hasIdentityMatrix()) { - if (child == null) { - handled = super.dispatchTouchEvent(event); + // If for some reason we ended up in an inconsistent state where it looks like we + // might produce a non-cancel motion event with no pointers in it, then drop the event. + // Make sure that we don't drop any cancel events. + if (newPointerIdBits == 0) { + if (event.getAction() != MotionEvent.ACTION_CANCEL) { + return false; } else { - final float offsetX = mScrollX - child.mLeft; - final float offsetY = mScrollY - child.mTop; - event.offsetLocation(offsetX, offsetY); + newPointerIdBits = oldPointerIdBits; + } + } + + // If the number of pointers is the same and we don't need to perform any fancy + // irreversible transformations, then we can reuse the motion event for this + // dispatch as long as we are careful to revert any changes we make. + // Otherwise we need to make a copy. + final MotionEvent transformedEvent; + if (newPointerIdBits == oldPointerIdBits) { + if (child == null || child.hasIdentityMatrix()) { + if (child == null) { + handled = super.dispatchTouchEvent(event); + } else { + final float offsetX = mScrollX - child.mLeft; + final float offsetY = mScrollY - child.mTop; + event.offsetLocation(offsetX, offsetY); - handled = child.dispatchTouchEvent(event); + handled = child.dispatchTouchEvent(event); - event.offsetLocation(-offsetX, -offsetY); + event.offsetLocation(-offsetX, -offsetY); + } + return handled; } - return handled; + transformedEvent = MotionEvent.obtain(event); + } else { + transformedEvent = event.split(newPointerIdBits); } - transformedEvent = MotionEvent.obtain(event); - } else { - transformedEvent = event.split(newPointerIdBits); - } - // Perform any necessary transformations and dispatch. - if (child == null) { - handled = super.dispatchTouchEvent(transformedEvent); - } else { - final float offsetX = mScrollX - child.mLeft; - final float offsetY = mScrollY - child.mTop; - transformedEvent.offsetLocation(offsetX, offsetY); - if (! child.hasIdentityMatrix()) { - transformedEvent.transform(child.getInverseMatrix()); + // Perform any necessary transformations and dispatch. + if (child == null) { + handled = super.dispatchTouchEvent(transformedEvent); + } else { + final float offsetX = mScrollX - child.mLeft; + final float offsetY = mScrollY - child.mTop; + transformedEvent.offsetLocation(offsetX, offsetY); + if (!child.hasIdentityMatrix()) { + transformedEvent.transform(child.getInverseMatrix()); + } + + handled = child.dispatchTouchEvent(transformedEvent); } - handled = child.dispatchTouchEvent(transformedEvent); - } + // Done. + transformedEvent.recycle(); + return handled; - // Done. - transformedEvent.recycle(); - return handled; + } finally { + event.setAction(oldAction); + } } /** diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 0e0262715d2f..0e1625aaedd8 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -4388,14 +4388,7 @@ public final class ViewRootImpl implements ViewParent, mReportNextDraw = false; mLastReportNextDrawReason = null; mActiveSurfaceSyncGroup = null; - if (mHasPendingTransactions) { - // TODO: We shouldn't ever actually hit this, it means mPendingTransaction wasn't - // merged with a sync group or BLASTBufferQueue before making it to this point - // But better a one or two frame flicker than steady-state broken from dropping - // whatever is in this transaction - mPendingTransaction.apply(); - mHasPendingTransactions = false; - } + mHasPendingTransactions = false; mSyncBuffer = false; if (isInWMSRequestedSync()) { mWmsRequestSyncGroup.markSyncReady(); diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 03a26722da8f..0acc6bde5bfd 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -20,6 +20,7 @@ import static android.view.ContentInfo.SOURCE_DRAG_AND_DROP; import static android.widget.TextView.ACCESSIBILITY_ACTION_SMART_START_ID; import static com.android.graphics.hwui.flags.Flags.highContrastTextSmallTextRect; +import static com.android.text.flags.Flags.contextMenuHideUnavailableItems; import android.R; import android.animation.ValueAnimator; @@ -3250,62 +3251,135 @@ public class Editor { final int menuItemOrderShare = 9; final int menuItemOrderAutofill = 10; - menu.add(CONTEXT_MENU_GROUP_UNDO_REDO, TextView.ID_UNDO, menuItemOrderUndo, - com.android.internal.R.string.undo) - .setAlphabeticShortcut('z') - .setOnMenuItemClickListener(mOnContextMenuItemClickListener) - .setIcon(a.getDrawable(0)) - .setEnabled(mTextView.canUndo()); - menu.add(CONTEXT_MENU_GROUP_UNDO_REDO, TextView.ID_REDO, menuItemOrderRedo, - com.android.internal.R.string.redo) - .setAlphabeticShortcut('z', KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON) - .setOnMenuItemClickListener(mOnContextMenuItemClickListener) - .setIcon(a.getDrawable(1)) - .setEnabled(mTextView.canRedo()); - - menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_CUT, menuItemOrderCut, - com.android.internal.R.string.cut) - .setAlphabeticShortcut('x') - .setOnMenuItemClickListener(mOnContextMenuItemClickListener) - .setIcon(a.getDrawable(2)) - .setEnabled(mTextView.canCut()); - menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_COPY, menuItemOrderCopy, - com.android.internal.R.string.copy) - .setAlphabeticShortcut('c') - .setOnMenuItemClickListener(mOnContextMenuItemClickListener) - .setIcon(a.getDrawable(3)) - .setEnabled(mTextView.canCopy()); - menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_PASTE, menuItemOrderPaste, - com.android.internal.R.string.paste) - .setAlphabeticShortcut('v') - .setEnabled(mTextView.canPaste()) - .setIcon(a.getDrawable(4)) - .setOnMenuItemClickListener(mOnContextMenuItemClickListener); - menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_PASTE_AS_PLAIN_TEXT, - menuItemOrderPasteAsPlainText, - com.android.internal.R.string.paste_as_plain_text) - .setAlphabeticShortcut('v', KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON) - .setEnabled(mTextView.canPasteAsPlainText()) - .setIcon(a.getDrawable(4)) - .setOnMenuItemClickListener(mOnContextMenuItemClickListener); - menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_SELECT_ALL, - menuItemOrderSelectAll, com.android.internal.R.string.selectAll) - .setAlphabeticShortcut('a') - .setEnabled(mTextView.canSelectAllText()) - .setIcon(a.getDrawable(5)) - .setOnMenuItemClickListener(mOnContextMenuItemClickListener); - - menu.add(CONTEXT_MENU_GROUP_MISC, TextView.ID_SHARE, menuItemOrderShare, - com.android.internal.R.string.share) - .setEnabled(mTextView.canShare()) - .setIcon(a.getDrawable(6)) - .setOnMenuItemClickListener(mOnContextMenuItemClickListener); - final String selected = mTextView.getSelectedText(); - menu.add(CONTEXT_MENU_GROUP_MISC, TextView.ID_AUTOFILL, menuItemOrderAutofill, - android.R.string.autofill) - .setEnabled(mTextView.canRequestAutofill() - && (selected == null || selected.isEmpty())) - .setOnMenuItemClickListener(mOnContextMenuItemClickListener); + if (contextMenuHideUnavailableItems()) { + if (mTextView.canUndo()) { + menu.add(CONTEXT_MENU_GROUP_UNDO_REDO, TextView.ID_UNDO, menuItemOrderUndo, + com.android.internal.R.string.undo) + .setAlphabeticShortcut('z') + .setOnMenuItemClickListener(mOnContextMenuItemClickListener) + .setIcon(a.getDrawable(0)); + } + + if (mTextView.canRedo()) { + menu.add(CONTEXT_MENU_GROUP_UNDO_REDO, TextView.ID_REDO, menuItemOrderRedo, + com.android.internal.R.string.redo) + .setAlphabeticShortcut('z', KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON) + .setOnMenuItemClickListener(mOnContextMenuItemClickListener) + .setIcon(a.getDrawable(1)); + } + + if (mTextView.canCut()) { + menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_CUT, menuItemOrderCut, + com.android.internal.R.string.cut) + .setAlphabeticShortcut('x') + .setOnMenuItemClickListener(mOnContextMenuItemClickListener) + .setIcon(a.getDrawable(2)); + } + + if (mTextView.canCopy()) { + menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_COPY, menuItemOrderCopy, + com.android.internal.R.string.copy) + .setAlphabeticShortcut('c') + .setOnMenuItemClickListener(mOnContextMenuItemClickListener) + .setIcon(a.getDrawable(3)); + } + + if (mTextView.canPaste()) { + menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_PASTE, menuItemOrderPaste, + com.android.internal.R.string.paste) + .setAlphabeticShortcut('v') + .setIcon(a.getDrawable(4)) + .setOnMenuItemClickListener(mOnContextMenuItemClickListener); + } + + if (mTextView.canPasteAsPlainText()) { + menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_PASTE_AS_PLAIN_TEXT, + menuItemOrderPasteAsPlainText, + com.android.internal.R.string.paste_as_plain_text) + .setAlphabeticShortcut('v', KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON) + .setIcon(a.getDrawable(4)) + .setOnMenuItemClickListener(mOnContextMenuItemClickListener); + } + + if (mTextView.canSelectAllText()) { + menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_SELECT_ALL, + menuItemOrderSelectAll, com.android.internal.R.string.selectAll) + .setAlphabeticShortcut('a') + .setIcon(a.getDrawable(5)) + .setOnMenuItemClickListener(mOnContextMenuItemClickListener); + } + + if (mTextView.canShare()) { + menu.add(CONTEXT_MENU_GROUP_MISC, TextView.ID_SHARE, menuItemOrderShare, + com.android.internal.R.string.share) + .setIcon(a.getDrawable(6)) + .setOnMenuItemClickListener(mOnContextMenuItemClickListener); + } + + final String selected = mTextView.getSelectedText(); + if (mTextView.canRequestAutofill() && (selected == null || selected.isEmpty())) { + menu.add(CONTEXT_MENU_GROUP_MISC, TextView.ID_AUTOFILL, menuItemOrderAutofill, + android.R.string.autofill) + .setOnMenuItemClickListener(mOnContextMenuItemClickListener); + } + } else { + menu.add(CONTEXT_MENU_GROUP_UNDO_REDO, TextView.ID_UNDO, menuItemOrderUndo, + com.android.internal.R.string.undo) + .setAlphabeticShortcut('z') + .setOnMenuItemClickListener(mOnContextMenuItemClickListener) + .setIcon(a.getDrawable(0)) + .setEnabled(mTextView.canUndo()); + menu.add(CONTEXT_MENU_GROUP_UNDO_REDO, TextView.ID_REDO, menuItemOrderRedo, + com.android.internal.R.string.redo) + .setAlphabeticShortcut('z', KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON) + .setOnMenuItemClickListener(mOnContextMenuItemClickListener) + .setIcon(a.getDrawable(1)) + .setEnabled(mTextView.canRedo()); + + menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_CUT, menuItemOrderCut, + com.android.internal.R.string.cut) + .setAlphabeticShortcut('x') + .setOnMenuItemClickListener(mOnContextMenuItemClickListener) + .setIcon(a.getDrawable(2)) + .setEnabled(mTextView.canCut()); + menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_COPY, menuItemOrderCopy, + com.android.internal.R.string.copy) + .setAlphabeticShortcut('c') + .setOnMenuItemClickListener(mOnContextMenuItemClickListener) + .setIcon(a.getDrawable(3)) + .setEnabled(mTextView.canCopy()); + menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_PASTE, menuItemOrderPaste, + com.android.internal.R.string.paste) + .setAlphabeticShortcut('v') + .setEnabled(mTextView.canPaste()) + .setIcon(a.getDrawable(4)) + .setOnMenuItemClickListener(mOnContextMenuItemClickListener); + menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_PASTE_AS_PLAIN_TEXT, + menuItemOrderPasteAsPlainText, + com.android.internal.R.string.paste_as_plain_text) + .setAlphabeticShortcut('v', KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON) + .setEnabled(mTextView.canPasteAsPlainText()) + .setIcon(a.getDrawable(4)) + .setOnMenuItemClickListener(mOnContextMenuItemClickListener); + menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_SELECT_ALL, + menuItemOrderSelectAll, com.android.internal.R.string.selectAll) + .setAlphabeticShortcut('a') + .setEnabled(mTextView.canSelectAllText()) + .setIcon(a.getDrawable(5)) + .setOnMenuItemClickListener(mOnContextMenuItemClickListener); + + menu.add(CONTEXT_MENU_GROUP_MISC, TextView.ID_SHARE, menuItemOrderShare, + com.android.internal.R.string.share) + .setEnabled(mTextView.canShare()) + .setIcon(a.getDrawable(6)) + .setOnMenuItemClickListener(mOnContextMenuItemClickListener); + final String selected = mTextView.getSelectedText(); + menu.add(CONTEXT_MENU_GROUP_MISC, TextView.ID_AUTOFILL, menuItemOrderAutofill, + android.R.string.autofill) + .setEnabled(mTextView.canRequestAutofill() + && (selected == null || selected.isEmpty())) + .setOnMenuItemClickListener(mOnContextMenuItemClickListener); + } a.recycle(); } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 1ea20fa85bd4..a346a679ea00 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -15552,15 +15552,21 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } - boolean canUndo() { + /** @hide */ + @VisibleForTesting + public boolean canUndo() { return mEditor != null && mEditor.canUndo(); } - boolean canRedo() { + /** @hide */ + @VisibleForTesting + public boolean canRedo() { return mEditor != null && mEditor.canRedo(); } - boolean canCut() { + /** @hide */ + @VisibleForTesting + public boolean canCut() { if (hasPasswordTransformationMethod()) { return false; } @@ -15573,7 +15579,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return false; } - boolean canCopy() { + /** @hide */ + @VisibleForTesting + public boolean canCopy() { if (hasPasswordTransformationMethod()) { return false; } @@ -15594,7 +15602,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener && isSuggestionsEnabled() && mEditor.shouldOfferToShowSuggestions(); } - boolean canShare() { + /** @hide */ + @VisibleForTesting + public boolean canShare() { if (!getContext().canStartActivityForResult() || !isDeviceProvisioned() || !getContext().getResources().getBoolean( com.android.internal.R.bool.config_textShareSupported)) { @@ -15613,8 +15623,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return mDeviceProvisionedState == DEVICE_PROVISIONED_YES; } + /** @hide */ + @VisibleForTesting @UnsupportedAppUsage - boolean canPaste() { + public boolean canPaste() { return (mText instanceof Editable && mEditor != null && mEditor.mKeyListener != null && getSelectionStart() >= 0 @@ -15622,7 +15634,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener && getClipboardManagerForUser().hasPrimaryClip()); } - boolean canPasteAsPlainText() { + /** @hide */ + @VisibleForTesting + public boolean canPasteAsPlainText() { if (!canPaste()) { return false; } @@ -15644,7 +15658,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return canShare(); } - boolean canSelectAllText() { + /** @hide */ + @VisibleForTesting + public boolean canSelectAllText() { return canSelectText() && !hasPasswordTransformationMethod() && !(getSelectionStart() == 0 && getSelectionEnd() == mText.length()); } diff --git a/core/java/com/android/internal/protolog/TEST_MAPPING b/core/java/com/android/internal/protolog/TEST_MAPPING new file mode 100644 index 000000000000..37d57eed8cf4 --- /dev/null +++ b/core/java/com/android/internal/protolog/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "postsubmit": [ + { + "name": "ProtologPerfTests" + } + ] +} diff --git a/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java b/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java index bcf1053e8ddd..3e76977c99fa 100644 --- a/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java +++ b/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java @@ -27,6 +27,7 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -40,6 +41,9 @@ import android.content.Intent; import android.graphics.drawable.Drawable; import android.graphics.drawable.GradientDrawable; import android.graphics.drawable.Icon; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import android.view.ContextMenu; import android.view.MenuItem; import android.view.textclassifier.TextClassification; @@ -47,9 +51,12 @@ import android.view.textclassifier.TextClassification; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.verification.VerificationMode; /** * TextViewTest tests {@link TextView}. @@ -86,6 +93,10 @@ public class TextViewContextMenuTest { private SelectionActionModeHelper mMockHelper; + @ClassRule public static final SetFlagsRule.ClassRule SET_FLAGS_CLASS_RULE = + new SetFlagsRule.ClassRule(); + @Rule public final SetFlagsRule mSetFlagsRule = SET_FLAGS_CLASS_RULE.createSetFlagsRule(); + @Before public void setUp() { mMockHelper = mock(SelectionActionModeHelper.class); @@ -234,6 +245,7 @@ public class TextViewContextMenuTest { } @Test + @DisableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS) public void testAutofillMenuItemEnabledWhenNoTextSelected() { ContextMenu menu = mock(ContextMenu.class); MenuItem mockMenuItem = newMockMenuItem(); @@ -254,6 +266,7 @@ public class TextViewContextMenuTest { } @Test + @DisableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS) public void testAutofillMenuItemNotEnabledWhenTextSelected() { ContextMenu menu = mock(ContextMenu.class); MenuItem mockMenuItem = newMockMenuItem(); @@ -271,4 +284,147 @@ public class TextViewContextMenuTest { verify(menu).add(anyInt(), eq(TextView.ID_AUTOFILL), anyInt(), anyInt()); verify(mockAutofillMenuItem).setEnabled(false); } + + private interface EditTextSetup { + void run(EditText et); + } + + private void verifyMenuItemNotAdded(EditTextSetup setup, int id, VerificationMode times) { + ContextMenu menu = mock(ContextMenu.class); + MenuItem mockMenuItem = newMockMenuItem(); + when(menu.add(anyInt(), anyInt(), anyInt(), anyInt())).thenReturn(mockMenuItem); + EditText et = spy(new EditText(getInstrumentation().getContext())); + setup.run(et); + Editor editor = new Editor(et); + editor.setTextContextMenuItems(menu); + verify(menu, times).add(anyInt(), eq(id), anyInt(), anyInt()); + } + + @Test + @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS) + public void testContextMenuUndoNotAddedWhenUnavailable() { + verifyMenuItemNotAdded((spy) -> doReturn(false).when(spy).canUndo(), + TextView.ID_UNDO, never()); + } + + @Test + @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS) + public void testContextMenuUndoAddedWhenAvailable() { + verifyMenuItemNotAdded((spy) -> doReturn(true).when(spy).canUndo(), TextView.ID_UNDO, + times(1)); + } + + @Test + @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS) + public void testContextMenuRedoNotAddedWhenUnavailable() { + verifyMenuItemNotAdded((spy) -> doReturn(false).when(spy).canRedo(), TextView.ID_REDO, + never()); + } + + @Test + @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS) + public void testContextMenuRedoAddedWhenUnavailable() { + verifyMenuItemNotAdded((spy) -> doReturn(true).when(spy).canRedo(), TextView.ID_REDO, + times(1)); + } + + @Test + @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS) + public void testContextMenuCutNotAddedWhenUnavailable() { + verifyMenuItemNotAdded((spy) -> doReturn(false).when(spy).canCut(), TextView.ID_CUT, + never()); + } + + @Test + @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS) + public void testContextMenuCutAddedWhenAvailable() { + verifyMenuItemNotAdded((spy) -> doReturn(true).when(spy).canCut(), TextView.ID_CUT, + times(1)); + } + + @Test + @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS) + public void testContextMenuCopyNotAddedWhenUnavailable() { + verifyMenuItemNotAdded((spy) -> doReturn(false).when(spy).canCopy(), TextView.ID_COPY, + never()); + } + + @Test + @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS) + public void testContextMenuCopyAddedWhenAvailable() { + verifyMenuItemNotAdded((spy) -> doReturn(true).when(spy).canCopy(), TextView.ID_COPY, + times(1)); + } + + @Test + @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS) + public void testContextMenuPasteNotAddedWhenUnavailable() { + verifyMenuItemNotAdded((spy) -> doReturn(false).when(spy).canPaste(), TextView.ID_PASTE, + never()); + } + + @Test + @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS) + public void testContextMenuPasteAddedWhenAvailable() { + verifyMenuItemNotAdded((spy) -> doReturn(true).when(spy).canPaste(), TextView.ID_PASTE, + times(1)); + } + + @Test + @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS) + public void testContextMenuPasteAsPlaintextNotAddedWhenUnavailable() { + verifyMenuItemNotAdded((spy) -> doReturn(false).when(spy).canPasteAsPlainText(), + TextView.ID_PASTE_AS_PLAIN_TEXT, never()); + } + + @Test + @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS) + public void testContextMenuPasteAsPlaintextAddedWhenAvailable() { + verifyMenuItemNotAdded((spy) -> doReturn(true).when(spy).canPasteAsPlainText(), + TextView.ID_PASTE_AS_PLAIN_TEXT, times(1)); + } + + @Test + @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS) + public void testContextMenuSelectAllNotAddedWhenUnavailable() { + verifyMenuItemNotAdded((spy) -> doReturn(false).when(spy).canSelectAllText(), + TextView.ID_SELECT_ALL, never()); + } + + @Test + @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS) + public void testContextMenuSelectAllAddedWhenAvailable() { + verifyMenuItemNotAdded((spy) -> doReturn(true).when(spy).canSelectAllText(), + TextView.ID_SELECT_ALL, times(1)); + } + + @Test + @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS) + public void testContextMenuShareNotAddedWhenUnavailable() { + verifyMenuItemNotAdded((spy) -> doReturn(false).when(spy).canShare(), TextView.ID_SHARE, + never()); + } + + @Test + @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS) + public void testContextMenuShareAddedWhenAvailable() { + verifyMenuItemNotAdded((spy) -> doReturn(true).when(spy).canShare(), TextView.ID_SHARE, + times(1)); + } + + @Test + @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS) + public void testContextMenuAutofillNotAddedWhenUnavailable() { + verifyMenuItemNotAdded((spy) -> doReturn(false).when(spy).canRequestAutofill(), + TextView.ID_AUTOFILL, never()); + } + + @Test + @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS) + public void testContextMenuAutofillNotAddedWhenUnavailableBecauseTextSelected() { + verifyMenuItemNotAdded((spy) -> { + doReturn(true).when(spy).canRequestAutofill(); + doReturn("test").when(spy).getSelectedText(); + }, TextView.ID_AUTOFILL, never()); + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellCoroutinesModule.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellCoroutinesModule.kt index a489c4ffdd94..423fe579a29a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellCoroutinesModule.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellCoroutinesModule.kt @@ -16,6 +16,7 @@ package com.android.wm.shell.dagger +import android.os.Handler import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.shared.annotations.ShellBackgroundThread import com.android.wm.shell.shared.annotations.ShellMainThread @@ -24,22 +25,37 @@ import dagger.Provides import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainCoroutineDispatcher import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.android.asCoroutineDispatcher import kotlinx.coroutines.asCoroutineDispatcher -/** Providers for various WmShell-specific coroutines-related constructs. */ +/** + * Providers for various WmShell-specific coroutines-related constructs. + * + * Providers of [MainCoroutineDispatcher] intentionally creates the dispatcher with a [Handler] + * backing it instead of a [ShellExecutor] because [ShellExecutor.asCoroutineDispatcher] will + * create a [CoroutineDispatcher] whose [CoroutineDispatcher.isDispatchNeeded] is effectively never + * dispatching. This is because even if dispatched, the backing [ShellExecutor.execute] always runs + * the [Runnable] immediately if called from the same thread, whereas + * [Handler.asCoroutineDispatcher] will create a [MainCoroutineDispatcher] that correctly + * dispatches (queues) when [CoroutineDispatcher.isDispatchNeeded] is true using [Handler.post]. + * For callers that do need a non-dispatching version, [MainCoroutineDispatcher.immediate] is + * available. + */ @Module class WMShellCoroutinesModule { @Provides @ShellMainThread - fun provideMainDispatcher(@ShellMainThread mainExecutor: ShellExecutor): CoroutineDispatcher = - mainExecutor.asCoroutineDispatcher() + fun provideMainDispatcher( + @ShellMainThread mainHandler: Handler + ): MainCoroutineDispatcher = mainHandler.asCoroutineDispatcher() @Provides @ShellBackgroundThread fun provideBackgroundDispatcher( - @ShellBackgroundThread backgroundExecutor: ShellExecutor - ): CoroutineDispatcher = backgroundExecutor.asCoroutineDispatcher() + @ShellBackgroundThread backgroundHandler: Handler + ): MainCoroutineDispatcher = backgroundHandler.asCoroutineDispatcher() @Provides @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 95f864a775be..8921ceb6175d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -21,6 +21,7 @@ import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT; import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED; import static android.view.Display.DEFAULT_DISPLAY; @@ -684,6 +685,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, setSideStagePosition(splitPosition, wct); options1 = options1 != null ? options1 : new Bundle(); addActivityOptions(options1, mSideStage); + prepareTasksForSplitScreen(new int[] {taskId1, taskId2}, wct); wct.startTask(taskId1, options1); startWithTask(wct, taskId2, options2, snapPosition, remoteTransition, instanceId); @@ -714,6 +716,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, options1 = options1 != null ? options1 : new Bundle(); addActivityOptions(options1, mSideStage); wct.sendPendingIntent(pendingIntent, fillInIntent, options1); + prepareTasksForSplitScreen(new int[] {taskId}, wct); startWithTask(wct, taskId, options2, snapPosition, remoteTransition, instanceId); } @@ -757,11 +760,30 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, options1 = options1 != null ? options1 : new Bundle(); addActivityOptions(options1, mSideStage); wct.startShortcut(mContext.getPackageName(), shortcutInfo, options1); + prepareTasksForSplitScreen(new int[] {taskId}, wct); startWithTask(wct, taskId, options2, snapPosition, remoteTransition, instanceId); } /** + * Prepares the tasks whose IDs are provided in `taskIds` for split screen by clearing their + * bounds and windowing mode so that they can inherit the bounds and the windowing mode of + * their root stages. + * + * @param taskIds an array of task IDs whose bounds will be cleared. + * @param wct transaction to clear the bounds on the tasks. + */ + private void prepareTasksForSplitScreen(int[] taskIds, WindowContainerTransaction wct) { + for (int taskId : taskIds) { + ActivityManager.RunningTaskInfo task = mTaskOrganizer.getRunningTaskInfo(taskId); + if (task != null) { + wct.setWindowingMode(task.token, WINDOWING_MODE_UNDEFINED) + .setBounds(task.token, null); + } + } + } + + /** * Starts with the second task to a split pair in one transition. * * @param wct transaction to start the first task diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt index d5874d1a7d3f..e17cb31407da 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt @@ -16,6 +16,8 @@ package com.android.systemui.scene.ui.composable +import androidx.compose.foundation.gestures.awaitEachGesture +import androidx.compose.foundation.gestures.awaitFirstDown import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.Text @@ -28,6 +30,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.pointerInput import com.android.compose.animation.scene.MutableSceneTransitionLayoutState import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.SceneTransitionLayout @@ -100,7 +103,13 @@ fun SceneContainer( } Box( - modifier = Modifier.fillMaxSize(), + modifier = + Modifier.fillMaxSize().pointerInput(Unit) { + awaitEachGesture { + awaitFirstDown(false) + viewModel.onSceneContainerUserInputStarted() + } + }, ) { SceneTransitionLayout(state = state, modifier = modifier.fillMaxSize()) { sceneByKey.forEach { (sceneKey, composableScene) -> diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt index d850f17cd89a..65236f02b635 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt @@ -30,8 +30,6 @@ import com.android.systemui.authentication.shared.model.AuthenticationMethodMode import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.EnableSceneContainer -import com.android.systemui.flags.Flags.REFACTOR_GETCURRENTUSER -import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.kosmos.testScope import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository @@ -90,8 +88,6 @@ class BouncerActionButtonInteractorTest : SysuiTestCase() { .thenReturn(needsEmergencyAffordance) whenever(telecomManager.isInCall).thenReturn(false) - kosmos.fakeFeatureFlagsClassic.set(REFACTOR_GETCURRENTUSER, true) - kosmos.fakeTelephonyRepository.setHasTelephonyRadio(true) kosmos.telecomManager = telecomManager diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt index c63381687f18..5b987b309b6b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt @@ -175,10 +175,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { emergencyAffordanceManager = kosmos.emergencyAffordanceManager whenever(emergencyAffordanceManager.needsEmergencyAffordance()).thenReturn(true) - kosmos.fakeFeatureFlagsClassic.apply { - set(Flags.NEW_NETWORK_SLICE_UI, false) - set(Flags.REFACTOR_GETCURRENTUSER, true) - } + kosmos.fakeFeatureFlagsClassic.apply { set(Flags.NEW_NETWORK_SLICE_UI, false) } mobileConnectionsRepository = kosmos.fakeMobileConnectionsRepository mobileConnectionsRepository.isAnySimSecure.value = false diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt index e3a69a964b45..35cefa6b58df 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt @@ -401,10 +401,10 @@ class SceneInteractorTest : SysuiTestCase() { underTest.setVisible(false, "reason") val isVisible by collectLastValue(underTest.isVisible) assertThat(isVisible).isFalse() - underTest.onRemoteUserInteractionStarted("reason") + underTest.onRemoteUserInputStarted("reason") assertThat(isVisible).isTrue() - underTest.onUserInteractionFinished() + underTest.onUserInputFinished() assertThat(isVisible).isFalse() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt index f856c559454c..832e7b1bcc0c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt @@ -237,7 +237,7 @@ class SceneContainerViewModelTest : SysuiTestCase() { sceneInteractor.setVisible(false, "reason") runCurrent() assertThat(underTest.isVisible).isFalse() - sceneInteractor.onRemoteUserInteractionStarted("reason") + sceneInteractor.onRemoteUserInputStarted("reason") runCurrent() assertThat(underTest.isVisible).isTrue() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModelTest.kt index 6a886643cebb..8b97739af1db 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModelTest.kt @@ -22,6 +22,8 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat @@ -83,4 +85,69 @@ class NotificationShadeWindowModelTest : SysuiTestCase() { ) assertThat(isKeyguardOccluded).isFalse() } + + @Test + fun transitionFromOccludedToDreamingTransitionRemainsTrue() = + testScope.runTest { + val isKeyguardOccluded by collectLastValue(underTest.isKeyguardOccluded) + assertThat(isKeyguardOccluded).isFalse() + + keyguardTransitionRepository.sendTransitionSteps( + listOf( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.DREAMING, + value = 0f, + transitionState = TransitionState.STARTED, + ), + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.DREAMING, + value = 0.5f, + transitionState = TransitionState.RUNNING, + ), + ), + testScope, + ) + assertThat(isKeyguardOccluded).isFalse() + + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.DREAMING, + value = 1f, + transitionState = TransitionState.FINISHED, + ), + ) + assertThat(isKeyguardOccluded).isTrue() + + keyguardTransitionRepository.sendTransitionSteps( + listOf( + TransitionStep( + from = KeyguardState.DREAMING, + to = KeyguardState.OCCLUDED, + value = 0f, + transitionState = TransitionState.STARTED, + ), + TransitionStep( + from = KeyguardState.DREAMING, + to = KeyguardState.OCCLUDED, + value = 0.5f, + transitionState = TransitionState.RUNNING, + ), + ), + testScope, + ) + assertThat(isKeyguardOccluded).isTrue() + + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.DREAMING, + to = KeyguardState.OCCLUDED, + value = 1f, + transitionState = TransitionState.FINISHED, + ), + ) + assertThat(isKeyguardOccluded).isTrue() + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt index 1356e93db549..06a2c5af2986 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt @@ -21,6 +21,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.shade.data.repository.shadeRepository import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimRounding @@ -84,4 +85,48 @@ class NotificationStackAppearanceInteractorTest : SysuiTestCase() { ) ) } + + @Test + fun shouldCloseGuts_userInputOngoing_currentGestureInGuts() = + testScope.runTest { + val shouldCloseGuts by collectLastValue(underTest.shouldCloseGuts) + + kosmos.sceneInteractor.onSceneContainerUserInputStarted() + underTest.setCurrentGestureInGuts(true) + + assertThat(shouldCloseGuts).isFalse() + } + + @Test + fun shouldCloseGuts_userInputOngoing_currentGestureNotInGuts() = + testScope.runTest { + val shouldCloseGuts by collectLastValue(underTest.shouldCloseGuts) + + kosmos.sceneInteractor.onSceneContainerUserInputStarted() + underTest.setCurrentGestureInGuts(false) + + assertThat(shouldCloseGuts).isTrue() + } + + @Test + fun shouldCloseGuts_userInputNotOngoing_currentGestureInGuts() = + testScope.runTest { + val shouldCloseGuts by collectLastValue(underTest.shouldCloseGuts) + + kosmos.sceneInteractor.onUserInputFinished() + underTest.setCurrentGestureInGuts(true) + + assertThat(shouldCloseGuts).isFalse() + } + + @Test + fun shouldCloseGuts_userInputNotOngoing_currentGestureNotInGuts() = + testScope.runTest { + val shouldCloseGuts by collectLastValue(underTest.shouldCloseGuts) + + kosmos.sceneInteractor.onUserInputFinished() + underTest.setCurrentGestureInGuts(false) + + assertThat(shouldCloseGuts).isFalse() + } } diff --git a/packages/SystemUI/res/values-sw600dp-land/config.xml b/packages/SystemUI/res/values-sw600dp-land/config.xml index 0c11d2fa1d11..fc6d20e11d3b 100644 --- a/packages/SystemUI/res/values-sw600dp-land/config.xml +++ b/packages/SystemUI/res/values-sw600dp-land/config.xml @@ -27,8 +27,6 @@ <!-- Whether to use the split 2-column notification shade --> <bool name="config_use_split_notification_shade">true</bool> - <bool name="config_use_large_screen_shade_header">true</bool> - <!-- The number of columns in the QuickSettings --> <integer name="quick_settings_num_columns">2</integer> diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml index c594f1cd9313..b4383156dc71 100644 --- a/packages/SystemUI/res/values-sw600dp/config.xml +++ b/packages/SystemUI/res/values-sw600dp/config.xml @@ -35,6 +35,8 @@ <!-- How many lines to show in the security footer --> <integer name="qs_security_footer_maxLines">1</integer> + <bool name="config_use_large_screen_shade_header">true</bool> + <!-- Whether to show bottom sheets edge to edge --> <bool name="config_edgeToEdgeBottomSheetDialog">false</bool> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 60fff282d041..9b45fa47cf21 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -483,22 +483,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @VisibleForTesting SparseArray<BiometricAuthenticated> mUserFingerprintAuthenticated = new SparseArray<>(); - private static int sCurrentUser; - - @Deprecated - public synchronized static void setCurrentUser(int currentUser) { - sCurrentUser = currentUser; - } - - /** - * @deprecated This can potentially return unexpected values in a multi user scenario - * as this state is managed by another component. Consider using {@link SelectedUserInteractor}. - */ - @Deprecated - public synchronized static int getCurrentUser() { - return sCurrentUser; - } - @Override public void onTrustChanged(boolean enabled, boolean newlyUnlocked, int userId, int flags, List<String> trustGrantedMessages) { @@ -969,7 +953,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mHandler.removeCallbacks(mFpCancelNotReceived); } try { - final int userId = mSelectedUserInteractor.getSelectedUserId(true); + final int userId = mSelectedUserInteractor.getSelectedUserId(); if (userId != authUserId) { mLogger.logFingerprintAuthForWrongUser(authUserId); return; @@ -1220,7 +1204,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mLogger.d("Aborted successful auth because device is going to sleep."); return; } - final int userId = mSelectedUserInteractor.getSelectedUserId(true); + final int userId = mSelectedUserInteractor.getSelectedUserId(); if (userId != authUserId) { mLogger.logFaceAuthForWrongUser(authUserId); return; @@ -2462,7 +2446,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE); mTaskStackChangeListeners.registerTaskStackListener(mTaskStackListener); - int user = mSelectedUserInteractor.getSelectedUserId(true); + int user = mSelectedUserInteractor.getSelectedUserId(); boolean isUserUnlocked = mUserManager.isUserUnlocked(user); mLogger.logUserUnlockedInitialState(user, isUserUnlocked); mUserIsUnlocked.put(user, isUserUnlocked); @@ -4081,7 +4065,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab pw.println(" " + subId + "=" + mServiceStates.get(subId)); } if (isFingerprintSupported()) { - final int userId = mSelectedUserInteractor.getSelectedUserId(true); + final int userId = mSelectedUserInteractor.getSelectedUserId(); final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId); BiometricAuthenticated fingerprint = mUserFingerprintAuthenticated.get(userId); pw.println(" Fingerprint state (user=" + userId + ")"); @@ -4124,7 +4108,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mFingerprintListenBuffer.toList() ).printTableData(pw); } else if (mFpm != null && mFingerprintSensorProperties.isEmpty()) { - final int userId = mSelectedUserInteractor.getSelectedUserId(true); + final int userId = mSelectedUserInteractor.getSelectedUserId(); pw.println(" Fingerprint state (user=" + userId + ")"); pw.println(" mFingerprintSensorProperties.isEmpty=" + mFingerprintSensorProperties.isEmpty()); @@ -4137,7 +4121,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mFingerprintListenBuffer.toList() ).printTableData(pw); } - final int userId = mSelectedUserInteractor.getSelectedUserId(true); + final int userId = mSelectedUserInteractor.getSelectedUserId(); final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId); pw.println(" authSinceBoot=" + getStrongAuthTracker().hasUserAuthenticatedSinceBoot()); diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt index 6757edba8ac3..b2d02edf3c45 100644 --- a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt @@ -120,7 +120,7 @@ constructor( Intent.FLAG_ACTIVITY_NEW_TASK, null, activityOptions.toBundle(), - selectedUserInteractor.getSelectedUserId(true), + selectedUserInteractor.getSelectedUserId(), ) } catch (e: RemoteException) { Log.w("CameraGestureHelper", "Unable to start camera activity", e) diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java index e07b5c228585..21922ff22afe 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java @@ -256,7 +256,7 @@ public class DozeSensors { Settings.Secure.DOZE_WAKE_DISPLAY_GESTURE, mConfig.wakeScreenGestureAvailable() && mConfig.alwaysOnEnabled( - mSelectedUserInteractor.getSelectedUserId(true)), + mSelectedUserInteractor.getSelectedUserId()), DozeLog.REASON_SENSOR_WAKE_UP_PRESENCE, false /* reports touch coordinates */, false /* touchscreen */ @@ -297,7 +297,7 @@ public class DozeSensors { private boolean udfpsLongPressConfigured() { return mUdfpsEnrolled - && (mConfig.alwaysOnEnabled(mSelectedUserInteractor.getSelectedUserId(true)) + && (mConfig.alwaysOnEnabled(mSelectedUserInteractor.getSelectedUserId()) || mScreenOffUdfpsEnabled); } @@ -477,7 +477,7 @@ public class DozeSensors { private final ContentObserver mSettingsObserver = new ContentObserver(mHandler) { @Override public void onChange(boolean selfChange, Collection<Uri> uris, int flags, int userId) { - if (userId != mSelectedUserInteractor.getSelectedUserId(true)) { + if (userId != mSelectedUserInteractor.getSelectedUserId()) { return; } for (TriggerSensor s : mTriggerSensors) { @@ -703,13 +703,13 @@ public class DozeSensors { } protected boolean enabledBySetting() { - if (!mConfig.enabled(mSelectedUserInteractor.getSelectedUserId(true))) { + if (!mConfig.enabled(mSelectedUserInteractor.getSelectedUserId())) { return false; } else if (TextUtils.isEmpty(mSetting)) { return true; } return mSecureSettings.getIntForUser(mSetting, mSettingDefault ? 1 : 0, - mSelectedUserInteractor.getSelectedUserId(true)) != 0; + mSelectedUserInteractor.getSelectedUserId()) != 0; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java index 4a9f741494f4..dd08d3262546 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java @@ -251,7 +251,7 @@ public class DozeTriggers implements DozeMachine.Part { return; } mNotificationPulseTime = SystemClock.elapsedRealtime(); - if (!mConfig.pulseOnNotificationEnabled(mSelectedUserInteractor.getSelectedUserId(true))) { + if (!mConfig.pulseOnNotificationEnabled(mSelectedUserInteractor.getSelectedUserId())) { runIfNotNull(onPulseSuppressedListener); mDozeLog.tracePulseDropped("pulseOnNotificationsDisabled"); return; diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 4d75d661de49..bb73f569d945 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -55,19 +55,13 @@ object Flags { // TODO(b/254512624): Tracking Bug @JvmField val NOTIFICATION_DRAG_TO_CONTENTS = - resourceBooleanFlag( - R.bool.config_notificationToContents, - "notification_drag_to_contents" - ) + resourceBooleanFlag(R.bool.config_notificationToContents, "notification_drag_to_contents") // TODO(b/280783617): Tracking Bug @Keep @JvmField val BUILDER_EXTRAS_OVERRIDE = - sysPropBooleanFlag( - "persist.sysui.notification.builder_extras_override", - default = true - ) + sysPropBooleanFlag("persist.sysui.notification.builder_extras_override", default = true) // 200 - keyguard/lockscreen // ** Flag retired ** @@ -81,10 +75,7 @@ object Flags { // TODO(b/254512676): Tracking Bug @JvmField val LOCKSCREEN_CUSTOM_CLOCKS = - resourceBooleanFlag( - R.bool.config_enableLockScreenCustomClocks, - "lockscreen_custom_clocks" - ) + resourceBooleanFlag(R.bool.config_enableLockScreenCustomClocks, "lockscreen_custom_clocks") /** * Whether the clock on a wide lock screen should use the new "stepping" animation for moving @@ -99,10 +90,6 @@ object Flags { // TODO(b/255607168): Tracking Bug @JvmField val DOZING_MIGRATION_1 = unreleasedFlag("dozing_migration_1") - // TODO(b/305984787): - @JvmField - val REFACTOR_GETCURRENTUSER = unreleasedFlag("refactor_getcurrentuser", teamfood = true) - /** Flag to control the revamp of keyguard biometrics progress animation */ // TODO(b/244313043): Tracking bug @JvmField val BIOMETRICS_ANIMATION_REVAMP = unreleasedFlag("biometrics_animation_revamp") @@ -125,13 +112,11 @@ object Flags { /** Whether the long-press gesture to open wallpaper picker is enabled. */ // TODO(b/266242192): Tracking Bug - @JvmField - val LOCK_SCREEN_LONG_PRESS_ENABLED = releasedFlag("lock_screen_long_press_enabled") + @JvmField val LOCK_SCREEN_LONG_PRESS_ENABLED = releasedFlag("lock_screen_long_press_enabled") /** Inflate and bind views upon emitting a blueprint value . */ // TODO(b/297365780): Tracking Bug - @JvmField - val LAZY_INFLATE_KEYGUARD = releasedFlag("lazy_inflate_keyguard") + @JvmField val LAZY_INFLATE_KEYGUARD = releasedFlag("lazy_inflate_keyguard") /** Enables UI updates for AI wallpapers in the wallpaper picker. */ // TODO(b/267722622): Tracking Bug @@ -145,8 +130,7 @@ object Flags { /** Add "Apply" button to wall paper picker's grid preview page. */ // TODO(b/294866904): Tracking bug. @JvmField - val WALLPAPER_PICKER_GRID_APPLY_BUTTON = - unreleasedFlag("wallpaper_picker_grid_apply_button") + val WALLPAPER_PICKER_GRID_APPLY_BUTTON = unreleasedFlag("wallpaper_picker_grid_apply_button") /** Flag meant to guard the talkback fix for the KeyguardIndicationTextView */ // TODO(b/286563884): Tracking bug @@ -190,10 +174,7 @@ object Flags { // TODO(b/254512383): Tracking Bug @JvmField val FULL_SCREEN_USER_SWITCHER = - resourceBooleanFlag( - R.bool.config_enableFullscreenUserSwitcher, - "full_screen_user_switcher" - ) + resourceBooleanFlag(R.bool.config_enableFullscreenUserSwitcher, "full_screen_user_switcher") // TODO(b/244064524): Tracking Bug @JvmField val QS_SECONDARY_DATA_SUB_INFO = releasedFlag("qs_secondary_data_sub_info") @@ -212,16 +193,15 @@ object Flags { @JvmField val NEW_NETWORK_SLICE_UI = releasedFlag("new_network_slice_ui") // TODO(b/311222557): Tracking bug - val ROAMING_INDICATOR_VIA_DISPLAY_INFO = - releasedFlag("roaming_indicator_via_display_info") + val ROAMING_INDICATOR_VIA_DISPLAY_INFO = releasedFlag("roaming_indicator_via_display_info") // TODO(b/308138154): Tracking bug val FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS = releasedFlag("filter_provisioning_network_subscriptions") // TODO(b/293863612): Tracking Bug - @JvmField val INCOMPATIBLE_CHARGING_BATTERY_ICON = - releasedFlag("incompatible_charging_battery_icon") + @JvmField + val INCOMPATIBLE_CHARGING_BATTERY_ICON = releasedFlag("incompatible_charging_battery_icon") // TODO(b/293585143): Tracking Bug val INSTANT_TETHER = releasedFlag("instant_tether") @@ -230,8 +210,7 @@ object Flags { val WIFI_SECONDARY_NETWORKS = releasedFlag("wifi_secondary_networks") // TODO(b/290676905): Tracking Bug - val NEW_SHADE_CARRIER_GROUP_MOBILE_ICONS = - releasedFlag("new_shade_carrier_group_mobile_icons") + val NEW_SHADE_CARRIER_GROUP_MOBILE_ICONS = releasedFlag("new_shade_carrier_group_mobile_icons") // 800 - general visual/theme @JvmField val MONET = resourceBooleanFlag(R.bool.flag_monet, "monet") @@ -280,8 +259,7 @@ object Flags { // TODO(b/273509374): Tracking Bug @JvmField - val ALWAYS_SHOW_HOME_CONTROLS_ON_DREAMS = - releasedFlag("always_show_home_controls_on_dreams") + val ALWAYS_SHOW_HOME_CONTROLS_ON_DREAMS = releasedFlag("always_show_home_controls_on_dreams") // 1100 - windowing @Keep @@ -304,9 +282,7 @@ object Flags { ) // TODO(b/293252410) : Tracking Bug - @JvmField - val LOCKSCREEN_ENABLE_LANDSCAPE = - unreleasedFlag("lockscreen.enable_landscape") + @JvmField val LOCKSCREEN_ENABLE_LANDSCAPE = unreleasedFlag("lockscreen.enable_landscape") // 1200 - predictive back @Keep @@ -327,8 +303,7 @@ object Flags { val QUICK_TAP_IN_PCC = releasedFlag("quick_tap_in_pcc") // TODO(b/261979569): Tracking Bug - val QUICK_TAP_FLOW_FRAMEWORK = - unreleasedFlag("quick_tap_flow_framework", teamfood = false) + val QUICK_TAP_FLOW_FRAMEWORK = unreleasedFlag("quick_tap_flow_framework", teamfood = false) // 1500 - chooser aka sharesheet @@ -364,14 +339,12 @@ object Flags { // TODO(b/265764985): Tracking Bug @Keep @JvmField - val ENABLE_DARK_VIGNETTE_WHEN_FOLDING = - unreleasedFlag("enable_dark_vignette_when_folding") + val ENABLE_DARK_VIGNETTE_WHEN_FOLDING = unreleasedFlag("enable_dark_vignette_when_folding") // TODO(b/265764985): Tracking Bug @Keep @JvmField - val ENABLE_UNFOLD_STATUS_BAR_ANIMATIONS = - unreleasedFlag("enable_unfold_status_bar_animations") + val ENABLE_UNFOLD_STATUS_BAR_ANIMATIONS = unreleasedFlag("enable_unfold_status_bar_animations") // TODO(b/316157842): Tracking Bug // Adds extra delay to notifications measure @@ -415,28 +388,26 @@ object Flags { unreleasedFlag("bigpicture_notification_lazy_loading") // TODO(b/283740863): Tracking Bug - @JvmField - val ENABLE_NEW_PRIVACY_DIALOG = releasedFlag("enable_new_privacy_dialog") + @JvmField val ENABLE_NEW_PRIVACY_DIALOG = releasedFlag("enable_new_privacy_dialog") // TODO(b/302144438): Tracking Bug - @JvmField val DECOUPLE_REMOTE_INPUT_DELEGATE_AND_CALLBACK_UPDATE = - unreleasedFlag("decouple_remote_input_delegate_and_callback_update") + @JvmField + val DECOUPLE_REMOTE_INPUT_DELEGATE_AND_CALLBACK_UPDATE = + unreleasedFlag("decouple_remote_input_delegate_and_callback_update") /** TODO(b/296223317): Enables the new keyguard presentation containing a clock. */ @JvmField val ENABLE_CLOCK_KEYGUARD_PRESENTATION = releasedFlag("enable_clock_keyguard_presentation") /** Enable the share wifi button in Quick Settings internet dialog. */ - @JvmField - val SHARE_WIFI_QS_BUTTON = releasedFlag("share_wifi_qs_button") + @JvmField val SHARE_WIFI_QS_BUTTON = releasedFlag("share_wifi_qs_button") /** Enable showing a dialog when clicking on Quick Settings bluetooth tile. */ - @JvmField - val BLUETOOTH_QS_TILE_DIALOG = releasedFlag("bluetooth_qs_tile_dialog") + @JvmField val BLUETOOTH_QS_TILE_DIALOG = releasedFlag("bluetooth_qs_tile_dialog") // TODO(b/300995746): Tracking Bug /** A resource flag for whether the communal service is enabled. */ @JvmField - val COMMUNAL_SERVICE_ENABLED = resourceBooleanFlag(R.bool.config_communalServiceEnabled, - "communal_service_enabled") + val COMMUNAL_SERVICE_ENABLED = + resourceBooleanFlag(R.bool.config_communalServiceEnabled, "communal_service_enabled") } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index fe3a1e447b07..0feb5ec277b4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -35,8 +35,6 @@ import static android.view.WindowManager.TransitionFlags; import static android.view.WindowManager.TransitionOldType; import static android.view.WindowManager.TransitionType; -import static com.android.systemui.Flags.refactorGetCurrentUser; - import android.annotation.NonNull; import android.app.ActivityManager; import android.app.ActivityTaskManager; @@ -680,9 +678,6 @@ public class KeyguardService extends Service { public void setCurrentUser(int userId) { trace("Deprecated/NOT USED: setCurrentUser userId=" + userId); checkPermission(); - if (!refactorGetCurrentUser()) { - mKeyguardViewMediator.setCurrentUser(userId); - } } @Override // Binder interface diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 17c5977fc80a..8c82900810be 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -41,7 +41,6 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STR import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE; import static com.android.systemui.DejankUtils.whitelistIpcs; import static com.android.systemui.Flags.notifyPowerManagerUserActivityBackground; -import static com.android.systemui.Flags.refactorGetCurrentUser; import static com.android.systemui.Flags.relockWithPowerButtonImmediately; import static com.android.systemui.Flags.translucentOccludingActivityFix; import static com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel.DREAMING_ANIMATION_DURATION_MS; @@ -626,11 +625,9 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, @Override public void onUserSwitching(int userId) { - if (DEBUG) Log.d(TAG, String.format("onUserSwitching %d", userId)); + Log.d(TAG, String.format("onUserSwitching %d", userId)); synchronized (KeyguardViewMediator.this) { - if (refactorGetCurrentUser()) { - notifyTrustedChangedLocked(mUpdateMonitor.getUserHasTrust(userId)); - } + notifyTrustedChangedLocked(mUpdateMonitor.getUserHasTrust(userId)); resetKeyguardDonePendingLocked(); dismiss(null /* callback */, null /* message */); adjustStatusBarLocked(); @@ -639,7 +636,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, @Override public void onUserSwitchComplete(int userId) { - if (DEBUG) Log.d(TAG, String.format("onUserSwitchComplete %d", userId)); + Log.d(TAG, String.format("onUserSwitchComplete %d", userId)); // We are calling dismiss again and with a delay as there are race conditions // in some scenarios caused by async layout listeners mHandler.postDelayed(() -> dismiss(null /* callback */, null /* message */), 500); @@ -1580,10 +1577,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); - if (!refactorGetCurrentUser()) { - KeyguardUpdateMonitor.setCurrentUser(mUserTracker.getUserId()); - } - // Assume keyguard is showing (unless it's disabled) until we know for sure, unless Keyguard // is disabled. if (isKeyguardServiceEnabled()) { @@ -2546,19 +2539,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } /** - * Update the newUserId. Call while holding WindowManagerService lock. - * NOTE: Should only be called by KeyguardViewMediator in response to the user id changing. - * - * @param newUserId The id of the incoming user. - */ - public void setCurrentUser(int newUserId) { - KeyguardUpdateMonitor.setCurrentUser(newUserId); - synchronized (this) { - notifyTrustedChangedLocked(mUpdateMonitor.getUserHasTrust(newUserId)); - } - } - - /** * This broadcast receiver should be registered with the SystemUI permission. */ private final BroadcastReceiver mDelayedLockBroadcastReceiver = new BroadcastReceiver() { @@ -3553,7 +3533,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, try { mStatusBarService.disableForUser(flags, mStatusBarDisableToken, mContext.getPackageName(), - mSelectedUserInteractor.getSelectedUserId(true)); + mSelectedUserInteractor.getSelectedUserId()); } catch (RemoteException e) { Log.d(TAG, "Failed to force clear flags", e); } @@ -3591,7 +3571,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, try { mStatusBarService.disableForUser(flags, mStatusBarDisableToken, mContext.getPackageName(), - mSelectedUserInteractor.getSelectedUserId(true)); + mSelectedUserInteractor.getSelectedUserId()); } catch (RemoteException e) { Log.d(TAG, "Failed to set disable flags: " + flags, e); } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt index 31b0bf7fe425..d9c48fa7e581 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt @@ -28,6 +28,7 @@ constructor( private val interactors: Set<TransitionInteractor>, private val auditLogger: KeyguardTransitionAuditLogger, private val bootInteractor: KeyguardTransitionBootInteractor, + private val statusBarDisableFlagsInteractor: StatusBarDisableFlagsInteractor, ) : CoreStartable { override fun start() { @@ -53,6 +54,7 @@ constructor( } auditLogger.start() bootInteractor.start() + statusBarDisableFlagsInteractor.start() } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StatusBarDisableFlagsInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StatusBarDisableFlagsInteractor.kt new file mode 100644 index 000000000000..47818cbfd2f2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StatusBarDisableFlagsInteractor.kt @@ -0,0 +1,157 @@ +/* + * 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.systemui.keyguard.domain.interactor + +import android.annotation.SuppressLint +import android.app.StatusBarManager +import android.content.Context +import android.os.Binder +import android.os.IBinder +import android.os.RemoteException +import android.provider.DeviceConfig +import com.android.internal.config.sysui.SystemUiDeviceConfigFlags +import com.android.internal.statusbar.IStatusBarService +import com.android.systemui.CoreStartable +import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.deviceconfig.domain.interactor.DeviceConfigInteractor +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor +import com.android.systemui.keyguard.KeyguardWmStateRefactor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.navigation.domain.interactor.NavigationInteractor +import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.power.shared.model.WakeSleepReason +import com.android.systemui.power.shared.model.WakefulnessModel +import com.android.systemui.user.domain.interactor.SelectedUserInteractor +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +/** + * Logic around StatusBarService#disableForUser, which is used to disable the home and recents + * button in certain device states. + * + * TODO(b/362313975): Remove post-Flexiglass, this duplicates StatusBarStartable logic. + */ +@SysUISingleton +class StatusBarDisableFlagsInteractor +@Inject +constructor( + @Application private val scope: CoroutineScope, + @Application private val applicationContext: Context, + @Background private val backgroundDispatcher: CoroutineDispatcher, + private val deviceEntryFaceAuthInteractor: DeviceEntryFaceAuthInteractor, + private val statusBarService: IStatusBarService, + keyguardTransitionInteractor: KeyguardTransitionInteractor, + selectedUserInteractor: SelectedUserInteractor, + deviceConfigInteractor: DeviceConfigInteractor, + navigationInteractor: NavigationInteractor, + authenticationInteractor: AuthenticationInteractor, + powerInteractor: PowerInteractor, +) : CoreStartable { + + private val disableToken: IBinder = Binder() + + private val disableFlagsForUserId = + combine( + selectedUserInteractor.selectedUser, + keyguardTransitionInteractor.startedKeyguardState, + deviceConfigInteractor.property( + namespace = DeviceConfig.NAMESPACE_SYSTEMUI, + name = SystemUiDeviceConfigFlags.NAV_BAR_HANDLE_SHOW_OVER_LOCKSCREEN, + default = true, + ), + navigationInteractor.isGesturalMode, + authenticationInteractor.authenticationMethod, + powerInteractor.detailedWakefulness, + ) { values -> + val selectedUserId = values[0] as Int + val startedState = values[1] as KeyguardState + val isShowHomeOverLockscreen = values[2] as Boolean + val isGesturalMode = values[3] as Boolean + val authenticationMethod = values[4] as AuthenticationMethodModel + val wakefulnessModel = values[5] as WakefulnessModel + val isOccluded = startedState == KeyguardState.OCCLUDED + + val hideHomeAndRecentsForBouncer = + startedState == KeyguardState.PRIMARY_BOUNCER || + startedState == KeyguardState.ALTERNATE_BOUNCER + val isKeyguardShowing = startedState != KeyguardState.GONE + val isPowerGestureIntercepted = + with(wakefulnessModel) { + isAwake() && + powerButtonLaunchGestureTriggered && + lastSleepReason == WakeSleepReason.POWER_BUTTON + } + + var flags = StatusBarManager.DISABLE_NONE + + if (hideHomeAndRecentsForBouncer || (isKeyguardShowing && !isOccluded)) { + if (!isShowHomeOverLockscreen || !isGesturalMode) { + flags = flags or StatusBarManager.DISABLE_HOME + } + flags = flags or StatusBarManager.DISABLE_RECENT + } + + if ( + isPowerGestureIntercepted && + isOccluded && + authenticationMethod.isSecure && + deviceEntryFaceAuthInteractor.isFaceAuthEnabledAndEnrolled() + ) { + flags = flags or StatusBarManager.DISABLE_RECENT + } + + selectedUserId to flags + } + .distinctUntilChanged() + + @SuppressLint("WrongConstant", "NonInjectedService") + override fun start() { + if (!KeyguardWmStateRefactor.isEnabled) { + return + } + + scope.launch { + disableFlagsForUserId.collect { (selectedUserId, flags) -> + if (applicationContext.getSystemService(Context.STATUS_BAR_SERVICE) == null) { + return@collect + } + + withContext(backgroundDispatcher) { + try { + statusBarService.disableForUser( + flags, + disableToken, + applicationContext.packageName, + selectedUserId, + ) + } catch (e: RemoteException) { + e.printStackTrace() + } + } + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java index 7c2d50c9a3a3..9abc494e56e6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java @@ -71,6 +71,7 @@ import com.android.systemui.qs.SideLabelTileLayout; import com.android.systemui.qs.logging.QSLogger; import java.io.PrintWriter; +import java.util.Objects; /** * Base quick-settings tile, extend this to create a new tile. @@ -668,6 +669,18 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy public String toString() { return "DrawableIcon"; } + + @Override + public boolean equals(@Nullable Object other) { + // No need to compare equality of the mInvisibleDrawable as that's generated from + // mDrawable's constant state. + return other instanceof DrawableIcon && ((DrawableIcon) other).mDrawable == mDrawable; + } + + @Override + public int hashCode() { + return Objects.hash(mDrawable); + } } public static class DrawableIconWithRes extends DrawableIcon { diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index a37a722e13e0..fe5cbb18f046 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -230,7 +230,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis // If scene framework is enabled, set the scene container window to // visible and let the touch "slip" into that window. if (SceneContainerFlag.isEnabled()) { - mSceneInteractor.get().onRemoteUserInteractionStarted("launcher swipe"); + mSceneInteractor.get().onRemoteUserInputStarted("launcher swipe"); } else { mShadeViewControllerLazy.get().startInputFocusTransfer(); } @@ -266,7 +266,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis if (SceneContainerFlag.isEnabled()) { int action = event.getActionMasked(); if (action == ACTION_DOWN) { - mSceneInteractor.get().onRemoteUserInteractionStarted( + mSceneInteractor.get().onRemoteUserInputStarted( "trackpad swipe"); } else if (action == ACTION_UP) { mSceneInteractor.get().changeScene( diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt index 3e2c6306467f..beb6816d70a9 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt @@ -56,7 +56,10 @@ constructor( * * For more information see the logic in `SceneInteractor` that mutates this. */ - val isRemoteUserInteractionOngoing = MutableStateFlow(false) + val isRemoteUserInputOngoing = MutableStateFlow(false) + + /** Whether there's ongoing user input on the scene container Composable hierarchy */ + val isSceneContainerUserInputOngoing = MutableStateFlow(false) private val defaultTransitionState = ObservableTransitionState.Idle(config.initialSceneKey) private val _transitionState = MutableStateFlow<Flow<ObservableTransitionState>?>(null) diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt index 1b9c346129c6..4c404e29018d 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt @@ -148,11 +148,11 @@ constructor( val isVisible: StateFlow<Boolean> = combine( repository.isVisible, - repository.isRemoteUserInteractionOngoing, + repository.isRemoteUserInputOngoing, ) { isVisible, isRemoteUserInteractionOngoing -> isVisibleInternal( raw = isVisible, - isRemoteUserInteractionOngoing = isRemoteUserInteractionOngoing, + isRemoteUserInputOngoing = isRemoteUserInteractionOngoing, ) } .stateIn( @@ -162,8 +162,13 @@ constructor( ) /** Whether there's an ongoing remotely-initiated user interaction. */ - val isRemoteUserInteractionOngoing: StateFlow<Boolean> = - repository.isRemoteUserInteractionOngoing + val isRemoteUserInteractionOngoing: StateFlow<Boolean> = repository.isRemoteUserInputOngoing + + /** + * Whether there's an ongoing user interaction started in the scene container Compose hierarchy. + */ + val isSceneContainerUserInputOngoing: StateFlow<Boolean> = + repository.isSceneContainerUserInputOngoing /** * The amount of transition into or out of the given [scene]. @@ -284,7 +289,7 @@ constructor( * Please do not call this from outside of the scene framework. If you are trying to force the * visibility to visible or invisible, prefer making changes to the existing caller of this * method or to upstream state used to calculate [isVisible]; for an example of the latter, - * please see [onRemoteUserInteractionStarted] and [onUserInteractionFinished]. + * please see [onRemoteUserInputStarted] and [onUserInputFinished]. */ fun setVisible(isVisible: Boolean, loggingReason: String) { val wasVisible = repository.isVisible.value @@ -301,6 +306,16 @@ constructor( } /** + * Notifies that a scene container user interaction has begun. + * + * This is a user interaction that originates within the Composable hierarchy of the scene + * container. + */ + fun onSceneContainerUserInputStarted() { + repository.isSceneContainerUserInputOngoing.value = true + } + + /** * Notifies that a remote user interaction has begun. * * This is a user interaction that originates outside of the UI of the scene container and @@ -311,18 +326,19 @@ constructor( * then rerouted by window manager to System UI. While the user interaction definitely continues * within the System UI process and code, it also originates remotely. */ - fun onRemoteUserInteractionStarted(loggingReason: String) { - logger.logRemoteUserInteractionStarted(loggingReason) - repository.isRemoteUserInteractionOngoing.value = true + fun onRemoteUserInputStarted(loggingReason: String) { + logger.logRemoteUserInputStarted(loggingReason) + repository.isRemoteUserInputOngoing.value = true } /** * Notifies that the current user interaction (internally or remotely started, see - * [onRemoteUserInteractionStarted]) has finished. + * [onSceneContainerUserInputStarted] and [onRemoteUserInputStarted]) has finished. */ - fun onUserInteractionFinished() { - logger.logUserInteractionFinished() - repository.isRemoteUserInteractionOngoing.value = false + fun onUserInputFinished() { + logger.logUserInputFinished() + repository.isSceneContainerUserInputOngoing.value = false + repository.isRemoteUserInputOngoing.value = false } /** @@ -351,9 +367,9 @@ constructor( private fun isVisibleInternal( raw: Boolean = repository.isVisible.value, - isRemoteUserInteractionOngoing: Boolean = repository.isRemoteUserInteractionOngoing.value, + isRemoteUserInputOngoing: Boolean = repository.isRemoteUserInputOngoing.value, ): Boolean { - return raw || isRemoteUserInteractionOngoing + return raw || isRemoteUserInputOngoing } /** diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/StatusBarStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/StatusBarStartable.kt index 893f030fab4d..d7413687eeae 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/StatusBarStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/StatusBarStartable.kt @@ -166,7 +166,7 @@ constructor( StatusBarManager.DISABLE_NONE, disableToken, applicationContext.packageName, - selectedUserInteractor.getSelectedUserId(true), + selectedUserInteractor.getSelectedUserId(), ) } catch (e: RemoteException) { Log.d(TAG, "Failed to clear flags", e) diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt index 94c94e22a10b..045a8879f572 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt @@ -115,7 +115,7 @@ class SceneLogger @Inject constructor(@SceneFrameworkLog private val logBuffer: ) } - fun logRemoteUserInteractionStarted( + fun logRemoteUserInputStarted( reason: String, ) { logBuffer.log( @@ -126,7 +126,7 @@ class SceneLogger @Inject constructor(@SceneFrameworkLog private val logBuffer: ) } - fun logUserInteractionFinished() { + fun logUserInputFinished() { logBuffer.log( tag = TAG, level = LogLevel.INFO, diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt index 9dfb7450fd3f..8b4b77f83218 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt @@ -31,6 +31,7 @@ import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.logger.SceneLogger import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -97,7 +98,9 @@ constructor( } /** - * Notifies that a [MotionEvent] is first seen at the top of the scene container UI. + * Notifies that a [MotionEvent] is first seen at the top of the scene container UI. This + * includes gestures on [SharedNotificationContainer] as well as the Composable scene container + * hierarchy. * * Call this before the [MotionEvent] starts to propagate through the UI hierarchy. */ @@ -108,11 +111,21 @@ constructor( event.actionMasked == MotionEvent.ACTION_UP || event.actionMasked == MotionEvent.ACTION_CANCEL ) { - sceneInteractor.onUserInteractionFinished() + sceneInteractor.onUserInputFinished() } } /** + * Notifies that a scene container user interaction has begun. + * + * This is a user interaction that has reached the Composable hierarchy of the scene container, + * rather than being handled by [SharedNotificationContainer]. + */ + fun onSceneContainerUserInputStarted() { + sceneInteractor.onSceneContainerUserInputStarted() + } + + /** * Notifies that a [MotionEvent] that was previously sent to [onMotionEvent] has passed through * the scene container UI. * diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt index 0a1f649691a1..ed590c37c384 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt @@ -36,6 +36,13 @@ import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.flags.Flags import com.android.systemui.util.Assert +import java.io.PrintWriter +import java.lang.ref.WeakReference +import java.util.concurrent.CountDownLatch +import java.util.concurrent.Executor +import javax.inject.Provider +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job @@ -44,30 +51,23 @@ import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex -import java.io.PrintWriter -import java.lang.ref.WeakReference -import java.util.concurrent.CountDownLatch -import java.util.concurrent.Executor -import javax.inject.Provider -import kotlin.properties.ReadWriteProperty -import kotlin.reflect.KProperty /** * SystemUI cache for keeping track of the current user and associated values. * - * The values provided asynchronously are NOT copies, but shared among all requesters. Do not - * modify them. + * The values provided asynchronously are NOT copies, but shared among all requesters. Do not modify + * them. * * This class purposefully doesn't use [BroadcastDispatcher] in order to receive the broadcast as - * soon as possible (and reduce its dependency graph). - * Other classes that want to listen to the broadcasts listened here SHOULD - * subscribe to this class instead. + * soon as possible (and reduce its dependency graph). Other classes that want to listen to the + * broadcasts listened here SHOULD subscribe to this class instead. * * @see UserTracker * * Class constructed and initialized in [SettingsModule]. */ -open class UserTrackerImpl internal constructor( +open class UserTrackerImpl +internal constructor( private val context: Context, private val featureFlagsProvider: Provider<FeatureFlagsClassic>, private val userManager: UserManager, @@ -87,8 +87,8 @@ open class UserTrackerImpl internal constructor( private set private val mutex = Any() - private val isBackgroundUserSwitchEnabled: Boolean get() = - featureFlagsProvider.get().isEnabled(Flags.USER_TRACKER_BACKGROUND_CALLBACKS) + private val isBackgroundUserSwitchEnabled: Boolean + get() = featureFlagsProvider.get().isEnabled(Flags.USER_TRACKER_BACKGROUND_CALLBACKS) @Deprecated("Use UserInteractor.getSelectedUserId()") override var userId: Int by SynchronizedDelegate(context.userId) @@ -118,8 +118,7 @@ open class UserTrackerImpl internal constructor( override var userProfiles: List<UserInfo> by SynchronizedDelegate(emptyList()) protected set - @GuardedBy("callbacks") - private val callbacks: MutableList<DataItem> = ArrayList() + @GuardedBy("callbacks") private val callbacks: MutableList<DataItem> = ArrayList() private var userSwitchingJob: Job? = null private var afterUserSwitchingJob: Job? = null @@ -128,23 +127,25 @@ open class UserTrackerImpl internal constructor( if (initialized) { return } + Log.i(TAG, "Starting user: $startingUser") initialized = true setUserIdInternal(startingUser) - val filter = IntentFilter().apply { - addAction(Intent.ACTION_LOCALE_CHANGED) - addAction(Intent.ACTION_USER_INFO_CHANGED) - addAction(Intent.ACTION_PROFILE_ADDED) - addAction(Intent.ACTION_PROFILE_REMOVED) - addAction(Intent.ACTION_PROFILE_AVAILABLE) - addAction(Intent.ACTION_PROFILE_UNAVAILABLE) - // These get called when a managed profile goes in or out of quiet mode. - addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE) - addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE) - addAction(Intent.ACTION_MANAGED_PROFILE_ADDED) - addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED) - addAction(Intent.ACTION_MANAGED_PROFILE_UNLOCKED) - } + val filter = + IntentFilter().apply { + addAction(Intent.ACTION_LOCALE_CHANGED) + addAction(Intent.ACTION_USER_INFO_CHANGED) + addAction(Intent.ACTION_PROFILE_ADDED) + addAction(Intent.ACTION_PROFILE_REMOVED) + addAction(Intent.ACTION_PROFILE_AVAILABLE) + addAction(Intent.ACTION_PROFILE_UNAVAILABLE) + // These get called when a managed profile goes in or out of quiet mode. + addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE) + addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE) + addAction(Intent.ACTION_MANAGED_PROFILE_ADDED) + addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED) + addAction(Intent.ACTION_MANAGED_PROFILE_UNLOCKED) + } context.registerReceiverForAllUsers(this, filter, null, backgroundHandler) registerUserSwitchObserver() @@ -191,36 +192,39 @@ open class UserTrackerImpl internal constructor( } private fun registerUserSwitchObserver() { - iActivityManager.registerUserSwitchObserver(object : UserSwitchObserver() { - override fun onBeforeUserSwitching(newUserId: Int) { - handleBeforeUserSwitching(newUserId) - } + iActivityManager.registerUserSwitchObserver( + object : UserSwitchObserver() { + override fun onBeforeUserSwitching(newUserId: Int) { + handleBeforeUserSwitching(newUserId) + } - override fun onUserSwitching(newUserId: Int, reply: IRemoteCallback?) { - if (isBackgroundUserSwitchEnabled) { - userSwitchingJob?.cancel() - userSwitchingJob = appScope.launch(backgroundContext) { - handleUserSwitchingCoroutines(newUserId) { - reply?.sendResult(null) - } + override fun onUserSwitching(newUserId: Int, reply: IRemoteCallback?) { + if (isBackgroundUserSwitchEnabled) { + userSwitchingJob?.cancel() + userSwitchingJob = + appScope.launch(backgroundContext) { + handleUserSwitchingCoroutines(newUserId) { reply?.sendResult(null) } + } + } else { + handleUserSwitching(newUserId) + reply?.sendResult(null) } - } else { - handleUserSwitching(newUserId) - reply?.sendResult(null) } - } - override fun onUserSwitchComplete(newUserId: Int) { - if (isBackgroundUserSwitchEnabled) { - afterUserSwitchingJob?.cancel() - afterUserSwitchingJob = appScope.launch(backgroundContext) { + override fun onUserSwitchComplete(newUserId: Int) { + if (isBackgroundUserSwitchEnabled) { + afterUserSwitchingJob?.cancel() + afterUserSwitchingJob = + appScope.launch(backgroundContext) { + handleUserSwitchComplete(newUserId) + } + } else { handleUserSwitchComplete(newUserId) } - } else { - handleUserSwitchComplete(newUserId) } - } - }, TAG) + }, + TAG + ) } @WorkerThread @@ -228,9 +232,10 @@ open class UserTrackerImpl internal constructor( setUserIdInternal(newUserId) notifySubscribers { callback, resultCallback -> - callback.onBeforeUserSwitching(newUserId) - resultCallback.run() - }.await() + callback.onBeforeUserSwitching(newUserId) + resultCallback.run() + } + .await() } @WorkerThread @@ -239,31 +244,34 @@ open class UserTrackerImpl internal constructor( Log.i(TAG, "Switching to user $newUserId") notifySubscribers { callback, resultCallback -> - callback.onUserChanging(newUserId, userContext, resultCallback) - }.await() + callback.onUserChanging(newUserId, userContext, resultCallback) + } + .await() } @WorkerThread protected open suspend fun handleUserSwitchingCoroutines(newUserId: Int, onDone: () -> Unit) = - coroutineScope { - Assert.isNotMainThread() - Log.i(TAG, "Switching to user $newUserId") + coroutineScope { + Assert.isNotMainThread() + Log.i(TAG, "Switching to user $newUserId") - for (callbackDataItem in synchronized(callbacks) { callbacks.toList() }) { - val callback: UserTracker.Callback = callbackDataItem.callback.get() ?: continue - launch(callbackDataItem.executor.asCoroutineDispatcher()) { + for (callbackDataItem in synchronized(callbacks) { callbacks.toList() }) { + val callback: UserTracker.Callback = callbackDataItem.callback.get() ?: continue + launch(callbackDataItem.executor.asCoroutineDispatcher()) { val mutex = Mutex(true) - val thresholdLogJob = launch(backgroundContext) { - delay(USER_CHANGE_THRESHOLD) - Log.e(TAG, "Failed to finish $callback in time") - } + val thresholdLogJob = + launch(backgroundContext) { + delay(USER_CHANGE_THRESHOLD) + Log.e(TAG, "Failed to finish $callback in time") + } callback.onUserChanging(userId, userContext) { mutex.unlock() } mutex.lock() thresholdLogJob.cancel() - }.join() - } - onDone() + } + .join() } + onDone() + } @WorkerThread protected open fun handleUserSwitchComplete(newUserId: Int) { @@ -284,36 +292,26 @@ open class UserTrackerImpl internal constructor( synchronized(mutex) { userProfiles = profiles.map { UserInfo(it) } // save a "deep" copy } - notifySubscribers { callback, _ -> - callback.onProfilesChanged(profiles) - } + notifySubscribers { callback, _ -> callback.onProfilesChanged(profiles) } } override fun addCallback(callback: UserTracker.Callback, executor: Executor) { - synchronized(callbacks) { - callbacks.add(DataItem(WeakReference(callback), executor)) - } + synchronized(callbacks) { callbacks.add(DataItem(WeakReference(callback), executor)) } } override fun removeCallback(callback: UserTracker.Callback) { - synchronized(callbacks) { - callbacks.removeIf { it.sameOrEmpty(callback) } - } + synchronized(callbacks) { callbacks.removeIf { it.sameOrEmpty(callback) } } } private inline fun notifySubscribers( - crossinline action: (UserTracker.Callback, resultCallback: Runnable) -> Unit + crossinline action: (UserTracker.Callback, resultCallback: Runnable) -> Unit ): CountDownLatch { - val list = synchronized(callbacks) { - callbacks.toList() - } + val list = synchronized(callbacks) { callbacks.toList() } val latch = CountDownLatch(list.size) list.forEach { val callback = it.callback.get() if (callback != null) { - it.executor.execute { - action(callback) { latch.countDown() } - } + it.executor.execute { action(callback) { latch.countDown() } } } else { latch.countDown() } @@ -328,20 +326,13 @@ open class UserTrackerImpl internal constructor( val ids = userProfiles.map { it.toFullString() } pw.println("userProfiles: $ids") } - val list = synchronized(callbacks) { - callbacks.toList() - } + val list = synchronized(callbacks) { callbacks.toList() } pw.println("Callbacks:") - list.forEach { - it.callback.get()?.let { - pw.println(" $it") - } - } + list.forEach { it.callback.get()?.let { pw.println(" $it") } } } - private class SynchronizedDelegate<T : Any>( - private var value: T - ) : ReadWriteProperty<UserTrackerImpl, T> { + private class SynchronizedDelegate<T : Any>(private var value: T) : + ReadWriteProperty<UserTrackerImpl, T> { @GuardedBy("mutex") override fun getValue(thisRef: UserTrackerImpl, property: KProperty<*>): T { diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModel.kt index 2f9848863059..f270e821840a 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModel.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModel.kt @@ -17,14 +17,15 @@ package com.android.systemui.shade.ui.viewmodel import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING +import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED -import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf +import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.util.kotlin.BooleanFlowOperators.any import javax.inject.Inject import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map /** Models UI state for the shade window. */ @SysUISingleton @@ -32,11 +33,38 @@ class NotificationShadeWindowModel @Inject constructor( keyguardTransitionInteractor: KeyguardTransitionInteractor, - keyguardInteractor: KeyguardInteractor, ) { + /** + * Considered to be occluded if in OCCLUDED, DREAMING, GLANCEABLE_HUB/Communal, or transitioning + * between those states. Every permutation is listed so we can use optimal flows and support + * Scenes. + */ val isKeyguardOccluded: Flow<Boolean> = - anyOf( - keyguardTransitionInteractor.transitionValue(OCCLUDED).map { it == 1f }, - keyguardTransitionInteractor.transitionValue(DREAMING).map { it == 1f }, - ) + listOf( + // Finished in state... + keyguardTransitionInteractor.isFinishedIn(OCCLUDED), + keyguardTransitionInteractor.isFinishedIn(DREAMING), + keyguardTransitionInteractor.isFinishedIn(Scenes.Communal, GLANCEABLE_HUB), + + // ... or transitions between those states + keyguardTransitionInteractor.isInTransition(Edge.create(OCCLUDED, DREAMING)), + keyguardTransitionInteractor.isInTransition(Edge.create(DREAMING, OCCLUDED)), + keyguardTransitionInteractor.isInTransition( + edge = Edge.create(from = OCCLUDED, to = Scenes.Communal), + edgeWithoutSceneContainer = Edge.create(from = OCCLUDED, to = GLANCEABLE_HUB), + ), + keyguardTransitionInteractor.isInTransition( + edge = Edge.create(from = Scenes.Communal, to = OCCLUDED), + edgeWithoutSceneContainer = Edge.create(from = GLANCEABLE_HUB, to = OCCLUDED), + ), + keyguardTransitionInteractor.isInTransition( + edge = Edge.create(from = DREAMING, to = Scenes.Communal), + edgeWithoutSceneContainer = Edge.create(from = DREAMING, to = GLANCEABLE_HUB), + ), + keyguardTransitionInteractor.isInTransition( + edge = Edge.create(from = Scenes.Communal, to = DREAMING), + edgeWithoutSceneContainer = Edge.create(from = GLANCEABLE_HUB, to = DREAMING), + ), + ) + .any() } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index d0c51bc28126..bf00a39c42ff 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -1255,6 +1255,11 @@ public class NotificationStackScrollLayout } @Override + public void closeGutsOnSceneTouch() { + mController.closeControlsDueToOutsideTouch(); + } + + @Override public void setSyntheticScrollConsumer(@Nullable Consumer<Float> consumer) { mScrollViewFields.setSyntheticScrollConsumer(consumer); } @@ -1265,6 +1270,11 @@ public class NotificationStackScrollLayout } @Override + public void setCurrentGestureInGutsConsumer(@Nullable Consumer<Boolean> consumer) { + mScrollViewFields.setCurrentGestureInGutsConsumer(consumer); + } + + @Override public void setHeadsUpHeightConsumer(@Nullable Consumer<Float> consumer) { mScrollViewFields.setHeadsUpHeightConsumer(consumer); } @@ -3548,33 +3558,41 @@ public class NotificationStackScrollLayout @Override public boolean dispatchTouchEvent(MotionEvent ev) { - if (SceneContainerFlag.isEnabled() && mIsBeingDragged) { + if (SceneContainerFlag.isEnabled()) { int action = ev.getActionMasked(); - boolean isUpOrCancel = action == ACTION_UP || action == ACTION_CANCEL; - if (mSendingTouchesToSceneFramework) { - MotionEvent adjustedEvent = MotionEvent.obtain(ev); - adjustedEvent.setLocation(ev.getRawX(), ev.getRawY()); - mController.sendTouchToSceneFramework(adjustedEvent); - mScrollViewFields.sendCurrentGestureOverscroll( - getExpandedInThisMotion() && !isUpOrCancel); - adjustedEvent.recycle(); - } else if (!isUpOrCancel) { - // if this is the first touch being sent to the scene framework, - // convert it into a synthetic DOWN event. - mSendingTouchesToSceneFramework = true; - MotionEvent downEvent = MotionEvent.obtain(ev); - downEvent.setAction(MotionEvent.ACTION_DOWN); - downEvent.setLocation(ev.getRawX(), ev.getRawY()); - mController.sendTouchToSceneFramework(downEvent); - mScrollViewFields.sendCurrentGestureOverscroll(getExpandedInThisMotion()); - downEvent.recycle(); - } - - if (isUpOrCancel) { - mScrollViewFields.sendCurrentGestureOverscroll(false); - setIsBeingDragged(false); + boolean isTouchInGuts = mController.isTouchInGutsView(ev); + if (action == MotionEvent.ACTION_DOWN && !isTouchInGuts) { + mController.closeControlsDueToOutsideTouch(); + } + if (mIsBeingDragged) { + boolean isUpOrCancel = action == ACTION_UP || action == ACTION_CANCEL; + if (mSendingTouchesToSceneFramework) { + MotionEvent adjustedEvent = MotionEvent.obtain(ev); + adjustedEvent.setLocation(ev.getRawX(), ev.getRawY()); + mScrollViewFields.sendCurrentGestureOverscroll( + getExpandedInThisMotion() && !isUpOrCancel); + mController.sendTouchToSceneFramework(adjustedEvent); + adjustedEvent.recycle(); + } else if (!isUpOrCancel) { + // if this is the first touch being sent to the scene framework, + // convert it into a synthetic DOWN event. + mSendingTouchesToSceneFramework = true; + MotionEvent downEvent = MotionEvent.obtain(ev); + downEvent.setAction(MotionEvent.ACTION_DOWN); + downEvent.setLocation(ev.getRawX(), ev.getRawY()); + mScrollViewFields.sendCurrentGestureInGuts(isTouchInGuts); + mScrollViewFields.sendCurrentGestureOverscroll(getExpandedInThisMotion()); + mController.sendTouchToSceneFramework(downEvent); + downEvent.recycle(); + } + + if (isUpOrCancel) { + mScrollViewFields.sendCurrentGestureInGuts(false); + mScrollViewFields.sendCurrentGestureOverscroll(false); + setIsBeingDragged(false); + } + return false; } - return false; } return TouchLogger.logDispatchTouch(TAG, ev, super.dispatchTouchEvent(ev)); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index c25b30db7754..4e73529b911d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -1690,7 +1690,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { mVisibilityProvider.obtain(entry, true)); } - public void closeControlsIfOutsideTouch(MotionEvent ev) { + private View getGutsView() { NotificationGuts guts = mNotificationGutsManager.getExposedGuts(); NotificationMenuRowPlugin menuRow = mSwipeHelper.getCurrentMenuRow(); View translatingParentView = mSwipeHelper.getTranslatingParentView(); @@ -1703,15 +1703,35 @@ public class NotificationStackScrollLayoutController implements Dumpable { // Checking menu view = translatingParentView; } + return view; + } + + public void closeControlsIfOutsideTouch(MotionEvent ev) { + SceneContainerFlag.assertInLegacyMode(); + View view = getGutsView(); if (view != null && !NotificationSwipeHelper.isTouchInView(ev, view)) { // Touch was outside visible guts / menu notification, close what's visible - mNotificationGutsManager.closeAndSaveGuts(false /* removeLeavebehind */, - false /* force */, true /* removeControls */, -1 /* x */, -1 /* y */, - false /* resetMenu */); - mSwipeHelper.resetExposedMenuView(true /* animate */, true /* force */); + closeAndSaveGuts(); } } + void closeControlsDueToOutsideTouch() { + if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return; + closeAndSaveGuts(); + } + + private void closeAndSaveGuts() { + mNotificationGutsManager.closeAndSaveGuts(false /* removeLeavebehind */, + false /* force */, true /* removeControls */, -1 /* x */, -1 /* y */, + false /* resetMenu */); + mSwipeHelper.resetExposedMenuView(true /* animate */, true /* force */); + } + + boolean isTouchInGutsView(MotionEvent event) { + View view = getGutsView(); + return NotificationSwipeHelper.isTouchInView(event, view); + } + public void clearSilentNotifications() { FooterViewRefactor.assertInLegacyMode(); // Leave the shade open if there will be other notifs left over to clear diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt index 383d8b3b26b5..aa3953987c10 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt @@ -53,6 +53,11 @@ class ScrollViewFields { */ var currentGestureOverscrollConsumer: Consumer<Boolean>? = null /** + * When a gesture is on open notification guts, which means scene container should not close the + * guts off of this gesture, we can notify the placeholder through here. + */ + var currentGestureInGutsConsumer: Consumer<Boolean>? = null + /** * Any time the heads up height is recalculated, it should be updated here to be used by the * placeholder */ @@ -66,6 +71,10 @@ class ScrollViewFields { fun sendCurrentGestureOverscroll(isCurrentGestureOverscroll: Boolean) = currentGestureOverscrollConsumer?.accept(isCurrentGestureOverscroll) + /** send [isCurrentGestureInGuts] to the [currentGestureInGutsConsumer], if present. */ + fun sendCurrentGestureInGuts(isCurrentGestureInGuts: Boolean) = + currentGestureInGutsConsumer?.accept(isCurrentGestureInGuts) + /** send the [headsUpHeight] to the [headsUpHeightConsumer], if present. */ fun sendHeadsUpHeight(headsUpHeight: Float) = headsUpHeightConsumer?.accept(headsUpHeight) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationViewHeightRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationViewHeightRepository.kt index f6d9351952f1..4907d444070d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationViewHeightRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationViewHeightRepository.kt @@ -39,4 +39,7 @@ class NotificationViewHeightRepository @Inject constructor() { * consumed part of the gesture. */ val isCurrentGestureOverscroll = MutableStateFlow(false) + + /** Whether the current touch gesture is on any open notification guts. */ + val isCurrentGestureInGuts = MutableStateFlow(false) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt index 8557afc6ebd3..756cd87970a4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.stack.domain.interactor import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shade.shared.model.ShadeMode import com.android.systemui.statusbar.notification.stack.data.repository.NotificationPlaceholderRepository @@ -39,6 +40,7 @@ class NotificationStackAppearanceInteractor constructor( private val viewHeightRepository: NotificationViewHeightRepository, private val placeholderRepository: NotificationPlaceholderRepository, + sceneInteractor: SceneInteractor, shadeInteractor: ShadeInteractor, ) { /** The bounds of the notification stack in the current scene. */ @@ -93,6 +95,15 @@ constructor( val isCurrentGestureOverscroll: Flow<Boolean> = viewHeightRepository.isCurrentGestureOverscroll.asStateFlow() + /** Whether we should close any notification guts that are currently open. */ + val shouldCloseGuts: Flow<Boolean> = + combine( + sceneInteractor.isSceneContainerUserInputOngoing, + viewHeightRepository.isCurrentGestureInGuts + ) { isUserInputOngoing, isCurrentGestureInGuts -> + isUserInputOngoing && !isCurrentGestureInGuts + } + /** Sets the alpha to apply to the NSSL for the brightness mirror */ fun setAlphaForBrightnessMirror(alpha: Float) { placeholderRepository.alphaForBrightnessMirror.value = alpha @@ -119,6 +130,10 @@ constructor( viewHeightRepository.isCurrentGestureOverscroll.value = isOverscroll } + fun setCurrentGestureInGuts(isInGuts: Boolean) { + viewHeightRepository.isCurrentGestureInGuts.value = isInGuts + } + fun setConstrainedAvailableSpace(height: Int) { placeholderRepository.constrainedAvailableSpace.value = height } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt index 1289cec3a282..235b4da3f029 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt @@ -71,6 +71,9 @@ interface NotificationScrollView { /** Set a consumer for current gesture overscroll events */ fun setCurrentGestureOverscrollConsumer(consumer: Consumer<Boolean>?) + /** Set a consumer for current gesture in guts events */ + fun setCurrentGestureInGutsConsumer(consumer: Consumer<Boolean>?) + /** Set a consumer for heads up height changed events */ fun setHeadsUpHeightConsumer(consumer: Consumer<Float>?) @@ -92,6 +95,12 @@ interface NotificationScrollView { /** Gets the inset for HUNs when they are not visible */ fun getHeadsUpInset(): Int + /** + * Signals that any open Notification guts should be closed, as scene container is handling + * touch events. + */ + fun closeGutsOnSceneTouch() + /** Adds a listener to be notified, when the stack height might have changed. */ fun addStackHeightChangedListener(runnable: Runnable) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt index c044f6f6a9b1..3cc6e81986c5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt @@ -36,6 +36,7 @@ import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch /** Binds the [NotificationScrollView]. */ @@ -98,13 +99,18 @@ constructor( .filter { it } .collect { view.setStackTop(-(view.getHeadsUpInset().toFloat())) } } + launch { + viewModel.shouldCloseGuts.filter { it }.collect { view.closeGutsOnSceneTouch() } + } launchAndDispose { view.setSyntheticScrollConsumer(viewModel.syntheticScrollConsumer) view.setCurrentGestureOverscrollConsumer(viewModel.currentGestureOverscrollConsumer) + view.setCurrentGestureInGutsConsumer(viewModel.currentGestureInGutsConsumer) DisposableHandle { view.setSyntheticScrollConsumer(null) view.setCurrentGestureOverscrollConsumer(null) + view.setCurrentGestureInGutsConsumer(null) } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt index b2045fe7569a..3999578b83e3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt @@ -136,6 +136,9 @@ constructor( val qsExpandFraction: Flow<Float> = shadeInteractor.qsExpansion.dumpWhileCollecting("qsExpandFraction") + /** Whether we should close any open notification guts. */ + val shouldCloseGuts: Flow<Boolean> = stackAppearanceInteractor.shouldCloseGuts + val shouldResetStackTop: Flow<Boolean> = sceneInteractor.transitionState .mapNotNull { state -> state is Idle && state.currentScene == Scenes.Gone } @@ -202,6 +205,10 @@ constructor( val currentGestureOverscrollConsumer: (Boolean) -> Unit = stackAppearanceInteractor::setCurrentGestureOverscroll + /** Receives whether the current touch gesture is inside any open guts. */ + val currentGestureInGutsConsumer: (Boolean) -> Unit = + stackAppearanceInteractor::setCurrentGestureInGuts + /** Whether the notification stack is scrollable or not. */ val isScrollable: Flow<Boolean> = sceneInteractor.currentScene diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt index 59c819d41493..cd32718dbe82 100644 --- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt @@ -3,8 +3,6 @@ package com.android.systemui.user.domain.interactor import android.annotation.UserIdInt import android.content.pm.UserInfo import android.os.UserManager -import com.android.keyguard.KeyguardUpdateMonitor -import com.android.systemui.Flags.refactorGetCurrentUser import com.android.systemui.dagger.SysUISingleton import com.android.systemui.user.data.repository.UserRepository import javax.inject.Inject @@ -21,23 +19,11 @@ class SelectedUserInteractor @Inject constructor(private val repository: UserRep /** Flow providing the [UserInfo] of the currently selected user. */ val selectedUserInfo = repository.selectedUserInfo - /** - * Returns the ID of the currently-selected user. - * - * @param bypassFlag this will ignore the feature flag and get the data from the repository - * instead. This is used for refactored methods that were previously pointing to `userTracker` - * and therefore should not be routed back to KeyguardUpdateMonitor when flag is disabled. - * KeyguardUpdateMonitor.getCurrentUser() is deprecated and will be removed soon (together - * with this flag). - */ + /** Returns the ID of the currently-selected user. */ @UserIdInt @JvmOverloads - fun getSelectedUserId(bypassFlag: Boolean = false): Int { - return if (bypassFlag || refactorGetCurrentUser()) { - repository.getSelectedUserInfo().id - } else { - KeyguardUpdateMonitor.getCurrentUser() - } + fun getSelectedUserId(): Int { + return repository.getSelectedUserInfo().id } /** diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 7aa415b64316..52fde7ed72c9 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -354,7 +354,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { ExtendedMockito.doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID) .when(SubscriptionManager::getDefaultSubscriptionId); when(mSelectedUserInteractor.getSelectedUserId()).thenReturn(mCurrentUserId); - when(mSelectedUserInteractor.getSelectedUserId(anyBoolean())).thenReturn(mCurrentUserId); mContext.getOrCreateTestableResources().addOverride( com.android.systemui.res.R.integer.config_face_auth_supported_posture, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index 37f1a3d73b0c..597ffef20ace 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -26,7 +26,6 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STR import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; import static com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR; -import static com.android.systemui.Flags.FLAG_REFACTOR_GET_CURRENT_USER; import static com.android.systemui.keyguard.KeyguardViewMediator.DELAYED_KEYGUARD_ACTION; import static com.android.systemui.keyguard.KeyguardViewMediator.KEYGUARD_LOCK_AFTER_DELAY_DEFAULT; import static com.android.systemui.keyguard.KeyguardViewMediator.REBOOT_MAINLINE_UPDATE; @@ -250,7 +249,6 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { when(mCommunalTransitionViewModel.getTransitionFromOccludedEnded()) .thenReturn(mock(Flow.class)); when(mSelectedUserInteractor.getSelectedUserId()).thenReturn(mDefaultUserId); - when(mSelectedUserInteractor.getSelectedUserId(anyBoolean())).thenReturn(mDefaultUserId); when(mProcessWrapper.isSystemUser()).thenReturn(true); mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl( mContext, @@ -275,7 +273,6 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { mKosmos.getNotificationShadeWindowModel(), mKosmos::getCommunalInteractor); mFeatureFlags = new FakeFeatureFlags(); - mSetFlagsRule.enableFlags(FLAG_REFACTOR_GET_CURRENT_USER); mSetFlagsRule.disableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR); DejankUtils.setImmediate(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerTest.kt index 230ddf9d25db..48c2cc7f11c4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerTest.kt @@ -56,11 +56,11 @@ class StatusBarTouchableRegionManagerTest : SysuiTestCase() { runCurrent() assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse() - sceneRepository.isRemoteUserInteractionOngoing.value = true + sceneRepository.isRemoteUserInputOngoing.value = true runCurrent() assertThat(underTest.shouldMakeEntireScreenTouchable()).isTrue() - sceneRepository.isRemoteUserInteractionOngoing.value = false + sceneRepository.isRemoteUserInputOngoing.value = false runCurrent() assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse() } @@ -71,7 +71,7 @@ class StatusBarTouchableRegionManagerTest : SysuiTestCase() { testScope.runTest { assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse() - sceneRepository.isRemoteUserInteractionOngoing.value = true + sceneRepository.isRemoteUserInputOngoing.value = true runCurrent() assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse() diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt index 78028f819fa0..26f6f3131585 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt @@ -3,7 +3,6 @@ package com.android.systemui.user.domain.interactor import android.content.pm.UserInfo import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.systemui.Flags.FLAG_REFACTOR_GET_CURRENT_USER import com.android.systemui.SysuiTestCase import com.android.systemui.user.data.repository.FakeUserRepository import com.google.common.truth.Truth.assertThat @@ -28,7 +27,6 @@ class SelectedUserInteractorTest : SysuiTestCase() { @Test fun getSelectedUserIdReturnsId() { - mSetFlagsRule.enableFlags(FLAG_REFACTOR_GET_CURRENT_USER) runBlocking { userRepository.setSelectedUserInfo(USER_INFOS[0]) } val actualId = underTest.getSelectedUserId() diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeWindowModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeWindowModelKosmos.kt index 6252d4498a5e..4b42e07f1f54 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeWindowModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeWindowModelKosmos.kt @@ -16,7 +16,6 @@ package com.android.systemui.shade.ui.viewmodel -import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.kosmos.Kosmos @@ -24,6 +23,5 @@ val Kosmos.notificationShadeWindowModel: NotificationShadeWindowModel by Kosmos.Fixture { NotificationShadeWindowModel( keyguardTransitionInteractor, - keyguardInteractor, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorKosmos.kt index dbfd9de2aa8c..2772d3698d88 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorKosmos.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.stack.domain.interactor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.statusbar.notification.stack.data.repository.notificationPlaceholderRepository import com.android.systemui.statusbar.notification.stack.data.repository.notificationViewHeightRepository @@ -26,6 +27,7 @@ val Kosmos.notificationStackAppearanceInteractor by Fixture { NotificationStackAppearanceInteractor( viewHeightRepository = notificationViewHeightRepository, placeholderRepository = notificationPlaceholderRepository, + sceneInteractor = sceneInteractor, shadeInteractor = shadeInteractor, ) } diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 19279a887d11..07e5f2e34ab8 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -3527,7 +3527,8 @@ class StorageManagerService extends IStorageManager.Stub // of the calling App final long token = Binder.clearCallingIdentity(); try { - Context targetAppContext = mContext.createPackageContext(packageName, 0); + Context targetAppContext = mContext.createPackageContextAsUser(packageName, + /* flags= */ 0, UserHandle.of(UserHandle.getUserId(originalUid))); Intent intent = new Intent(Intent.ACTION_DEFAULT); intent.setClassName(packageName, appInfo.manageSpaceActivityName); diff --git a/services/core/java/com/android/server/wm/ActionChain.java b/services/core/java/com/android/server/wm/ActionChain.java deleted file mode 100644 index c697d33ed4e2..000000000000 --- a/services/core/java/com/android/server/wm/ActionChain.java +++ /dev/null @@ -1,239 +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.server.wm; - -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.util.Slog; - -import com.android.window.flags.Flags; - -/** - * Represents a chain of WM actions where each action is "caused by" the prior action (except the - * first one of course). A whole chain is associated with one Transition (in fact, the purpose - * of this object is to communicate, to all callees, which transition they are part of). - * - * A single action is defined as "one logical thing requested of WM". This usually corresponds to - * each ingress-point into the process. For example, when starting an activity: - * * the first action is to pause the current/top activity. - * At this point, control leaves the process while the activity pauses. - * * Then WM receives completePause (a new ingress). This is a new action that gets linked - * to the prior action. This action involves resuming the next activity, at which point, - * control leaves the process again. - * * Eventually, when everything is done, we will have formed a chain of actions. - * - * We don't technically need to hold onto each prior action in the chain once a new action has - * been linked to the same transition; however, keeping the whole chain enables improved - * debugging and the ability to detect anomalies. - */ -public class ActionChain { - private static final String TAG = "TransitionChain"; - - /** - * Normal link type. This means the action was expected and is properly linked to the - * current chain. - */ - static final int TYPE_NORMAL = 0; - - /** - * This is the "default" link. It means we haven't done anything to properly track this case - * so it may or may not be correct. It represents the behavior as if there was no tracking. - * - * Any type that has "default" behavior uses the global "collecting transition" if it exists, - * otherwise it doesn't use any transition. - */ - static final int TYPE_DEFAULT = 1; - - /** - * This means the action was performed via a legacy code-path. These should be removed - * eventually. This will have the "default" behavior. - */ - static final int TYPE_LEGACY = 2; - - /** This is for a test. */ - static final int TYPE_TEST = 3; - - /** This is finishing a transition. Collection isn't supported during this. */ - static final int TYPE_FINISH = 4; - - /** - * Something unexpected happened so this action was started to recover from the unexpected - * state. This means that a "real" chain-link couldn't be determined. For now, the behavior of - * this is the same as "default". - */ - static final int TYPE_FAILSAFE = 5; - - /** - * Types of chain links (ie. how is this action associated with the chain it is linked to). - * @hide - */ - @IntDef(prefix = { "TYPE_" }, value = { - TYPE_NORMAL, - TYPE_DEFAULT, - TYPE_LEGACY, - TYPE_TEST, - TYPE_FINISH, - TYPE_FAILSAFE - }) - public @interface LinkType {} - - /** Identifies the entry-point of this action. */ - @NonNull - final String mSource; - - /** Reference to ATMS. TEMPORARY! ONLY USE THIS WHEN tracker_plumbing flag is DISABLED! */ - @Nullable - ActivityTaskManagerService mTmpAtm; - - /** The transition that this chain's changes belong to. */ - @Nullable - Transition mTransition; - - /** The previous action in the chain. */ - @Nullable - ActionChain mPrevious = null; - - /** Classification of how this action is connected to the chain. */ - @LinkType int mType = TYPE_NORMAL; - - /** When this Action started. */ - long mCreateTimeMs; - - private ActionChain(String source, @LinkType int type, Transition transit) { - mSource = source; - mCreateTimeMs = System.currentTimeMillis(); - mType = type; - mTransition = transit; - if (mTransition != null) { - mTransition.recordChain(this); - } - } - - private Transition getTransition() { - if (!Flags.transitTrackerPlumbing()) { - return mTmpAtm.getTransitionController().getCollectingTransition(); - } - return mTransition; - } - - boolean isFinishing() { - return mType == TYPE_FINISH; - } - - /** - * Some common checks to determine (and report) whether this chain has a collecting transition. - */ - private boolean expectCollecting() { - if (getTransition() == null) { - Slog.e(TAG, "Can't collect into a chain with no transition"); - return false; - } - if (isFinishing()) { - Slog.e(TAG, "Trying to collect into a finished transition"); - return false; - } - if (mTransition.mController.getCollectingTransition() != mTransition) { - Slog.e(TAG, "Mismatch between current collecting (" - + mTransition.mController.getCollectingTransition() + ") and chain (" - + mTransition + ")"); - return false; - } - return true; - } - - /** - * Helper to collect a container into the associated transition. This will automatically do - * nothing if the chain isn't associated with a collecting transition. - */ - void collect(@NonNull WindowContainer wc) { - if (!wc.mTransitionController.isShellTransitionsEnabled()) return; - if (!expectCollecting()) return; - getTransition().collect(wc); - } - - /** - * An interface for creating and tracking action chains. - */ - static class Tracker { - private final ActivityTaskManagerService mAtm; - - Tracker(ActivityTaskManagerService atm) { - mAtm = atm; - } - - private ActionChain makeChain(String source, @LinkType int type, Transition transit) { - final ActionChain out = new ActionChain(source, type, transit); - if (!Flags.transitTrackerPlumbing()) { - out.mTmpAtm = mAtm; - } - return out; - } - - private ActionChain makeChain(String source, @LinkType int type) { - return makeChain(source, type, - mAtm.getTransitionController().getCollectingTransition()); - } - - /** - * Starts tracking a normal action. - * @see #TYPE_NORMAL - */ - @NonNull - ActionChain start(String source, Transition transit) { - return makeChain(source, TYPE_NORMAL, transit); - } - - /** @see #TYPE_DEFAULT */ - @NonNull - ActionChain startDefault(String source) { - return makeChain(source, TYPE_DEFAULT); - } - - /** - * Starts tracking an action that finishes a transition. - * @see #TYPE_NORMAL - */ - @NonNull - ActionChain startFinish(String source, Transition finishTransit) { - return makeChain(source, TYPE_FINISH, finishTransit); - } - - /** @see #TYPE_LEGACY */ - @NonNull - ActionChain startLegacy(String source) { - return makeChain(source, TYPE_LEGACY, null); - } - - /** @see #TYPE_FAILSAFE */ - @NonNull - ActionChain startFailsafe(String source) { - return makeChain(source, TYPE_FAILSAFE); - } - } - - /** Helpers for usage in tests. */ - @NonNull - static ActionChain test() { - return new ActionChain("test", TYPE_TEST, null /* transition */); - } - - @NonNull - static ActionChain testFinish(Transition toFinish) { - return new ActionChain("test", TYPE_FINISH, toFinish); - } -} diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 03c7fd1e3ffe..0f108c5ed5d7 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -795,7 +795,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { WindowOrganizerController mWindowOrganizerController; TaskOrganizerController mTaskOrganizerController; TaskFragmentOrganizerController mTaskFragmentOrganizerController; - ActionChain.Tracker mChainTracker; @Nullable private BackgroundActivityStartCallback mBackgroundActivityStartCallback; @@ -870,7 +869,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { mInternal = new LocalService(); GL_ES_VERSION = SystemProperties.getInt("ro.opengles.version", GL_ES_VERSION_UNDEFINED); mWindowOrganizerController = new WindowOrganizerController(this); - mChainTracker = new ActionChain.Tracker(this); mTaskOrganizerController = mWindowOrganizerController.mTaskOrganizerController; mTaskFragmentOrganizerController = mWindowOrganizerController.mTaskFragmentOrganizerController; diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 82ede7efda5a..e25db7e2a3ec 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -181,7 +181,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { final @TransitionType int mType; private int mSyncId = -1; private @TransitionFlags int mFlags; - final TransitionController mController; + private final TransitionController mController; private final BLASTSyncEngine mSyncEngine; private final Token mToken; @@ -329,9 +329,6 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { */ ArrayList<ActivityRecord> mConfigAtEndActivities = null; - /** The current head of the chain of actions related to this transition. */ - ActionChain mChainHead = null; - @VisibleForTesting Transition(@TransitionType int type, @TransitionFlags int flags, TransitionController controller, BLASTSyncEngine syncEngine) { @@ -1210,14 +1207,10 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { * The transition has finished animating and is ready to finalize WM state. This should not * be called directly; use {@link TransitionController#finishTransition} instead. */ - void finishTransition(@NonNull ActionChain chain) { + void finishTransition() { if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER) && mIsPlayerEnabled) { asyncTraceEnd(System.identityHashCode(this)); } - if (!chain.isFinishing()) { - throw new IllegalStateException("Can't finish on a non-finishing transition " - + chain.mTransition); - } mLogger.mFinishTimeNs = SystemClock.elapsedRealtimeNanos(); mController.mLoggerHandler.post(mLogger::logOnFinish); mController.mTransitionTracer.logFinishedTransition(this); @@ -2170,7 +2163,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { if (mFinishTransaction != null) { mFinishTransaction.apply(); } - mController.finishTransition(mController.mAtm.mChainTracker.startFinish("clean-up", this)); + mController.finishTransition(this); } private void cleanUpInternal() { @@ -3386,11 +3379,6 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { return false; } - void recordChain(@NonNull ActionChain chain) { - chain.mPrevious = mChainHead; - mChainHead = chain; - } - @VisibleForTesting static class ChangeInfo { private static final int FLAG_NONE = 0; diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index 56a24dd4ca49..9bbf10243133 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -52,8 +52,8 @@ import android.window.WindowContainerTransaction; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.ProtoLog; import com.android.internal.protolog.ProtoLogGroup; +import com.android.internal.protolog.ProtoLog; import com.android.server.FgThread; import com.android.window.flags.Flags; @@ -921,12 +921,7 @@ class TransitionController { } /** @see Transition#finishTransition */ - void finishTransition(@NonNull ActionChain chain) { - if (!chain.isFinishing()) { - throw new IllegalStateException("Can't finish on a non-finishing transition " - + chain.mTransition); - } - final Transition record = chain.mTransition; + void finishTransition(Transition record) { // It is usually a no-op but make sure that the metric consumer is removed. mTransitionMetricsReporter.reportAnimationStart(record.getToken(), 0 /* startTime */); // It is a no-op if the transition did not change the display. @@ -942,7 +937,7 @@ class TransitionController { mTrackCount = 0; } updateRunningRemoteAnimation(record, false /* isPlaying */); - record.finishTransition(chain); + record.finishTransition(); for (int i = mAnimatingExitWindows.size() - 1; i >= 0; i--) { final WindowState w = mAnimatingExitWindows.get(i); if (w.mAnimatingExit && w.mHasSurface && !w.inTransition()) { diff --git a/services/core/java/com/android/server/wm/TrustedOverlayHost.java b/services/core/java/com/android/server/wm/TrustedOverlayHost.java index 9b868bebd868..5f3c5583e024 100644 --- a/services/core/java/com/android/server/wm/TrustedOverlayHost.java +++ b/services/core/java/com/android/server/wm/TrustedOverlayHost.java @@ -112,11 +112,16 @@ class TrustedOverlayHost { final SurfaceControl.Transaction t = mWmService.mTransactionFactory.get(); for (int i = mOverlays.size() - 1; i >= 0; i--) { - SurfaceControlViewHost.SurfacePackage l = mOverlays.get(i); - if (l.getSurfaceControl().isSameSurface(p.getSurfaceControl())) { - mOverlays.remove(i); - t.reparent(l.getSurfaceControl(), null); - l.release(); + SurfaceControlViewHost.SurfacePackage l = mOverlays.get(i); + SurfaceControl overlaySurfaceControl = l.getSurfaceControl(); + if (overlaySurfaceControl == null) { + // Remove the overlay if the surfacepackage was released. Ownership + // is shared, so this may happen. + mOverlays.remove(i); + } else if (overlaySurfaceControl.isSameSurface(p.getSurfaceControl())) { + mOverlays.remove(i); + t.reparent(l.getSurfaceControl(), null); + l.release(); } } t.apply(); diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 60ccdc72ee09..e1e64ee10245 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -223,8 +223,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub final long ident = Binder.clearCallingIdentity(); try { synchronized (mGlobalLock) { - final ActionChain chain = mService.mChainTracker.startLegacy("applyTransactLegacy"); - applyTransaction(t, -1 /*syncId*/, chain, caller); + applyTransaction(t, -1 /*syncId*/, null /*transition*/, caller); } } finally { Binder.restoreCallingIdentity(ident); @@ -243,8 +242,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub try { synchronized (mGlobalLock) { if (callback == null) { - final ActionChain chain = mService.mChainTracker.startLegacy("applySyncLegacy"); - applyTransaction(t, -1 /* syncId*/, chain, caller); + applyTransaction(t, -1 /* syncId*/, null /*transition*/, caller); return -1; } @@ -264,15 +262,13 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub final int syncId = syncGroup.mSyncId; if (mTransitionController.isShellTransitionsEnabled()) { mTransitionController.startLegacySyncOrQueue(syncGroup, (deferred) -> { - applyTransaction(t, syncId, mService.mChainTracker.startLegacy( - "applySyncLegacy"), caller, deferred); + applyTransaction(t, syncId, null /* transition */, caller, deferred); setSyncReady(syncId); }); } else { if (!mService.mWindowManager.mSyncEngine.hasActiveSync()) { mService.mWindowManager.mSyncEngine.startSyncSet(syncGroup); - applyTransaction(t, syncId, mService.mChainTracker.startLegacy( - "applySyncLegacy"), caller); + applyTransaction(t, syncId, null /*transition*/, caller); setSyncReady(syncId); } else { // Because the BLAST engine only supports one sync at a time, queue the @@ -280,8 +276,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub mService.mWindowManager.mSyncEngine.queueSyncSet( () -> mService.mWindowManager.mSyncEngine.startSyncSet(syncGroup), () -> { - applyTransaction(t, syncId, mService.mChainTracker.startLegacy( - "applySyncLegacy"), caller); + applyTransaction(t, syncId, null /*transition*/, caller); setSyncReady(syncId); }); } @@ -318,8 +313,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub throw new IllegalArgumentException("Can't use legacy transitions in" + " compatibility mode with no WCT."); } - applyTransaction(t, -1 /* syncId */, - mService.mChainTracker.startLegacy("wrongLegacyTransit"), caller); + applyTransaction(t, -1 /* syncId */, null, caller); return null; } final WindowContainerTransaction wct = @@ -340,11 +334,10 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub nextTransition.calcParallelCollectType(wct); mTransitionController.startCollectOrQueue(nextTransition, (deferred) -> { - final ActionChain chain = mService.mChainTracker.start( - "startNewTransit", nextTransition); nextTransition.start(); nextTransition.mLogger.mStartWCT = wct; - applyTransaction(wct, -1 /* syncId */, chain, caller, deferred); + applyTransaction(wct, -1 /* syncId */, nextTransition, caller, + deferred); wctApplied.meet(); if (needsSetReady) { setAllReadyIfNeeded(nextTransition, wct); @@ -358,9 +351,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub Slog.e(TAG, "Trying to start a transition that isn't collecting. This probably" + " means Shell took too long to respond to a request. WM State may be" + " incorrect now, please file a bug"); - final ActionChain chain = mService.mChainTracker.startFailsafe("startTransit"); - chain.mTransition = null; - applyTransaction(wct, -1 /*syncId*/, chain, caller); + applyTransaction(wct, -1 /*syncId*/, null /*transition*/, caller); return transition.getToken(); } // Currently, application of wct can span multiple looper loops (ie. @@ -376,20 +367,16 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub if (transition.shouldApplyOnDisplayThread()) { mService.mH.post(() -> { synchronized (mService.mGlobalLock) { - final ActionChain chain = mService.mChainTracker.start( - "startTransit", transition); transition.start(); - applyTransaction(wct, -1 /* syncId */, chain, caller); + applyTransaction(wct, -1 /* syncId */, transition, caller); if (wctApplied != null) { wctApplied.meet(); } } }); } else { - final ActionChain chain = mService.mChainTracker.start("startTransit", - transition); transition.start(); - applyTransaction(wct, -1 /* syncId */, chain, caller); + applyTransaction(wct, -1 /* syncId */, transition, caller); if (wctApplied != null) { wctApplied.meet(); } @@ -488,8 +475,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub dc.mAppTransition.overridePendingAppTransitionRemote(adapter, true /* sync */, false /* isActivityEmbedding */); syncId = startSyncWithOrganizer(callback); - applyTransaction(t, syncId, mService.mChainTracker.startLegacy("legacyTransit"), - caller); + applyTransaction(t, syncId, null /* transition */, caller); setSyncReady(syncId); } } finally { @@ -507,8 +493,6 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub try { synchronized (mGlobalLock) { final Transition transition = Transition.fromBinder(transitionToken); - final ActionChain chain = - mService.mChainTracker.startFinish("finishTransit", transition); // apply the incoming transaction before finish in case it alters the visibility // of the participants. if (t != null) { @@ -516,9 +500,9 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub // changes of the transition participants will only set visible-requested // and still let finishTransition handle the participants. mTransitionController.mFinishingTransition = transition; - applyTransaction(t, -1 /* syncId */, chain, caller); + applyTransaction(t, -1 /* syncId */, null /*transition*/, caller, transition); } - mTransitionController.finishTransition(chain); + mTransitionController.finishTransition(transition); mTransitionController.mFinishingTransition = null; } } finally { @@ -553,10 +537,9 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub final CallerInfo caller = new CallerInfo(); final long ident = Binder.clearCallingIdentity(); try { - if (!mTransitionController.isShellTransitionsEnabled()) { + if (mTransitionController.getTransitionPlayer() == null) { // No need to worry about transition when Shell transition is not enabled. - applyTransaction(wct, -1 /* syncId */, - mService.mChainTracker.startLegacy("legacyTFTransact"), caller); + applyTransaction(wct, -1 /* syncId */, null /* transition */, caller); return; } @@ -565,8 +548,8 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub // Although there is an active sync, we want to apply the transaction now. // TODO(b/232042367) Redesign the organizer update on activity callback so that we // we will know about the transition explicitly. - final ActionChain chain = mService.mChainTracker.startDefault("tfTransact"); - if (chain.mTransition == null) { + final Transition transition = mTransitionController.getCollectingTransition(); + if (transition == null) { // This should rarely happen, and we should try to avoid using // {@link #applySyncTransaction} with Shell transition. // We still want to apply and merge the transaction to the active sync @@ -576,7 +559,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub + " because there is an ongoing sync for" + " applySyncTransaction()."); } - applyTransaction(wct, -1 /* syncId */, chain, caller); + applyTransaction(wct, -1 /* syncId */, transition, caller); return; } @@ -587,9 +570,8 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub transition.abort(); return; } - final ActionChain chain = mService.mChainTracker.start("tfTransact", transition); - final int effects = applyTransaction(wct, -1 /* syncId */, chain, caller, deferred); - if (effects == TRANSACT_EFFECTS_NONE && transition.mParticipants.isEmpty()) { + if (applyTransaction(wct, -1 /* syncId */, transition, caller, deferred) + == TRANSACT_EFFECTS_NONE && transition.mParticipants.isEmpty()) { transition.abort(); return; } @@ -604,10 +586,15 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } private int applyTransaction(@NonNull WindowContainerTransaction t, int syncId, - @NonNull ActionChain chain, @NonNull CallerInfo caller, boolean deferred) { + @Nullable Transition transition, @NonNull CallerInfo caller) { + return applyTransaction(t, syncId, transition, caller, null /* finishTransition */); + } + + private int applyTransaction(@NonNull WindowContainerTransaction t, int syncId, + @Nullable Transition transition, @NonNull CallerInfo caller, boolean deferred) { if (deferred) { try { - return applyTransaction(t, syncId, chain, caller); + return applyTransaction(t, syncId, transition, caller); } catch (RuntimeException e) { // If the transaction is deferred, the caller could be from TransitionController // #tryStartCollectFromQueue that executes on system's worker thread rather than @@ -617,17 +604,19 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } return TRANSACT_EFFECTS_NONE; } - return applyTransaction(t, syncId, chain, caller); + return applyTransaction(t, syncId, transition, caller); } /** * @param syncId If non-null, this will be a sync-transaction. - * @param chain A lifecycle-chain to acculumate changes into. + * @param transition A transition to collect changes into. * @param caller Info about the calling process. + * @param finishTransition The transition that is currently being finished. * @return The effects of the window container transaction. */ private int applyTransaction(@NonNull WindowContainerTransaction t, int syncId, - @NonNull ActionChain chain, @NonNull CallerInfo caller) { + @Nullable Transition transition, @NonNull CallerInfo caller, + @Nullable Transition finishTransition) { int effects = TRANSACT_EFFECTS_NONE; ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Apply window transaction, syncId=%d", syncId); mService.deferWindowLayout(); @@ -635,21 +624,20 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub boolean deferResume = true; mService.mTaskSupervisor.setDeferRootVisibilityUpdate(true /* deferUpdate */); boolean deferTransitionReady = false; - if (chain.mTransition != null && !t.isEmpty() && !chain.isFinishing()) { - if (chain.mTransition.isCollecting()) { + if (transition != null && !t.isEmpty()) { + if (transition.isCollecting()) { deferTransitionReady = true; - chain.mTransition.deferTransitionReady(); + transition.deferTransitionReady(); } else { Slog.w(TAG, "Transition is not collecting when applyTransaction." - + " transition=" + chain.mTransition + " state=" - + chain.mTransition.getState()); - chain.mTransition = null; + + " transition=" + transition + " state=" + transition.getState()); + transition = null; } } try { final ArraySet<WindowContainer<?>> haveConfigChanges = new ArraySet<>(); - if (chain.mTransition != null) { - chain.mTransition.applyDisplayChangeIfNeeded(haveConfigChanges); + if (transition != null) { + transition.applyDisplayChangeIfNeeded(haveConfigChanges); if (!haveConfigChanges.isEmpty()) { effects |= TRANSACT_EFFECTS_CLIENT_CONFIG; } @@ -657,7 +645,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub final List<WindowContainerTransaction.HierarchyOp> hops = t.getHierarchyOps(); final int hopSize = hops.size(); Iterator<Map.Entry<IBinder, WindowContainerTransaction.Change>> entries; - if (chain.mTransition != null) { + if (transition != null) { // Mark any config-at-end containers before applying config changes so that // the config changes don't dispatch to client. entries = t.getChanges().entrySet().iterator(); @@ -667,7 +655,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub if (!entry.getValue().getConfigAtTransitionEnd()) continue; final WindowContainer wc = WindowContainer.fromBinder(entry.getKey()); if (wc == null || !wc.isAttached()) continue; - chain.mTransition.setConfigAtEnd(wc); + transition.setConfigAtEnd(wc); } } entries = t.getChanges().entrySet().iterator(); @@ -684,13 +672,15 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub if (syncId >= 0) { addToSyncSet(syncId, wc); } - chain.collect(wc); + if (transition != null) transition.collect(wc); if ((entry.getValue().getChangeMask() & WindowContainerTransaction.Change.CHANGE_FORCE_NO_PIP) != 0) { // Disable entering pip (eg. when recents pretends to finish itself) - if (chain.mTransition != null) { - chain.mTransition.setCanPipOnFinish(false /* canPipOnFinish */); + if (finishTransition != null) { + finishTransition.setCanPipOnFinish(false /* canPipOnFinish */); + } else if (transition != null) { + transition.setCanPipOnFinish(false /* canPipOnFinish */); } } // A bit hacky, but we need to detect "remove PiP" so that we can "wrap" the @@ -738,9 +728,9 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub if (hopSize > 0) { final boolean isInLockTaskMode = mService.isInLockTaskMode(); for (int i = 0; i < hopSize; ++i) { - effects |= applyHierarchyOp(hops.get(i), effects, syncId, chain, + effects |= applyHierarchyOp(hops.get(i), effects, syncId, transition, isInLockTaskMode, caller, t.getErrorCallbackToken(), - t.getTaskFragmentOrganizer()); + t.getTaskFragmentOrganizer(), finishTransition); } } // Queue-up bounds-change transactions for tasks which are now organized. Do @@ -799,7 +789,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } } finally { if (deferTransitionReady) { - chain.mTransition.continueTransitionReady(); + transition.continueTransitionReady(); } mService.mTaskSupervisor.setDeferRootVisibilityUpdate(false /* deferUpdate */); if (deferResume) { @@ -1089,9 +1079,9 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } private int applyHierarchyOp(WindowContainerTransaction.HierarchyOp hop, int effects, - int syncId, @NonNull ActionChain chain, boolean isInLockTaskMode, + int syncId, @Nullable Transition transition, boolean isInLockTaskMode, @NonNull CallerInfo caller, @Nullable IBinder errorCallbackToken, - @Nullable ITaskFragmentOrganizer organizer) { + @Nullable ITaskFragmentOrganizer organizer, @Nullable Transition finishTransition) { final int type = hop.getType(); switch (type) { case HIERARCHY_OP_TYPE_REMOVE_TASK: { @@ -1161,7 +1151,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub break; } case HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT: { - effects |= reparentChildrenTasksHierarchyOp(hop, chain.mTransition, syncId, + effects |= reparentChildrenTasksHierarchyOp(hop, transition, syncId, isInLockTaskMode); break; } @@ -1214,13 +1204,13 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub if (syncId >= 0) { addToSyncSet(syncId, wc); } - if (chain.mTransition != null) { - chain.mTransition.collect(wc); + if (transition != null) { + transition.collect(wc); if (hop.isReparent()) { if (wc.getParent() != null) { // Collect the current parent. It's visibility may change as // a result of this reparenting. - chain.mTransition.collect(wc.getParent()); + transition.collect(wc.getParent()); } if (hop.getNewParent() != null) { final WindowContainer parentWc = @@ -1229,7 +1219,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub Slog.e(TAG, "Can't resolve parent window from token"); break; } - chain.mTransition.collect(parentWc); + transition.collect(parentWc); } } } @@ -1243,8 +1233,8 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub break; } case HIERARCHY_OP_TYPE_ADD_TASK_FRAGMENT_OPERATION: { - effects |= applyTaskFragmentOperation(hop, chain, isInLockTaskMode, - caller, errorCallbackToken, organizer); + effects |= applyTaskFragmentOperation(hop, transition, isInLockTaskMode, caller, + errorCallbackToken, organizer); break; } case HIERARCHY_OP_TYPE_PENDING_INTENT: { @@ -1358,13 +1348,13 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub break; } case HIERARCHY_OP_TYPE_RESTORE_TRANSIENT_ORDER: { - if (!chain.isFinishing()) break; + if (finishTransition == null) break; final WindowContainer container = WindowContainer.fromBinder(hop.getContainer()); if (container == null) break; final Task thisTask = container.asActivityRecord() != null ? container.asActivityRecord().getTask() : container.asTask(); if (thisTask == null) break; - final Task restoreAt = chain.mTransition.getTransientLaunchRestoreTarget(container); + final Task restoreAt = finishTransition.getTransientLaunchRestoreTarget(container); if (restoreAt == null) break; final TaskDisplayArea taskDisplayArea = thisTask.getTaskDisplayArea(); taskDisplayArea.moveRootTaskBehindRootTask(thisTask.getRootTask(), restoreAt); @@ -1454,7 +1444,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub * {@link #TRANSACT_EFFECTS_LIFECYCLE} or {@link #TRANSACT_EFFECTS_CLIENT_CONFIG}. */ private int applyTaskFragmentOperation(@NonNull WindowContainerTransaction.HierarchyOp hop, - @NonNull ActionChain chain, boolean isInLockTaskMode, @NonNull CallerInfo caller, + @Nullable Transition transition, boolean isInLockTaskMode, @NonNull CallerInfo caller, @Nullable IBinder errorCallbackToken, @Nullable ITaskFragmentOrganizer organizer) { if (!validateTaskFragmentOperation(hop, errorCallbackToken, organizer)) { return TRANSACT_EFFECTS_NONE; @@ -1477,7 +1467,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub break; } createTaskFragment(taskFragmentCreationParams, errorCallbackToken, caller, - chain.mTransition); + transition); break; } case OP_TYPE_DELETE_TASK_FRAGMENT: { @@ -1494,7 +1484,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub break; } } - effects |= deleteTaskFragment(taskFragment, chain.mTransition); + effects |= deleteTaskFragment(taskFragment, transition); break; } case OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT: { @@ -1543,14 +1533,14 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub opType, exception); break; } - if (chain.mTransition != null) { - chain.collect(activity); + if (transition != null) { + transition.collect(activity); if (activity.getParent() != null) { // Collect the current parent. Its visibility may change as a result of // this reparenting. - chain.collect(activity.getParent()); + transition.collect(activity.getParent()); } - chain.collect(taskFragment); + transition.collect(taskFragment); } activity.reparent(taskFragment, POSITION_TOP); effects |= TRANSACT_EFFECTS_LIFECYCLE; @@ -1706,8 +1696,8 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub // If any TaskFragment in the Task is collected by the transition, we make the decor // surface visible in sync with the TaskFragment transition. Otherwise, we make the // decor surface visible immediately. - final TaskFragment syncTaskFragment = chain.mTransition != null - ? task.getTaskFragment(chain.mTransition.mParticipants::contains) + final TaskFragment syncTaskFragment = transition != null + ? task.getTaskFragment(transition.mParticipants::contains) : null; if (syncTaskFragment != null) { @@ -1759,7 +1749,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub // The decor surface boost/unboost must be applied after the transition is // completed. Otherwise, the decor surface could be moved before Shell completes // the transition, causing flicker. - runAfterTransition(chain.mTransition, task::commitDecorSurfaceBoostedState); + runAfterTransition(transition, task::commitDecorSurfaceBoostedState); } break; } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index da3ada8671df..c5c371ff85d5 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -106,7 +106,9 @@ import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.os.BinderInternal; import com.android.internal.os.RuntimeInit; import com.android.internal.policy.AttributeCache; +import com.android.internal.protolog.ProtoLog; import com.android.internal.protolog.ProtoLogConfigurationService; +import com.android.internal.protolog.ProtoLogGroup; import com.android.internal.util.ConcurrentUtils; import com.android.internal.util.EmergencyAffordanceManager; import com.android.internal.util.FrameworkStatsLog; @@ -1099,6 +1101,10 @@ public final class SystemServer implements Dumpable { t.traceEnd(); } + t.traceBegin("InitializeProtoLog"); + ProtoLog.init(ProtoLogGroup.values()); + t.traceEnd(); + // Platform compat service is used by ActivityManagerService, PackageManagerService, and // possibly others in the future. b/135010838. t.traceBegin("PlatformCompat"); diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamOverlayServiceTest.java b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamOverlayServiceTest.java index 54f46078d30b..698ce13aa6db 100644 --- a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamOverlayServiceTest.java +++ b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamOverlayServiceTest.java @@ -18,7 +18,9 @@ package com.android.server.dreams; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -27,7 +29,9 @@ import android.content.ComponentName; import android.content.Intent; import android.os.IBinder; import android.os.RemoteException; +import android.platform.test.annotations.EnableFlags; import android.service.dreams.DreamOverlayService; +import android.service.dreams.Flags; import android.service.dreams.IDreamOverlay; import android.service.dreams.IDreamOverlayCallback; import android.service.dreams.IDreamOverlayClient; @@ -221,6 +225,47 @@ public class DreamOverlayServiceTest { verify(monitor, never()).onWakeUp(); } + /** + * Verifies that only the currently started dream is able to affect the overlay. + */ + @Test + @EnableFlags(Flags.FLAG_DREAM_WAKE_REDIRECT) + public void testRedirectToWakeAcrossClients() throws RemoteException { + doAnswer(invocation -> { + ((Runnable) invocation.getArgument(0)).run(); + return null; + }).when(mExecutor).execute(any()); + + final TestDreamOverlayService.Monitor monitor = Mockito.mock( + TestDreamOverlayService.Monitor.class); + final TestDreamOverlayService service = new TestDreamOverlayService(monitor, mExecutor); + final IBinder binder = service.onBind(new Intent()); + final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(binder); + + service.redirectWake(true); + + final IDreamOverlayClient client = getClient(overlay); + + // Start the dream. + client.startDream(mLayoutParams, mOverlayCallback, + FIRST_DREAM_COMPONENT.flattenToString(), false); + // Make sure redirect state is set on dream. + verify(mOverlayCallback).onRedirectWake(eq(true)); + + // Make sure new changes are propagated. + clearInvocations(mOverlayCallback); + service.redirectWake(false); + verify(mOverlayCallback).onRedirectWake(eq(false)); + + + // Start another dream, make sure new dream is informed of current state. + service.redirectWake(true); + clearInvocations(mOverlayCallback); + client.startDream(mLayoutParams, mOverlayCallback, + FIRST_DREAM_COMPONENT.flattenToString(), false); + verify(mOverlayCallback).onRedirectWake(eq(true)); + } + private static IDreamOverlayClient getClient(IDreamOverlay overlay) throws RemoteException { final OverlayClientCallback callback = new OverlayClientCallback(); overlay.getClient(callback); diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java index 52a80b01971d..56fca31afa37 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -1251,7 +1251,7 @@ public class TransitionTests extends WindowTestsBase { final Transition transition = app.mTransitionController.createTransition(TRANSIT_OPEN); app.mTransitionController.requestStartTransition(transition, app.getTask(), null /* remoteTransition */, null /* displayChange */); - transition.collectExistenceChange(app.getTask()); + app.mTransitionController.collectExistenceChange(app.getTask()); mDisplayContent.setFixedRotationLaunchingAppUnchecked(app); final AsyncRotationController asyncRotationController = mDisplayContent.getAsyncRotationController(); @@ -1416,8 +1416,7 @@ public class TransitionTests extends WindowTestsBase { activity1.setVisibleRequested(false); activity2.setVisibleRequested(true); - final ActionChain chain = ActionChain.testFinish(null); - openTransition.finishTransition(chain); + openTransition.finishTransition(); // We finished the openTransition. Even though activity1 is visibleRequested=false, since // the closeTransition animation hasn't played yet, make sure that we didn't commit @@ -1430,7 +1429,7 @@ public class TransitionTests extends WindowTestsBase { // normally. mWm.mSyncEngine.abort(closeTransition.getSyncId()); - closeTransition.finishTransition(chain); + closeTransition.finishTransition(); assertFalse(activity1.isVisible()); assertTrue(activity2.isVisible()); @@ -1450,7 +1449,7 @@ public class TransitionTests extends WindowTestsBase { activity1.setState(ActivityRecord.State.INITIALIZING, "test"); activity1.mLaunchTaskBehind = true; mWm.mSyncEngine.abort(noChangeTransition.getSyncId()); - noChangeTransition.finishTransition(chain); + noChangeTransition.finishTransition(); assertTrue(activity1.mLaunchTaskBehind); } @@ -1469,7 +1468,7 @@ public class TransitionTests extends WindowTestsBase { // We didn't call abort on the transition itself, so it will still run onTransactionReady // normally. mWm.mSyncEngine.abort(transition1.getSyncId()); - transition1.finishTransition(ActionChain.testFinish(transition1)); + transition1.finishTransition(); verify(transitionEndedListener).run(); @@ -1531,7 +1530,7 @@ public class TransitionTests extends WindowTestsBase { verify(taskSnapshotController, times(1)).recordSnapshot(eq(task2)); - controller.finishTransition(ActionChain.testFinish(openTransition)); + controller.finishTransition(openTransition); // We are now going to simulate closing task1 to return back to (open) task2. final Transition closeTransition = createTestTransition(TRANSIT_CLOSE, controller); @@ -1596,7 +1595,7 @@ public class TransitionTests extends WindowTestsBase { doReturn(true).when(task1).isTranslucentForTransition(); assertFalse(controller.canApplyDim(task1)); - controller.finishTransition(ActionChain.testFinish(closeTransition)); + controller.finishTransition(closeTransition); assertTrue(wasInFinishingTransition[0]); assertFalse(calledListenerOnOtherDisplay[0]); assertNull(controller.mFinishingTransition); @@ -1652,7 +1651,7 @@ public class TransitionTests extends WindowTestsBase { // to avoid the latency to resume the current top, i.e. appB. assertTrue(controller.isTransientVisible(taskRecent)); // The recent is paused after the transient transition is finished. - controller.finishTransition(ActionChain.testFinish(transition)); + controller.finishTransition(transition); assertFalse(controller.isTransientVisible(taskRecent)); } diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java index 9602ae29604f..7652861449de 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java @@ -407,7 +407,7 @@ public class WallpaperControllerTests extends WindowTestsBase { final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class); token.finishSync(t, token.getSyncGroup(), false /* cancel */); transit.onTransactionReady(transit.getSyncId(), t); - dc.mTransitionController.finishTransition(ActionChain.testFinish(transit)); + dc.mTransitionController.finishTransition(transit); assertFalse(wallpaperWindow.isVisible()); assertFalse(token.isVisible()); } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index a215c0a80b46..bcf4ebcd9740 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -2131,7 +2131,7 @@ public class WindowTestsBase extends SystemServiceTestsBase { } public void finish() { - mController.finishTransition(ActionChain.testFinish(mLastTransit)); + mController.finishTransition(mLastTransit); } } } |