Implement LaunchableView for footer actions Views.

Bug: 243636422
Test: Manual
Change-Id: I0264b119d60cc70c1d9a23d056343c764242a955
This commit is contained in:
Jordan Demeulenaere
2022-08-24 12:33:06 +02:00
parent 4327ce40f8
commit ad33fb2b75
10 changed files with 202 additions and 42 deletions

View File

@@ -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)

View File

@@ -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)
}
}

View File

@@ -14,7 +14,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<FrameLayout
<com.android.systemui.statusbar.AlphaOptimizedFrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="@dimen/qs_footer_action_button_size"
android:layout_height="@dimen/qs_footer_action_button_size"
@@ -36,4 +36,4 @@
android:layout_gravity="bottom|end"
android:src="@drawable/fgs_dot"
android:contentDescription="@string/fgs_dot_content_description" />
</FrameLayout>
</com.android.systemui.statusbar.AlphaOptimizedFrameLayout>

View File

@@ -14,7 +14,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout
<com.android.systemui.common.ui.view.LaunchableLinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="0dp"
android:layout_height="@dimen/qs_security_footer_single_line_height"
@@ -63,4 +63,4 @@
android:src="@*android:drawable/ic_chevron_end"
android:autoMirrored="true"
android:tint="?android:attr/textColorSecondary" />
</LinearLayout>
</com.android.systemui.common.ui.view.LaunchableLinearLayout>

View File

@@ -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

View File

@@ -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

View File

@@ -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)
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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);
}
}