Merge "Introduce TaskFragment transition type" into sc-v2-dev
diff --git a/core/java/android/view/InputWindowHandle.java b/core/java/android/view/InputWindowHandle.java
index cca1799..139bff4b 100644
--- a/core/java/android/view/InputWindowHandle.java
+++ b/core/java/android/view/InputWindowHandle.java
@@ -49,6 +49,11 @@
*/
@Nullable
private IBinder windowToken;
+ /**
+ * Used to cache IWindow from the windowToken so we don't need to convert every time getWindow
+ * is called.
+ */
+ private IWindow window;
// The window name.
public String name;
@@ -151,7 +156,7 @@
.append(", visible=").append(visible)
.append(", scaleFactor=").append(scaleFactor)
.append(", transform=").append(transform)
- .append(", windowToken=").append(getWindow())
+ .append(", windowToken=").append(windowToken)
.toString();
}
@@ -186,9 +191,14 @@
public void setWindowToken(IWindow iwindow) {
windowToken = iwindow.asBinder();
+ window = iwindow;
}
public IWindow getWindow() {
- return IWindow.Stub.asInterface(windowToken);
+ if (window != null) {
+ return window;
+ }
+ window = IWindow.Stub.asInterface(windowToken);
+ return window;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index c32dddf..3b9bcd3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -93,6 +93,7 @@
private final Rect mDividerBounds = new Rect();
private final Rect mBounds1 = new Rect();
private final Rect mBounds2 = new Rect();
+ private final Rect mTmpBounds = new Rect();
private final SplitLayoutHandler mSplitLayoutHandler;
private final SplitWindowManager mSplitWindowManager;
private final DisplayImeController mDisplayImeController;
@@ -188,9 +189,10 @@
final int rotation = configuration.windowConfiguration.getRotation();
final Rect rootBounds = configuration.windowConfiguration.getBounds();
if (rotation != mRotation || !mRootBounds.equals(rootBounds)) {
+ mTmpBounds.set(mRootBounds);
mRootBounds.set(rootBounds);
mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds);
- resetDividerPosition();
+ initDividerPosition(mTmpBounds);
affectsLayout = true;
}
@@ -202,6 +204,19 @@
return affectsLayout;
}
+ private void initDividerPosition(Rect oldBounds) {
+ final float snapRatio = (float) mDividePosition
+ / (float) (isLandscape(oldBounds) ? oldBounds.width() : oldBounds.height());
+ // Estimate position by previous ratio.
+ final float length =
+ (float) (isLandscape() ? mRootBounds.width() : mRootBounds.height());
+ final int estimatePosition = (int) (length * snapRatio);
+ // Init divider position by estimated position using current bounds snap algorithm.
+ mDividePosition = mDividerSnapAlgorithm.calculateNonDismissingSnapTarget(
+ estimatePosition).position;
+ updateBounds(mDividePosition);
+ }
+
/** Updates recording bounds of divider window and both of the splits. */
private void updateBounds(int position) {
mDividerBounds.set(mRootBounds);
diff --git a/packages/SettingsLib/TwoTargetPreference/res/layout-v31/preference_two_target_divider.xml b/packages/SettingsLib/TwoTargetPreference/res/layout-v31/preference_two_target_divider.xml
index f21e51c..4641421 100644
--- a/packages/SettingsLib/TwoTargetPreference/res/layout-v31/preference_two_target_divider.xml
+++ b/packages/SettingsLib/TwoTargetPreference/res/layout-v31/preference_two_target_divider.xml
@@ -22,6 +22,8 @@
android:layout_height="match_parent"
android:gravity="start|center_vertical"
android:orientation="horizontal"
+ android:paddingStart="?android:attr/listPreferredItemPaddingEnd"
+ android:paddingLeft="?android:attr/listPreferredItemPaddingEnd"
android:paddingTop="16dp"
android:paddingBottom="16dp">
<View
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
index edb05691..dd3bb89 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
@@ -27,6 +27,7 @@
import android.widget.ImageView;
import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
import com.android.settingslib.Utils;
import com.android.systemui.Dumpable;
@@ -79,7 +80,11 @@
mLockIcon.setImageDrawable(drawable);
}
- void setCenterLocation(@NonNull PointF center, int radius) {
+ /**
+ * Set the location of the lock icon.
+ */
+ @VisibleForTesting
+ public void setCenterLocation(@NonNull PointF center, int radius) {
mLockIconCenter = center;
mRadius = radius;
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index aa8cbd7..47f0714 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -68,7 +68,8 @@
/**
* Controls when to show the LockIcon affordance (lock/unlocked icon or circle) on lock screen.
*
- * This view will only be shown if the user has UDFPS or FaceAuth enrolled
+ * For devices with UDFPS, the lock icon will show at the sensor location. Else, the lock
+ * icon will show a set distance from the bottom of the device.
*/
@StatusBarComponent.StatusBarScope
public class LockIconViewController extends ViewController<LockIconView> implements Dumpable {
@@ -172,15 +173,14 @@
@Override
protected void onInit() {
+ mAuthController.addCallback(mAuthControllerCallback);
+ mUdfpsSupported = mAuthController.getUdfpsSensorLocation() != null;
+
mView.setAccessibilityDelegate(mAccessibilityDelegate);
}
@Override
protected void onViewAttached() {
- // we check this here instead of onInit since the FingerprintManager + FaceManager may not
- // have started up yet onInit
- mUdfpsSupported = mAuthController.getUdfpsSensorLocation() != null;
-
updateConfiguration();
updateKeyguardShowing();
mUserUnlockedWithBiometric = false;
@@ -584,4 +584,12 @@
public void setAlpha(float alpha) {
mView.setAlpha(alpha);
}
+
+ private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() {
+ @Override
+ public void onAllAuthenticatorsRegistered() {
+ mUdfpsSupported = mAuthController.getUdfpsSensorLocation() != null;
+ updateConfiguration();
+ }
+ };
}
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index b126cdd..3555e8d 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -452,6 +452,12 @@
private boolean mCancelled;
@Override
+ public void onAnimationStart(Animator animation) {
+ super.onAnimationStart(animation);
+ mCallback.onBeginDrag(animView);
+ }
+
+ @Override
public void onAnimationCancel(Animator animation) {
mCancelled = true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 0ce1846..ab5f2b828 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -786,7 +786,11 @@
.build(sensorIds, credentialAllowed, mFpProps, mFaceProps);
}
- interface Callback {
+ /**
+ * AuthController callback used to receive signal for when biometric authenticators are
+ * registered.
+ */
+ public interface Callback {
/**
* Called when authenticators are registered. If authenticators are already
* registered before this call, this callback will never be triggered.
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
index 0eaef72..31d51f1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
@@ -154,6 +154,7 @@
@Override
public void onStart() {
super.onStart();
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_ACTIVITY_STARTED);
if (mPreview.getDrawable() != null) {
// We already have an image, so no need to try to load again.
@@ -245,6 +246,8 @@
}
private void onCachedImageLoaded(ImageLoader.Result imageResult) {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_ACTIVITY_CACHED_IMAGE_LOADED);
+
BitmapDrawable drawable = new BitmapDrawable(getResources(), imageResult.bitmap);
mPreview.setImageDrawable(drawable);
mPreview.setAlpha(1f);
@@ -282,6 +285,8 @@
finish();
}
if (isFinishing()) {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_ACTIVITY_FINISHED);
+
if (mScrollCaptureResponse != null) {
mScrollCaptureResponse.close();
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
index 2b7bcc0..169b28c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
@@ -77,7 +77,13 @@
@UiEvent(doc = "The long screenshot capture failed")
SCREENSHOT_LONG_SCREENSHOT_FAILURE(881),
@UiEvent(doc = "The long screenshot capture completed successfully")
- SCREENSHOT_LONG_SCREENSHOT_COMPLETED(882);
+ SCREENSHOT_LONG_SCREENSHOT_COMPLETED(882),
+ @UiEvent(doc = "Long screenshot editor activity started")
+ SCREENSHOT_LONG_SCREENSHOT_ACTIVITY_STARTED(889),
+ @UiEvent(doc = "Long screenshot editor activity loaded a previously saved screenshot")
+ SCREENSHOT_LONG_SCREENSHOT_ACTIVITY_CACHED_IMAGE_LOADED(890),
+ @UiEvent(doc = "Long screenshot editor activity finished")
+ SCREENSHOT_LONG_SCREENSHOT_ACTIVITY_FINISHED(891);
private final int mId;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
index dba3401..9c755e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
@@ -85,23 +85,26 @@
}
/**
- * Set the content of this view to be visible in an animated way.
- *
- * @param contentVisible True if the content should be visible or false if it should be hidden.
+ * @param visible True if we should animate contents visible
*/
- public void setContentVisible(boolean contentVisible) {
- setContentVisible(contentVisible, true /* animate */);
+ public void setContentVisible(boolean visible) {
+ setContentVisible(visible, true /* animate */, null /* runAfter */);
}
+
/**
- * Set the content of this view to be visible.
- * @param contentVisible True if the content should be visible or false if it should be hidden.
- * @param animate Should an animation be performed.
+ * @param visible True if the contents should be visible
+ * @param animate True if we should fade to new visibility
+ * @param runAfter Runnable to run after visibility updates
*/
- private void setContentVisible(boolean contentVisible, boolean animate) {
- if (mContentVisible != contentVisible) {
+ public void setContentVisible(boolean visible, boolean animate, Runnable runAfter) {
+ if (mContentVisible != visible) {
mContentAnimating = animate;
- mContentVisible = contentVisible;
- setViewVisible(mContent, contentVisible, animate, mContentVisibilityEndRunnable);
+ mContentVisible = visible;
+ Runnable endRunnable = runAfter == null ? mContentVisibilityEndRunnable : () -> {
+ mContentVisibilityEndRunnable.run();
+ runAfter.run();
+ };
+ setViewVisible(mContent, visible, animate, endRunnable);
}
if (!mContentAnimating) {
@@ -113,6 +116,10 @@
return mContentVisible;
}
+ public void setVisible(boolean nowVisible, boolean animate) {
+ setVisible(nowVisible, animate, null);
+ }
+
/**
* Make this view visible. If {@code false} is passed, the view will fade out it's content
* and set the view Visibility to GONE. If only the content should be changed
@@ -121,7 +128,7 @@
* @param nowVisible should the view be visible
* @param animate should the change be animated.
*/
- public void setVisible(boolean nowVisible, boolean animate) {
+ public void setVisible(boolean nowVisible, boolean animate, Runnable runAfter) {
if (mIsVisible != nowVisible) {
mIsVisible = nowVisible;
if (animate) {
@@ -132,10 +139,10 @@
} else {
setWillBeGone(true);
}
- setContentVisible(nowVisible, true /* animate */);
+ setContentVisible(nowVisible, true /* animate */, runAfter);
} else {
setVisibility(nowVisible ? VISIBLE : GONE);
- setContentVisible(nowVisible, false /* animate */);
+ setContentVisible(nowVisible, false /* animate */, runAfter);
setWillBeGone(false);
notifyHeightChanged(false /* needsAnimation */);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
index 23aefd9..594afce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
@@ -46,6 +46,7 @@
private Runnable mRoundingChangedCallback;
private ExpandableNotificationRow mTrackedHeadsUp;
private float mAppearFraction;
+ private boolean mIsDismissAllInProgress;
private ExpandableView mSwipedView = null;
private ExpandableView mViewBeforeSwipedView = null;
@@ -162,6 +163,10 @@
}
}
+ void setDismissAllInProgress(boolean isClearingAll) {
+ mIsDismissAllInProgress = isClearingAll;
+ }
+
private float getRoundness(ExpandableView view, boolean top) {
if (view == null) {
return 0f;
@@ -171,6 +176,11 @@
|| view == mViewAfterSwipedView) {
return 1f;
}
+ if (view instanceof ExpandableNotificationRow
+ && ((ExpandableNotificationRow) view).canViewBeDismissed()
+ && mIsDismissAllInProgress) {
+ return 1.0f;
+ }
if ((view.isPinned()
|| (view.isHeadsUpAnimatingAway()) && !mExpanded)) {
return 1.0f;
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 d19eb12..7babcba 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
@@ -139,6 +139,10 @@
private static final boolean DEBUG_REMOVE_ANIMATION = SystemProperties.getBoolean(
"persist.debug.nssl.dismiss", false /* default */);
+ // Delay in milli-seconds before shade closes for clear all.
+ private final int DELAY_BEFORE_SHADE_CLOSE = 200;
+ private boolean mShadeNeedsToClose = false;
+
private static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f;
private static final float RUBBER_BAND_FACTOR_AFTER_EXPAND = 0.15f;
private static final float RUBBER_BAND_FACTOR_ON_PANEL_EXPAND = 0.21f;
@@ -253,7 +257,6 @@
protected FooterView mFooterView;
protected EmptyShadeView mEmptyShadeView;
private boolean mDismissAllInProgress;
- private boolean mFadeNotificationsOnDismiss;
private FooterDismissListener mFooterDismissListener;
private boolean mFlingAfterUpEvent;
@@ -1205,7 +1208,7 @@
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
private void clampScrollPosition() {
int scrollRange = getScrollRange();
- if (scrollRange < mOwnScrollY) {
+ if (scrollRange < mOwnScrollY && !mAmbientState.isDismissAllInProgress()) {
boolean animateStackY = false;
if (scrollRange < getScrollAmountToScrollBoundary()
&& mAnimateStackYForContentHeightChange) {
@@ -1721,6 +1724,11 @@
@ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
public void dismissViewAnimated(View child, Runnable endRunnable, int delay, long duration) {
+ if (child instanceof SectionHeaderView) {
+ ((StackScrollerDecorView) child).setContentVisible(
+ false /* visible */, true /* animate */, endRunnable);
+ return;
+ }
mSwipeHelper.dismissChild(child, 0, endRunnable, delay, true, duration,
true /* isDismissAll */);
}
@@ -4062,6 +4070,19 @@
runAnimationFinishedRunnables();
clearTransient();
clearHeadsUpDisappearRunning();
+
+ if (mAmbientState.isDismissAllInProgress()) {
+ setDismissAllInProgress(false);
+
+ if (mShadeNeedsToClose) {
+ mShadeNeedsToClose = false;
+ postDelayed(
+ () -> {
+ mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
+ },
+ DELAY_BEFORE_SHADE_CLOSE /* delayMillis */);
+ }
+ }
}
@ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
@@ -4430,6 +4451,7 @@
public void setDismissAllInProgress(boolean dismissAllInProgress) {
mDismissAllInProgress = dismissAllInProgress;
mAmbientState.setDismissAllInProgress(dismissAllInProgress);
+ mController.getNoticationRoundessManager().setDismissAllInProgress(dismissAllInProgress);
handleDismissAllClipping();
}
@@ -4973,64 +4995,129 @@
mHeadsUpAppearanceController = headsUpAppearanceController;
}
- @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
- @VisibleForTesting
- void clearNotifications(@SelectedRows int selection, boolean closeShade) {
- // animate-swipe all dismissable notifications, then animate the shade closed
- int numChildren = getChildCount();
+ private boolean isVisible(View child) {
+ boolean hasClipBounds = child.getClipBounds(mTmpRect);
+ return child.getVisibility() == View.VISIBLE
+ && (!hasClipBounds || mTmpRect.height() > 0);
+ }
- final ArrayList<View> viewsToHide = new ArrayList<>(numChildren);
- final ArrayList<ExpandableNotificationRow> viewsToRemove = new ArrayList<>(numChildren);
- for (int i = 0; i < numChildren; i++) {
- final View child = getChildAt(i);
- if (child instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) child;
- boolean parentVisible = false;
- boolean hasClipBounds = child.getClipBounds(mTmpRect);
- if (includeChildInDismissAll(row, selection)) {
- viewsToRemove.add(row);
- if (child.getVisibility() == View.VISIBLE
- && (!hasClipBounds || mTmpRect.height() > 0)) {
- viewsToHide.add(child);
- parentVisible = true;
- }
- } else if (child.getVisibility() == View.VISIBLE
- && (!hasClipBounds || mTmpRect.height() > 0)) {
- parentVisible = true;
- }
- List<ExpandableNotificationRow> children = row.getAttachedChildren();
- if (children != null) {
- for (ExpandableNotificationRow childRow : children) {
- if (includeChildInDismissAll(row, selection)) {
- viewsToRemove.add(childRow);
- if (parentVisible && row.areChildrenExpanded()) {
- hasClipBounds = childRow.getClipBounds(mTmpRect);
- if (childRow.getVisibility() == View.VISIBLE
- && (!hasClipBounds || mTmpRect.height() > 0)) {
- viewsToHide.add(childRow);
- }
- }
+ private boolean shouldHideParent(View view, @SelectedRows int selection) {
+ final boolean silentSectionWillBeGone =
+ !mController.hasNotifications(ROWS_GENTLE, false /* clearable */);
+
+ // The only SectionHeaderView we have is the silent section header.
+ if (view instanceof SectionHeaderView && silentSectionWillBeGone) {
+ return true;
+ }
+ if (view instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+ if (isVisible(row) && includeChildInDismissAll(row, selection)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean isChildrenVisible(ExpandableNotificationRow parent) {
+ List<ExpandableNotificationRow> children = parent.getAttachedChildren();
+ return isVisible(parent)
+ && children != null
+ && parent.areChildrenExpanded();
+ }
+
+ // Similar to #getRowsToDismissInBackend, but filters for visible views.
+ private ArrayList<View> getVisibleViewsToAnimateAway(@SelectedRows int selection) {
+ final int viewCount = getChildCount();
+ final ArrayList<View> viewsToHide = new ArrayList<>(viewCount);
+
+ for (int i = 0; i < viewCount; i++) {
+ final View view = getChildAt(i);
+
+ if (shouldHideParent(view, selection)) {
+ viewsToHide.add(view);
+ }
+ if (view instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
+
+ if (isChildrenVisible(parent)) {
+ for (ExpandableNotificationRow child : parent.getAttachedChildren()) {
+ if (isVisible(child) && includeChildInDismissAll(child, selection)) {
+ viewsToHide.add(child);
}
}
}
}
}
+ return viewsToHide;
+ }
+ private ArrayList<ExpandableNotificationRow> getRowsToDismissInBackend(
+ @SelectedRows int selection) {
+ final int childCount = getChildCount();
+ final ArrayList<ExpandableNotificationRow> viewsToRemove = new ArrayList<>(childCount);
+
+ for (int i = 0; i < childCount; i++) {
+ final View view = getChildAt(i);
+ if (!(view instanceof ExpandableNotificationRow)) {
+ continue;
+ }
+ ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
+ if (includeChildInDismissAll(parent, selection)) {
+ viewsToRemove.add(parent);
+ }
+ List<ExpandableNotificationRow> children = parent.getAttachedChildren();
+ if (isVisible(parent) && children != null) {
+ for (ExpandableNotificationRow child : children) {
+ if (includeChildInDismissAll(parent, selection)) {
+ viewsToRemove.add(child);
+ }
+ }
+ }
+ }
+ return viewsToRemove;
+ }
+
+ /**
+ * Collects a list of visible rows, and animates them away in a staggered fashion as if they
+ * were dismissed. Notifications are dismissed in the backend via onDismissAllAnimationsEnd.
+ */
+ @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
+ @VisibleForTesting
+ void clearNotifications(@SelectedRows int selection, boolean closeShade) {
+ // Animate-swipe all dismissable notifications, then animate the shade closed
+ final ArrayList<View> viewsToAnimateAway = getVisibleViewsToAnimateAway(selection);
+ final ArrayList<ExpandableNotificationRow> rowsToDismissInBackend =
+ getRowsToDismissInBackend(selection);
if (mDismissListener != null) {
mDismissListener.onDismiss(selection);
}
-
- if (viewsToRemove.isEmpty()) {
- if (closeShade && mShadeController != null) {
- mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
- }
+ final Runnable dismissInBackend = () -> {
+ onDismissAllAnimationsEnd(rowsToDismissInBackend, selection);
+ };
+ if (viewsToAnimateAway.isEmpty()) {
+ dismissInBackend.run();
return;
}
+ // Disable normal animations
+ setDismissAllInProgress(true);
+ mShadeNeedsToClose = closeShade;
- performDismissAllAnimations(
- viewsToHide,
- closeShade,
- () -> onDismissAllAnimationsEnd(viewsToRemove, selection));
+ // Decrease the delay for every row we animate to give the sense of
+ // accelerating the swipes
+ final int rowDelayDecrement = 5;
+ int currentDelay = 60;
+ int totalDelay = 0;
+ final int numItems = viewsToAnimateAway.size();
+ for (int i = numItems - 1; i >= 0; i--) {
+ View view = viewsToAnimateAway.get(i);
+ Runnable endRunnable = null;
+ if (i == 0) {
+ endRunnable = dismissInBackend;
+ }
+ dismissViewAnimated(view, endRunnable, totalDelay, ANIMATION_DURATION_SWIPE);
+ currentDelay = Math.max(30, currentDelay - rowDelayDecrement);
+ totalDelay += currentDelay;
+ }
}
private boolean includeChildInDismissAll(
@@ -5039,63 +5126,6 @@
return canChildBeDismissed(row) && matchesSelection(row, selection);
}
- /**
- * Given a list of rows, animates them away in a staggered fashion as if they were dismissed.
- * Doesn't actually dismiss them, though -- that must be done in the onAnimationComplete
- * handler.
- *
- * @param hideAnimatedList List of rows to animated away. Should only be views that are
- * currently visible, or else the stagger will look funky.
- * @param closeShade Whether to close the shade after the stagger animation completes.
- * @param onAnimationComplete Called after the entire animation completes (including the shade
- * closing if appropriate). The rows must be dismissed for real here.
- */
- @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
- private void performDismissAllAnimations(
- final ArrayList<View> hideAnimatedList,
- final boolean closeShade,
- final Runnable onAnimationComplete) {
-
- final Runnable onSlideAwayAnimationComplete = () -> {
- if (closeShade) {
- mShadeController.addPostCollapseAction(() -> {
- setDismissAllInProgress(false);
- onAnimationComplete.run();
- });
- mShadeController.animateCollapsePanels(
- CommandQueue.FLAG_EXCLUDE_NONE);
- } else {
- setDismissAllInProgress(false);
- onAnimationComplete.run();
- }
- };
-
- if (hideAnimatedList.isEmpty()) {
- onSlideAwayAnimationComplete.run();
- return;
- }
-
- // let's disable our normal animations
- setDismissAllInProgress(true);
-
- // Decrease the delay for every row we animate to give the sense of
- // accelerating the swipes
- int rowDelayDecrement = 10;
- int currentDelay = 140;
- int totalDelay = 180;
- int numItems = hideAnimatedList.size();
- for (int i = numItems - 1; i >= 0; i--) {
- View view = hideAnimatedList.get(i);
- Runnable endRunnable = null;
- if (i == 0) {
- endRunnable = onSlideAwayAnimationComplete;
- }
- dismissViewAnimated(view, endRunnable, totalDelay, ANIMATION_DURATION_SWIPE);
- currentDelay = Math.max(50, currentDelay - rowDelayDecrement);
- totalDelay += currentDelay;
- }
- }
-
/** Register a {@link View.OnClickListener} to be invoked when the Manage button is clicked. */
public void setManageButtonClickListener(@Nullable OnClickListener listener) {
mManageButtonClickListener = listener;
@@ -5114,6 +5144,7 @@
mFooterDismissListener.onDismiss();
}
clearNotifications(ROWS_ALL, true /* closeShade */);
+ footerView.setSecondaryVisible(false /* visible */, true /* animate */);
});
setFooterView(footerView);
}
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 04129b5..f85c749 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
@@ -1152,6 +1152,10 @@
mZenModeController.areNotificationsHiddenInShade());
}
+ public boolean areNotificationsHiddenInShade() {
+ return mZenModeController.areNotificationsHiddenInShade();
+ }
+
public boolean isShowingEmptyShadeView() {
return mShowEmptyShadeView;
}
@@ -1200,6 +1204,10 @@
* Return whether there are any clearable notifications
*/
public boolean hasActiveClearableNotifications(@SelectedRows int selection) {
+ return hasNotifications(selection, true /* clearable */);
+ }
+
+ public boolean hasNotifications(@SelectedRows int selection, boolean isClearable) {
if (mDynamicPrivacyController.isInLockedDownShade()) {
return false;
}
@@ -1210,8 +1218,11 @@
continue;
}
final ExpandableNotificationRow row = (ExpandableNotificationRow) child;
- if (row.canViewBeDismissed() &&
- NotificationStackScrollLayout.matchesSelection(row, selection)) {
+ final boolean matchClearable =
+ isClearable ? row.canViewBeDismissed() : !row.canViewBeDismissed();
+ final boolean inSection =
+ NotificationStackScrollLayout.matchesSelection(row, selection);
+ if (matchClearable && inSection) {
return true;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 2c810c9..8be5de7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -366,6 +366,20 @@
return stackHeight / stackEndHeight;
}
+ public boolean hasOngoingNotifs(StackScrollAlgorithmState algorithmState) {
+ for (int i = 0; i < algorithmState.visibleChildren.size(); i++) {
+ View child = algorithmState.visibleChildren.get(i);
+ if (!(child instanceof ExpandableNotificationRow)) {
+ continue;
+ }
+ final ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ if (!row.canViewBeDismissed()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
// TODO(b/172289889) polish shade open from HUN
/**
* Populates the {@link ExpandableViewState} for a single child.
@@ -430,7 +444,9 @@
+ view.getIntrinsicHeight();
final boolean noSpaceForFooter = footerEnd > ambientState.getStackEndHeight();
((FooterView.FooterViewState) viewState).hideContent =
- isShelfShowing || noSpaceForFooter;
+ isShelfShowing || noSpaceForFooter
+ || (ambientState.isDismissAllInProgress()
+ && !hasOngoingNotifs(algorithmState));
}
} else {
if (view != ambientState.getTrackedHeadsUpRow()) {
@@ -467,7 +483,6 @@
}
}
}
-
// Clip height of view right before shelf.
viewState.height = (int) (getMaxAllowedChildHeight(view) * expansionFraction);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index 2702bf7..e3a4bf0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -46,7 +46,7 @@
public static final int ANIMATION_DURATION_WAKEUP = 500;
public static final int ANIMATION_DURATION_GO_TO_FULL_SHADE = 448;
public static final int ANIMATION_DURATION_APPEAR_DISAPPEAR = 464;
- public static final int ANIMATION_DURATION_SWIPE = 260;
+ public static final int ANIMATION_DURATION_SWIPE = 200;
public static final int ANIMATION_DURATION_DIMMED_ACTIVATED = 220;
public static final int ANIMATION_DURATION_CLOSE_REMOTE_INPUT = 150;
public static final int ANIMATION_DURATION_HEADS_UP_APPEAR = 400;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index a56b8ac..c639eec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -204,13 +204,15 @@
private ControlsListingController.ControlsListingCallback mListingCallback =
new ControlsListingController.ControlsListingCallback() {
public void onServicesUpdated(List<ControlsServiceInfo> serviceInfos) {
- boolean available = !serviceInfos.isEmpty();
+ post(() -> {
+ boolean available = !serviceInfos.isEmpty();
- if (available != mControlServicesAvailable) {
- mControlServicesAvailable = available;
- updateControlsVisibility();
- updateAffordanceColors();
- }
+ if (available != mControlServicesAvailable) {
+ mControlServicesAvailable = available;
+ updateControlsVisibility();
+ updateAffordanceColors();
+ }
+ });
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 07508a6..39dc540 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -652,7 +652,7 @@
private KeyguardMediaController mKeyguardMediaController;
- private boolean mStatusViewCentered = false;
+ private boolean mStatusViewCentered = true;
private View.AccessibilityDelegate mAccessibilityDelegate = new View.AccessibilityDelegate() {
@Override
@@ -1082,7 +1082,7 @@
constraintSet.applyTo(mNotificationContainerParent);
mNotificationContainerParent.setSplitShadeEnabled(mShouldUseSplitNotificationShade);
- updateKeyguardStatusViewAlignment(false /* animate */);
+ updateKeyguardStatusViewAlignment(/* animate= */false);
mLockscreenSmartspaceController.onSplitShadeChanged(mShouldUseSplitNotificationShade);
mKeyguardMediaController.refreshMediaPosition();
}
@@ -1129,6 +1129,9 @@
keyguardStatusView = (KeyguardStatusView) mLayoutInflater.inflate(
R.layout.keyguard_status_view, mNotificationContainerParent, false);
mNotificationContainerParent.addView(keyguardStatusView, statusIndex);
+ // When it's reinflated, this is centered by default. If it shouldn't be, this will update
+ // below when resources are updated.
+ mStatusViewCentered = true;
attachSplitShadeMediaPlayerContainer(
keyguardStatusView.findViewById(R.id.status_view_media_container));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 8ebaf91..832f317 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -312,17 +312,11 @@
private void onNotificationRemoved(String key, StatusBarNotification old, int reason) {
if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old);
- if (old != null) {
- if (CLOSE_PANEL_WHEN_EMPTIED && !hasActiveNotifications()
- && !mNotificationPanel.isTracking() && !mNotificationPanel.isQsExpanded()) {
- if (mStatusBarStateController.getState() == StatusBarState.SHADE
- && reason != NotificationListenerService.REASON_CLICK) {
- mCommandQueue.animateCollapsePanels();
- } else if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED
+ if (old != null && CLOSE_PANEL_WHEN_EMPTIED && !hasActiveNotifications()
+ && !mNotificationPanel.isTracking() && !mNotificationPanel.isQsExpanded()
+ && mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED
&& !isCollapsing()) {
- mStatusBarStateController.setState(StatusBarState.KEYGUARD);
- }
- }
+ mStatusBarStateController.setState(StatusBarState.KEYGUARD);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
new file mode 100644
index 0000000..d279bbb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2021 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;
+
+import static junit.framework.Assert.assertEquals;
+
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.PointF;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.os.Vibrator;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.util.DisplayMetrics;
+import android.view.View;
+import android.view.accessibility.AccessibilityManager;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardViewController;
+import com.android.keyguard.LockIconView;
+import com.android.keyguard.LockIconViewController;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.biometrics.AuthRippleController;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.concurrency.DelayableExecutor;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class LockIconViewControllerTest extends SysuiTestCase {
+ private @Mock LockIconView mLockIconView;
+ private @Mock Context mContext;
+ private @Mock Resources mResources;
+ private @Mock DisplayMetrics mDisplayMetrics;
+ private @Mock StatusBarStateController mStatusBarStateController;
+ private @Mock KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private @Mock KeyguardViewController mKeyguardViewController;
+ private @Mock KeyguardStateController mKeyguardStateController;
+ private @Mock FalsingManager mFalsingManager;
+ private @Mock AuthController mAuthController;
+ private @Mock DumpManager mDumpManager;
+ private @Mock AccessibilityManager mAccessibilityManager;
+ private @Mock ConfigurationController mConfigurationController;
+ private @Mock DelayableExecutor mDelayableExecutor;
+ private @Mock Vibrator mVibrator;
+ private @Mock AuthRippleController mAuthRippleController;
+
+ private LockIconViewController mLockIconViewController;
+
+ // Capture listeners so that they can be used to send events
+ @Captor private ArgumentCaptor<View.OnAttachStateChangeListener> mAttachCaptor =
+ ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
+ private View.OnAttachStateChangeListener mAttachListener;
+
+ @Captor private ArgumentCaptor<AuthController.Callback> mAuthControllerCallbackCaptor;
+ private AuthController.Callback mAuthControllerCallback;
+
+ @Captor private ArgumentCaptor<PointF> mPointCaptor;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ when(mLockIconView.getResources()).thenReturn(mResources);
+ when(mLockIconView.getContext()).thenReturn(mContext);
+ when(mContext.getResources()).thenReturn(mResources);
+ when(mResources.getDisplayMetrics()).thenReturn(mDisplayMetrics);
+
+ mLockIconViewController = new LockIconViewController(
+ mLockIconView,
+ mStatusBarStateController,
+ mKeyguardUpdateMonitor,
+ mKeyguardViewController,
+ mKeyguardStateController,
+ mFalsingManager,
+ mAuthController,
+ mDumpManager,
+ mAccessibilityManager,
+ mConfigurationController,
+ mDelayableExecutor,
+ mVibrator,
+ mAuthRippleController
+ );
+ }
+
+ @Test
+ public void testUpdateFingerprintLocationOnInit() {
+ // GIVEN fp sensor location is available pre-init
+ final PointF udfpsLocation = new PointF(50, 75);
+ final int radius = 33;
+ final FingerprintSensorPropertiesInternal fpProps =
+ new FingerprintSensorPropertiesInternal(
+ /* sensorId */ 0,
+ /* strength */ 0,
+ /* max enrollments per user */ 5,
+ /* component info */ new ArrayList<>(),
+ /* sensorType */ 3,
+ /* resetLockoutRequiresHwToken */ false,
+ (int) udfpsLocation.x, (int) udfpsLocation.y, radius);
+ when(mAuthController.getUdfpsSensorLocation()).thenReturn(udfpsLocation);
+ when(mAuthController.getUdfpsProps()).thenReturn(List.of(fpProps));
+
+ // WHEN lock icon view controller is initialized and attached
+ mLockIconViewController.init();
+ captureAttachListener();
+ mAttachListener.onViewAttachedToWindow(null);
+
+ // THEN lock icon view location is updated with the same coordinates as fpProps
+ verify(mLockIconView).setCenterLocation(mPointCaptor.capture(), eq(radius));
+ assertEquals(udfpsLocation, mPointCaptor.getValue());
+ }
+
+ @Test
+ public void testUpdateFingerprintLocationOnAuthenticatorsRegistered() {
+ // GIVEN fp sensor location is not available pre-init
+ when(mAuthController.getFingerprintSensorLocation()).thenReturn(null);
+ when(mAuthController.getUdfpsProps()).thenReturn(null);
+ mLockIconViewController.init();
+
+ // GIVEN fp sensor location is available post-init
+ captureAuthControllerCallback();
+ final PointF udfpsLocation = new PointF(50, 75);
+ final int radius = 33;
+ final FingerprintSensorPropertiesInternal fpProps =
+ new FingerprintSensorPropertiesInternal(
+ /* sensorId */ 0,
+ /* strength */ 0,
+ /* max enrollments per user */ 5,
+ /* component info */ new ArrayList<>(),
+ /* sensorType */ 3,
+ /* resetLockoutRequiresHwToken */ false,
+ (int) udfpsLocation.x, (int) udfpsLocation.y, radius);
+ when(mAuthController.getUdfpsSensorLocation()).thenReturn(udfpsLocation);
+ when(mAuthController.getUdfpsProps()).thenReturn(List.of(fpProps));
+
+ // WHEN all authenticators are registered
+ mAuthControllerCallback.onAllAuthenticatorsRegistered();
+
+ // THEN lock icon view location is updated with the same coordinates as fpProps
+ verify(mLockIconView).setCenterLocation(mPointCaptor.capture(), eq(radius));
+ assertEquals(udfpsLocation, mPointCaptor.getValue());
+ }
+
+ private void captureAuthControllerCallback() {
+ verify(mAuthController).addCallback(mAuthControllerCallbackCaptor.capture());
+ mAuthControllerCallback = mAuthControllerCallbackCaptor.getValue();
+ }
+
+ private void captureAttachListener() {
+ verify(mLockIconView).addOnAttachStateChangeListener(mAttachCaptor.capture());
+ mAttachListener = mAttachCaptor.getValue();
+ }
+}
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index ce1592d..73ef385 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -703,6 +703,7 @@
// Always calculate the delay because the caller may need to know the individual drawn time.
info.mWindowsDrawnDelayMs = info.calculateDelay(timestampNs);
info.removePendingDrawActivity(r);
+ info.updatePendingDraw(false /* keepInitializing */);
final TransitionInfoSnapshot infoSnapshot = new TransitionInfoSnapshot(info);
if (info.mLoggedTransitionStarting && info.allDrawn()) {
done(false /* abort */, info, "notifyWindowsDrawn - all windows drawn", timestampNs);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 856c1e0..dc6f5b6 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -676,6 +676,12 @@
boolean mLastImeShown;
/**
+ * When set to true, the IME insets will be frozen until the next app becomes IME input target.
+ * @see InsetsPolicy#adjustVisibilityForIme
+ */
+ boolean mImeInsetsFrozenUntilStartInput;
+
+ /**
* A flag to determine if this AR is in the process of closing or entering PIP. This is needed
* to help AR know that the app is in the process of closing but hasn't yet started closing on
* the WM side.
@@ -1460,6 +1466,7 @@
associateStartingDataWithTask();
overrideConfigurationPropagation(mStartingWindow, task);
}
+ mImeInsetsFrozenUntilStartInput = false;
}
if (rootTask != null && rootTask.topRunningActivity() == this) {
@@ -3928,7 +3935,9 @@
if (tStartingWindow != null && fromActivity.mStartingSurface != null) {
// In this case, the starting icon has already been displayed, so start
// letting windows get shown immediately without any more transitions.
- getDisplayContent().mSkipAppTransitionAnimation = true;
+ if (fromActivity.mVisible) {
+ mDisplayContent.mSkipAppTransitionAnimation = true;
+ }
ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Moving existing starting %s"
+ " from %s to %s", tStartingWindow, fromActivity, this);
@@ -4938,6 +4947,7 @@
&& imeInputTarget.getWindow().mActivityRecord == this
&& mDisplayContent.mInputMethodWindow != null
&& mDisplayContent.mInputMethodWindow.isVisible();
+ mImeInsetsFrozenUntilStartInput = true;
}
final DisplayContent displayContent = getDisplayContent();
@@ -6069,6 +6079,14 @@
// closing activity having to wait until idle timeout to be stopped or destroyed if the
// next activity won't report idle (e.g. repeated view animation).
mTaskSupervisor.scheduleProcessStoppingAndFinishingActivitiesIfNeeded();
+
+ // If the activity is visible, but no windows are eligible to start input, unfreeze
+ // to avoid permanently frozen IME insets.
+ if (mImeInsetsFrozenUntilStartInput && getWindow(
+ win -> WindowManager.LayoutParams.mayUseInputMethod(win.mAttrs.flags))
+ == null) {
+ mImeInsetsFrozenUntilStartInput = false;
+ }
}
}
@@ -8053,6 +8071,13 @@
}
}
+ @Override
+ void onResize() {
+ // Reset freezing IME insets flag when the activity resized.
+ mImeInsetsFrozenUntilStartInput = false;
+ super.onResize();
+ }
+
/** Returns true if the configuration is compatible with this activity. */
boolean isConfigurationCompatible(Configuration config) {
final int orientation = getRequestedOrientation();
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 4aea942..74287c4 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -4022,6 +4022,9 @@
void updateImeInputAndControlTarget(WindowState target) {
if (mImeInputTarget != target) {
ProtoLog.i(WM_DEBUG_IME, "setInputMethodInputTarget %s", target);
+ if (target != null && target.mActivityRecord != null) {
+ target.mActivityRecord.mImeInsetsFrozenUntilStartInput = false;
+ }
setImeInputTarget(target);
updateImeControlTarget();
}
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 0a83784..869d351 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -213,7 +213,7 @@
InsetsState getInsetsForWindow(WindowState target) {
final InsetsState originalState = mStateController.getInsetsForWindow(target);
final InsetsState state = adjustVisibilityForTransientTypes(originalState);
- return target.mIsImWindow ? adjustVisibilityForIme(state, state == originalState) : state;
+ return adjustVisibilityForIme(target, state, state == originalState);
}
/**
@@ -243,16 +243,37 @@
return state;
}
- // Navigation bar insets is always visible to IME.
- private static InsetsState adjustVisibilityForIme(InsetsState originalState,
+ private InsetsState adjustVisibilityForIme(WindowState w, InsetsState originalState,
boolean copyState) {
- final InsetsSource originalNavSource = originalState.peekSource(ITYPE_NAVIGATION_BAR);
- if (originalNavSource != null && !originalNavSource.isVisible()) {
- final InsetsState state = copyState ? new InsetsState(originalState) : originalState;
- final InsetsSource navSource = new InsetsSource(originalNavSource);
- navSource.setVisible(true);
- state.addSource(navSource);
- return state;
+ if (w.mIsImWindow) {
+ // Navigation bar insets is always visible to IME.
+ final InsetsSource originalNavSource = originalState.peekSource(ITYPE_NAVIGATION_BAR);
+ if (originalNavSource != null && !originalNavSource.isVisible()) {
+ final InsetsState state = copyState ? new InsetsState(originalState)
+ : originalState;
+ final InsetsSource navSource = new InsetsSource(originalNavSource);
+ navSource.setVisible(true);
+ state.addSource(navSource);
+ return state;
+ }
+ } else if (w.mActivityRecord != null && w.mActivityRecord.mImeInsetsFrozenUntilStartInput) {
+ // During switching tasks with gestural navigation, if the IME is attached to
+ // one app window on that time, even the next app window is behind the IME window,
+ // conceptually the window should not receive the IME insets if the next window is
+ // not eligible IME requester and ready to show IME on top of it.
+ final boolean shouldImeAttachedToApp = mDisplayContent.shouldImeAttachedToApp();
+ final InsetsSource originalImeSource = originalState.peekSource(ITYPE_IME);
+
+ if (shouldImeAttachedToApp && originalImeSource != null) {
+ final boolean imeVisibility =
+ w.mActivityRecord.mLastImeShown || w.getRequestedVisibility(ITYPE_IME);
+ final InsetsState state = copyState ? new InsetsState(originalState)
+ : originalState;
+ final InsetsSource imeSource = new InsetsSource(originalImeSource);
+ imeSource.setVisible(imeVisibility);
+ state.addSource(imeSource);
+ return state;
+ }
}
return originalState;
}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 6e4792bd..0b9d3dc 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -324,6 +324,7 @@
// Reset the adjacent TaskFragment if its adjacent TaskFragment is also this TaskFragment.
if (mAdjacentTaskFragment != null && mAdjacentTaskFragment.mAdjacentTaskFragment == this) {
mAdjacentTaskFragment.mAdjacentTaskFragment = null;
+ mAdjacentTaskFragment.mDelayLastActivityRemoval = false;
}
mAdjacentTaskFragment = null;
mDelayLastActivityRemoval = false;
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
index 89a126b..0b91802 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
@@ -476,6 +476,16 @@
}
@Test
+ public void testConsecutiveLaunch() {
+ mTrampolineActivity.setState(ActivityRecord.State.INITIALIZING, "test");
+ onActivityLaunched(mTrampolineActivity);
+ mActivityMetricsLogger.notifyActivityLaunching(mTopActivity.intent,
+ mTrampolineActivity /* caller */, mTrampolineActivity.getUid());
+ notifyActivityLaunched(START_SUCCESS, mTopActivity);
+ transitToDrawnAndVerifyOnLaunchFinished(mTopActivity);
+ }
+
+ @Test
public void testConsecutiveLaunchNewTask() {
final IBinder launchCookie = mock(IBinder.class);
final WindowContainerToken launchRootTask = mock(WindowContainerToken.class);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index b35f748..9ad479a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -40,6 +40,7 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
+import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -2509,8 +2510,10 @@
@Test
public void testTransferStartingWindow() {
registerTestStartingWindowOrganizer();
- final ActivityRecord activity1 = new ActivityBuilder(mAtm).setCreateTask(true).build();
- final ActivityRecord activity2 = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final ActivityRecord activity1 = new ActivityBuilder(mAtm).setCreateTask(true)
+ .setVisible(false).build();
+ final ActivityRecord activity2 = new ActivityBuilder(mAtm).setCreateTask(true)
+ .setVisible(false).build();
activity1.addStartingWindow(mPackageName,
android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true,
false, false);
@@ -2519,6 +2522,7 @@
android.R.style.Theme, null, "Test", 0, 0, 0, 0, activity1,
true, true, false, true, false, false);
waitUntilHandlersIdle();
+ assertFalse(mDisplayContent.mSkipAppTransitionAnimation);
assertNoStartingWindow(activity1);
assertHasStartingWindow(activity2);
}
@@ -2626,6 +2630,7 @@
false /* newTask */, false /* isTaskSwitch */, null /* options */,
null /* sourceRecord */);
+ assertTrue(mDisplayContent.mSkipAppTransitionAnimation);
assertNull(middle.mStartingWindow);
assertHasStartingWindow(top);
assertTrue(top.isVisible());
@@ -2907,6 +2912,73 @@
assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
}
+ @Test
+ public void testImeInsetsFrozenFlag_resetWhenReparented() {
+ final ActivityRecord activity = createActivityWithTask();
+ final WindowState app = createWindow(null, TYPE_APPLICATION, activity, "app");
+ final WindowState imeWindow = createWindow(null, TYPE_APPLICATION, "imeWindow");
+ final Task newTask = new TaskBuilder(mSupervisor).build();
+ makeWindowVisible(app, imeWindow);
+ mDisplayContent.mInputMethodWindow = imeWindow;
+ mDisplayContent.setImeLayeringTarget(app);
+ mDisplayContent.setImeInputTarget(app);
+
+ // Simulate app is closing and expect the last IME is shown and IME insets is frozen.
+ app.mActivityRecord.commitVisibility(false, false);
+ assertTrue(app.mActivityRecord.mLastImeShown);
+ assertTrue(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
+
+ // Expect IME insets frozen state will reset when the activity is reparent to the new task.
+ activity.setState(RESUMED, "test");
+ activity.reparent(newTask, 0 /* top */, "test");
+ assertFalse(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
+ }
+
+ @UseTestDisplay(addWindows = W_INPUT_METHOD)
+ @Test
+ public void testImeInsetsFrozenFlag_resetWhenResized() {
+ final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+ makeWindowVisibleAndDrawn(app, mImeWindow);
+ mDisplayContent.setImeLayeringTarget(app);
+ mDisplayContent.setImeInputTarget(app);
+
+ // Simulate app is closing and expect the last IME is shown and IME insets is frozen.
+ app.mActivityRecord.commitVisibility(false, false);
+ assertTrue(app.mActivityRecord.mLastImeShown);
+ assertTrue(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
+
+ // Expect IME insets frozen state will reset when the activity is reparent to the new task.
+ app.mActivityRecord.onResize();
+ assertFalse(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
+ }
+
+ @UseTestDisplay(addWindows = W_INPUT_METHOD)
+ @Test
+ public void testImeInsetsFrozenFlag_resetWhenNoImeFocusableInActivity() {
+ final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+ makeWindowVisibleAndDrawn(app, mImeWindow);
+ mDisplayContent.setImeLayeringTarget(app);
+ mDisplayContent.setImeInputTarget(app);
+
+ // Simulate app is closing and expect the last IME is shown and IME insets is frozen.
+ app.mActivityRecord.commitVisibility(false, false);
+ app.mActivityRecord.onWindowsGone();
+
+ assertTrue(app.mActivityRecord.mLastImeShown);
+ assertTrue(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
+
+ // Expect IME insets frozen state will reset when the activity has no IME focusable window.
+ app.mActivityRecord.forAllWindowsUnchecked(w -> {
+ w.mAttrs.flags |= FLAG_ALT_FOCUSABLE_IM;
+ return true;
+ }, true);
+
+ app.mActivityRecord.commitVisibility(true, false);
+ app.mActivityRecord.onWindowsVisible();
+
+ assertFalse(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
+ }
+
private void assertHasStartingWindow(ActivityRecord atoken) {
assertNotNull(atoken.mStartingSurface);
assertNotNull(atoken.mStartingData);
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index a8e1753..8e7ba4bc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -258,6 +258,7 @@
final ActivityManagerInternal amInternal = mAmService.mInternal;
spyOn(amInternal);
doNothing().when(amInternal).trimApplications();
+ doNothing().when(amInternal).scheduleAppGcs();
doNothing().when(amInternal).updateCpuStats();
doNothing().when(amInternal).updateOomAdj();
doNothing().when(amInternal).updateBatteryStats(any(), anyInt(), anyInt(), anyBoolean());
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 17288c2..b17ea5e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -892,6 +892,40 @@
assertTrue(mAppWindow.getInsetsState().getSourceOrDefaultVisibility(ITYPE_NAVIGATION_BAR));
}
+ @Test
+ public void testAdjustImeInsetsVisibilityWhenSwitchingApps() {
+ final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+ final WindowState app2 = createWindow(null, TYPE_APPLICATION, "app2");
+ final WindowState imeWindow = createWindow(null, TYPE_APPLICATION, "imeWindow");
+ spyOn(imeWindow);
+ doReturn(true).when(imeWindow).isVisible();
+ mDisplayContent.mInputMethodWindow = imeWindow;
+
+ final InsetsStateController controller = mDisplayContent.getInsetsStateController();
+ controller.getImeSourceProvider().setWindow(imeWindow, null, null);
+
+ // Simulate app requests IME with updating all windows Insets State when IME is above app.
+ mDisplayContent.setImeLayeringTarget(app);
+ mDisplayContent.setImeInputTarget(app);
+ assertTrue(mDisplayContent.shouldImeAttachedToApp());
+ controller.getImeSourceProvider().scheduleShowImePostLayout(app);
+ controller.getImeSourceProvider().getSource().setVisible(true);
+ controller.updateAboveInsetsState(imeWindow, false);
+
+ // Expect all app windows behind IME can receive IME insets visible.
+ assertTrue(app.getInsetsState().getSource(ITYPE_IME).isVisible());
+ assertTrue(app2.getInsetsState().getSource(ITYPE_IME).isVisible());
+
+ // Simulate app plays closing transition to app2.
+ app.mActivityRecord.commitVisibility(false, false);
+ assertTrue(app.mActivityRecord.mLastImeShown);
+ assertTrue(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
+
+ // Verify the IME insets is visible on app, but not for app2 during app task switching.
+ assertTrue(app.getInsetsState().getSource(ITYPE_IME).isVisible());
+ assertFalse(app2.getInsetsState().getSource(ITYPE_IME).isVisible());
+ }
+
@UseTestDisplay(addWindows = { W_ACTIVITY })
@Test
public void testUpdateImeControlTargetWhenLeavingMultiWindow() {