diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt index 8f9ced6956ca7..eac5d275092a5 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt @@ -113,6 +113,19 @@ constructor( } val animateFrom = animatedParent?.dialogContentWithBackground ?: view + if (animatedParent == null && animateFrom !is LaunchableView) { + // Make sure the View we launch from implements LaunchableView to avoid visibility + // issues. Given that we don't own dialog decorViews so we can't enforce it for launches + // from a dialog. + // TODO(b/243636422): Throw instead of logging to enforce this. + Log.w( + TAG, + "A dialog was launched from a View that does not implement LaunchableView. This " + + "can lead to subtle bugs where the visibility of the View we are " + + "launching from is not what we expected." + ) + } + // Make sure we don't run the launch animation from the same view twice at the same time. if (animateFrom.getTag(TAG_LAUNCH_ANIMATION_RUNNING) != null) { Log.e(TAG, "Not running dialog launch animation as there is already one running") @@ -156,9 +169,14 @@ constructor( openedDialogs.firstOrNull { it.dialog == animateFrom }?.dialogContentWithBackground ?: throw IllegalStateException( "The animateFrom dialog was not animated using " + - "DialogLaunchAnimator.showFrom(View|Dialog)") + "DialogLaunchAnimator.showFrom(View|Dialog)" + ) showFromView( - dialog, view, animateBackgroundBoundsChange = animateBackgroundBoundsChange, cuj = cuj) + dialog, + view, + animateBackgroundBoundsChange = animateBackgroundBoundsChange, + cuj = cuj + ) } /** @@ -197,7 +215,7 @@ constructor( // bouncer. if ( !dialog.isShowing || - (!callback.isUnlocked() && !callback.isShowingAlternateAuthOnUnlock()) + (!callback.isUnlocked() && !callback.isShowingAlternateAuthOnUnlock()) ) { return null } @@ -556,11 +574,12 @@ private class AnimatedDialog( window.setDecorFitsSystemWindows(false) val viewWithInsets = (dialogContentWithBackground.parent as ViewGroup) viewWithInsets.setOnApplyWindowInsetsListener { view, windowInsets -> - val type = if (wasFittingNavigationBars) { - WindowInsets.Type.displayCutout() or WindowInsets.Type.navigationBars() - } else { - WindowInsets.Type.displayCutout() - } + val type = + if (wasFittingNavigationBars) { + WindowInsets.Type.displayCutout() or WindowInsets.Type.navigationBars() + } else { + WindowInsets.Type.displayCutout() + } val insets = windowInsets.getInsets(type) view.setPadding(insets.left, insets.top, insets.right, insets.bottom) diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt index 7499302c06b22..67b59e0e9928e 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt @@ -16,15 +16,79 @@ package com.android.systemui.animation +import android.view.View + /** A view that can expand/launch into an app or a dialog. */ interface LaunchableView { /** - * Set whether this view should block/prevent all visibility changes. This ensures that this - * view remains invisible during the launch animation given that it is ghosted and already drawn + * Set whether this view should block/postpone all visibility changes. This ensures that this + * view: + * - remains invisible during the launch animation given that it is ghosted and already drawn * somewhere else. + * - remains invisible as long as a dialog expanded from it is shown. + * - restores its expected visibility once the dialog expanded from it is dismissed. * * Note that when this is set to true, both the [normal][android.view.View.setVisibility] and * [transition][android.view.View.setTransitionVisibility] visibility changes must be blocked. + * + * @param block whether we should block/postpone all calls to `setVisibility` and + * `setTransitionVisibility`. */ fun setShouldBlockVisibilityChanges(block: Boolean) } + +/** A delegate that can be used by views to make the implementation of [LaunchableView] easier. */ +class LaunchableViewDelegate( + private val view: View, + + /** + * The lambda that should set the actual visibility of [view], usually by calling + * super.setVisibility(visibility). + */ + private val superSetVisibility: (Int) -> Unit, + + /** + * The lambda that should set the actual transition visibility of [view], usually by calling + * super.setTransitionVisibility(visibility). + */ + private val superSetTransitionVisibility: (Int) -> Unit, +) { + private var blockVisibilityChanges = false + private var lastVisibility = view.visibility + + /** Call this when [LaunchableView.setShouldBlockVisibilityChanges] is called. */ + fun setShouldBlockVisibilityChanges(block: Boolean) { + if (block == blockVisibilityChanges) { + return + } + + blockVisibilityChanges = block + if (block) { + lastVisibility = view.visibility + } else { + superSetVisibility(lastVisibility) + } + } + + /** Call this when [View.setVisibility] is called. */ + fun setVisibility(visibility: Int) { + if (blockVisibilityChanges) { + lastVisibility = visibility + return + } + + superSetVisibility(visibility) + } + + /** Call this when [View.setTransitionVisibility] is called. */ + fun setTransitionVisibility(visibility: Int) { + if (blockVisibilityChanges) { + // View.setTransitionVisibility just sets the visibility flag, so we don't have to save + // the transition visibility separately from the normal visibility. + lastVisibility = visibility + return + } + + superSetTransitionVisibility(visibility) + } +} diff --git a/packages/SystemUI/res-keyguard/layout/footer_actions_number_button.xml b/packages/SystemUI/res-keyguard/layout/footer_actions_number_button.xml index 940b9e1fd4556..a7ffe9ca256f4 100644 --- a/packages/SystemUI/res-keyguard/layout/footer_actions_number_button.xml +++ b/packages/SystemUI/res-keyguard/layout/footer_actions_number_button.xml @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. --> - - \ No newline at end of file + \ No newline at end of file diff --git a/packages/SystemUI/res-keyguard/layout/footer_actions_text_button.xml b/packages/SystemUI/res-keyguard/layout/footer_actions_text_button.xml index 27a1fa8f4a7b7..fc18132d4dc38 100644 --- a/packages/SystemUI/res-keyguard/layout/footer_actions_text_button.xml +++ b/packages/SystemUI/res-keyguard/layout/footer_actions_text_button.xml @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. --> - - \ No newline at end of file + \ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/ContentDescriptionViewBinder.kt b/packages/SystemUI/src/com/android/systemui/common/ui/binder/ContentDescriptionViewBinder.kt similarity index 96% rename from packages/SystemUI/src/com/android/systemui/common/ui/ContentDescriptionViewBinder.kt rename to packages/SystemUI/src/com/android/systemui/common/ui/binder/ContentDescriptionViewBinder.kt index ee9dfb5f5d2c0..d6433aae9845f 100644 --- a/packages/SystemUI/src/com/android/systemui/common/ui/ContentDescriptionViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/common/ui/binder/ContentDescriptionViewBinder.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.common.ui +package com.android.systemui.common.ui.binder import android.view.View import com.android.systemui.common.shared.model.ContentDescription diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/IconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/common/ui/binder/IconViewBinder.kt similarity index 95% rename from packages/SystemUI/src/com/android/systemui/common/ui/IconViewBinder.kt rename to packages/SystemUI/src/com/android/systemui/common/ui/binder/IconViewBinder.kt index 0e0d19815d7bd..aecee2afc9d23 100644 --- a/packages/SystemUI/src/com/android/systemui/common/ui/IconViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/common/ui/binder/IconViewBinder.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.common.ui +package com.android.systemui.common.ui.binder import android.widget.ImageView import com.android.systemui.common.shared.model.Icon diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableLinearLayout.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableLinearLayout.kt new file mode 100644 index 0000000000000..c27b82aeeb47c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableLinearLayout.kt @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2022 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.common.ui.view + +import android.content.Context +import android.util.AttributeSet +import android.widget.LinearLayout +import com.android.systemui.animation.LaunchableView +import com.android.systemui.animation.LaunchableViewDelegate + +/** A [LinearLayout] that also implements [LaunchableView]. */ +class LaunchableLinearLayout : LinearLayout, LaunchableView { + private val delegate = + LaunchableViewDelegate( + this, + superSetVisibility = { super.setVisibility(it) }, + superSetTransitionVisibility = { super.setTransitionVisibility(it) }, + ) + + constructor(context: Context?) : super(context) + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) + constructor( + context: Context?, + attrs: AttributeSet?, + defStyleAttr: Int, + ) : super(context, attrs, defStyleAttr) + + constructor( + context: Context?, + attrs: AttributeSet?, + defStyleAttr: Int, + defStyleRes: Int, + ) : super(context, attrs, defStyleAttr, defStyleRes) + + override fun setShouldBlockVisibilityChanges(block: Boolean) { + delegate.setShouldBlockVisibilityChanges(block) + } + + override fun setVisibility(visibility: Int) { + delegate.setVisibility(visibility) + } + + override fun setTransitionVisibility(visibility: Int) { + delegate.setTransitionVisibility(visibility) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt index 484ae0a9c93bc..8dd506ec87754 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt @@ -31,8 +31,8 @@ import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import com.android.systemui.R -import com.android.systemui.common.ui.ContentDescriptionViewBinder -import com.android.systemui.common.ui.IconViewBinder +import com.android.systemui.common.ui.binder.ContentDescriptionViewBinder +import com.android.systemui.common.ui.binder.IconViewBinder import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.people.ui.view.PeopleViewBinder.bind import com.android.systemui.qs.FooterActionsView diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt index 5147d59340394..2731d64ee4e70 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt @@ -44,6 +44,7 @@ import com.android.settingslib.Utils import com.android.systemui.FontSizeUtils import com.android.systemui.R import com.android.systemui.animation.LaunchableView +import com.android.systemui.animation.LaunchableViewDelegate import com.android.systemui.plugins.qs.QSIconView import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.qs.QSTile.BooleanState @@ -138,8 +139,11 @@ open class QSTileViewImpl @JvmOverloads constructor( private var lastStateDescription: CharSequence? = null private var tileState = false private var lastState = INVALID - private var blockVisibilityChanges = false - private var lastVisibility = View.VISIBLE + private val launchableViewDelegate = LaunchableViewDelegate( + this, + superSetVisibility = { super.setVisibility(it) }, + superSetTransitionVisibility = { super.setTransitionVisibility(it) }, + ) private val locInScreen = IntArray(2) @@ -343,33 +347,15 @@ open class QSTileViewImpl @JvmOverloads constructor( } override fun setShouldBlockVisibilityChanges(block: Boolean) { - blockVisibilityChanges = block - - if (block) { - lastVisibility = visibility - } else { - visibility = lastVisibility - } + launchableViewDelegate.setShouldBlockVisibilityChanges(block) } override fun setVisibility(visibility: Int) { - if (blockVisibilityChanges) { - lastVisibility = visibility - return - } - - super.setVisibility(visibility) + launchableViewDelegate.setVisibility(visibility) } override fun setTransitionVisibility(visibility: Int) { - if (blockVisibilityChanges) { - // View.setTransitionVisibility just sets the visibility flag, so we don't have to save - // the transition visibility separately from the normal visibility. - lastVisibility = visibility - return - } - - super.setTransitionVisibility(visibility) + launchableViewDelegate.setTransitionVisibility(visibility) } // Accessibility diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedFrameLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedFrameLayout.java index 359272e8a7e03..662f70ef269ea 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedFrameLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedFrameLayout.java @@ -20,12 +20,28 @@ import android.content.Context; import android.util.AttributeSet; import android.widget.FrameLayout; +import com.android.systemui.animation.LaunchableView; +import com.android.systemui.animation.LaunchableViewDelegate; + +import kotlin.Unit; + /** * A frame layout which does not have overlapping renderings commands and therefore does not need a * layer when alpha is changed. */ -public class AlphaOptimizedFrameLayout extends FrameLayout +public class AlphaOptimizedFrameLayout extends FrameLayout implements LaunchableView { + private final LaunchableViewDelegate mLaunchableViewDelegate = new LaunchableViewDelegate( + this, + visibility -> { + super.setVisibility(visibility); + return Unit.INSTANCE; + }, + visibility -> { + super.setTransitionVisibility(visibility); + return Unit.INSTANCE; + }); + public AlphaOptimizedFrameLayout(Context context) { super(context); } @@ -47,4 +63,19 @@ public class AlphaOptimizedFrameLayout extends FrameLayout public boolean hasOverlappingRendering() { return false; } + + @Override + public void setShouldBlockVisibilityChanges(boolean block) { + mLaunchableViewDelegate.setShouldBlockVisibilityChanges(block); + } + + @Override + public void setVisibility(int visibility) { + mLaunchableViewDelegate.setVisibility(visibility); + } + + @Override + public void setTransitionVisibility(int visibility) { + mLaunchableViewDelegate.setTransitionVisibility(visibility); + } }